#!/usr/bin/env php E codecept.pharsrc/Codeception/Events.php (Y m%7!src/Codeception/TestInterface.php(Ysrc/Codeception/Extension.php (Y g)H)src/Codeception/PHPUnit/ResultPrinter.php(YU%src/Codeception/PHPUnit/Log/JUnit.php (Y @0src/Codeception/PHPUnit/Constraint/WebDriver.php(YzuM/src/Codeception/PHPUnit/Constraint/JsonType.php(Yox1src/Codeception/PHPUnit/Constraint/CrawlerNot.php4(Y4\3src/Codeception/PHPUnit/Constraint/WebDriverNot.phpx(Yx].src/Codeception/PHPUnit/Constraint/Crawler.php(Yʶ3src/Codeception/PHPUnit/Constraint/JsonContains.php(YXBɶ+src/Codeception/PHPUnit/Constraint/Page.php(Yl*src/Codeception/PHPUnit/ConsolePrinter.phpo(Yo$src/Codeception/PHPUnit/Listener.php=(Y=.0src/Codeception/PHPUnit/ResultPrinter/Report.php(YCHsrc/Codeception/PHPUnit/ResultPrinter/template/scenario_header.html.dist3(Y3ʤ=src/Codeception/PHPUnit/ResultPrinter/template/step.html.distb(Ybd>src/Codeception/PHPUnit/ResultPrinter/template/suite.html.dist3(Y3|=src/Codeception/PHPUnit/ResultPrinter/template/fail.html.dist;(Y;˟Asrc/Codeception/PHPUnit/ResultPrinter/template/scenario.html.dist(YS&Bsrc/Codeception/PHPUnit/ResultPrinter/template/scenarios.html.dist(YM.Asrc/Codeception/PHPUnit/ResultPrinter/template/substeps.html.dist#(Y#͠w.src/Codeception/PHPUnit/ResultPrinter/HTML.phpC"(YC"8,src/Codeception/PHPUnit/ResultPrinter/UI.php (Y "src/Codeception/PHPUnit/Runner.php(Y}&src/Codeception/PHPUnit/FilterTest.php(Y_#,src/Codeception/PHPUnit/Overrides/Filter.php (Y +8 src/Codeception/SuiteManager.php(YPsrc/Codeception/Example.php (Y Y.src/Codeception/Subscriber/ExtensionLoader.php(YP*src/Codeception/Subscriber/AutoRebuild.php(Yzc2src/Codeception/Subscriber/Shared/StaticEvents.php(YS&src/Codeception/Subscriber/Console.phpL(YLqZ%src/Codeception/Subscriber/Module.phpl (Yl gh(src/Codeception/Subscriber/Bootstrap.phpe(Ye炫+src/Codeception/Subscriber/Dependencies.php(Y|ۜ'src/Codeception/Subscriber/FailFast.php(Yt82src/Codeception/Subscriber/GracefulTermination.php(Y+src/Codeception/Subscriber/ErrorHandler.php(Yq0.src/Codeception/Subscriber/BeforeAfterTest.php(YŶ src/Codeception/InitTemplate.php(YS,src/Codeception/Application.php(Y!src/Codeception/Lib/Framework.php(Yr"i&src/Codeception/Lib/Interfaces/ORM.php?(Y?ζ/src/Codeception/Lib/Interfaces/MultiSession.php(Y{2/src/Codeception/Lib/Interfaces/PartedModule.php (Y O"V&src/Codeception/Lib/Interfaces/API.phpb(YbD^Ƕ1src/Codeception/Lib/Interfaces/ElementLocator.php(YN)src/Codeception/Lib/Interfaces/Remote.php(YbW/src/Codeception/Lib/Interfaces/ActiveRecord.phpA(YANŶ2src/Codeception/Lib/Interfaces/PageSourceSaver.phpU(YU*`2src/Codeception/Lib/Interfaces/SessionSnapshot.php(YA-src/Codeception/Lib/Interfaces/DataMapper.phpf(Yf?j3src/Codeception/Lib/Interfaces/DoctrineProvider.phpu(Yu'&src/Codeception/Lib/Interfaces/Web.phpm(Ymyi2src/Codeception/Lib/Interfaces/DependsOnModule.php_(Y_Ī2src/Codeception/Lib/Interfaces/ScreenshotSaver.phph(Yhh%src/Codeception/Lib/Interfaces/Db.php(Y(src/Codeception/Lib/Interfaces/Queue.php^(Y^^=2src/Codeception/Lib/Interfaces/RequiresPackage.php(YQ6src/Codeception/Lib/Interfaces/ConflictsWithModule.php(YhГsrc/Codeception/Lib/Di.php+(Y+Ňޱ,src/Codeception/Lib/Shared/LaravelCommon.phpw (Yw :<2src/Codeception/Lib/Generator/Shared/Classname.php(Y/!&src/Codeception/Lib/Generator/Test.phpI(YI-1src/Codeception/Lib/Generator/GherkinSnippets.php(YnO'src/Codeception/Lib/Generator/Group.phpB(YB+b,src/Codeception/Lib/Generator/PageObject.php(Y,src/Codeception/Lib/Generator/StepObject.php(Y`&src/Codeception/Lib/Generator/Cept.php(YuA)src/Codeception/Lib/Generator/Feature.php(YL<'src/Codeception/Lib/Generator/Actor.phpy(Yya&src/Codeception/Lib/Generator/Cest.php(YB)src/Codeception/Lib/Generator/Actions.php(Y(src/Codeception/Lib/Generator/Helper.php(Yyi6$src/Codeception/Lib/Notification.php(Y<$src/Codeception/Lib/ParamsLoader.php (Y \#src/Codeception/Lib/DbPopulator.phpz (Yz ~:'src/Codeception/Lib/ModuleContainer.php+9(Y+9D ,src/Codeception/Lib/Actor/Shared/Comment.php(Y`+src/Codeception/Lib/Actor/Shared/Friend.php(YtGsrc/Codeception/Lib/Parser.php(Y@C)src/Codeception/Lib/Console/Colorizer.phpQ(YQz &src/Codeception/Lib/Console/Output.php (Y cJ¶.src/Codeception/Lib/Console/MessageFactory.php(Y߻W'src/Codeception/Lib/Console/Message.php& (Y& )X+src/Codeception/Lib/Console/DiffFactory.phpB(YBj$src/Codeception/Lib/InnerBrowser.php(YDAsrc/Codeception/Lib/Friend.php(Y[$src/Codeception/Lib/GroupManager.php(Y+x)src/Codeception/Lib/Connector/Guzzle6.php+(Y+IJBAsrc/Codeception/Lib/Connector/Shared/PhpSuperGlobalsConverter.php(Y.6src/Codeception/Lib/Connector/Shared/LaravelCommon.php0(Y0Zն)src/Codeception/Lib/Connector/Symfony.php (Y s%src/Codeception/Lib/Connector/ZF2.php((Y(P3src/Codeception/Lib/Connector/Lumen/DummyKernel.php(YP|g1src/Codeception/Lib/Connector/Yii2/TestMailer.phpP(YPS*-src/Codeception/Lib/Connector/Yii2/Logger.phpg(Yg|B)4src/Codeception/Lib/Connector/Yii2/FixturesStore.php'(Y'|.7src/Codeception/Lib/Connector/Phalcon/MemorySession.php(YFC)Bsrc/Codeception/Lib/Connector/ZendExpressive/ResponseCollector.php(Y1Ķ)src/Codeception/Lib/Connector/Phalcon.php(YMǶ(src/Codeception/Lib/Connector/Guzzle.php#(Y#z&src/Codeception/Lib/Connector/Yii2.php(Yvp>src/Codeception/Lib/Connector/ZF2/PersistentServiceManager.php(Y}JDsrc/Codeception/Lib/Connector/Laravel5/ExceptionHandlerDecorator.php (Y J*src/Codeception/Lib/Connector/Laravel5.php*(Y*>&src/Codeception/Lib/Connector/Yii1.php(YYڶ'src/Codeception/Lib/Connector/Lumen.phpI(YI7'%src/Codeception/Lib/Connector/ZF1.phpw(Yw+src/Codeception/Lib/Connector/Universal.php(Y3F0src/Codeception/Lib/Connector/ZendExpressive.php!(Y!Ϲ"src/Codeception/Lib/Driver/Oci.php (Y N=&src/Codeception/Lib/Driver/MongoDb.php(YC܊%src/Codeception/Lib/Driver/Sqlite.phpu (Yu p2Z)src/Codeception/Lib/Driver/PostgreSql.php (Y aȴ$src/Codeception/Lib/Driver/MySql.php(Y5(src/Codeception/Lib/Driver/AmazonSQS.phpq(Yq ,(src/Codeception/Lib/Driver/Beanstalk.php:(Y:<ɬ!src/Codeception/Lib/Driver/Db.php_(Y_hV#src/Codeception/Lib/Driver/Iron.php (Y Xot%src/Codeception/Lib/Driver/SqlSrv.phpv (Yv WԄ'src/Codeception/Lib/Driver/Facebook.php(Y.src/Codeception/Coverage/DummyCodeCoverage.php(Y;p#src/Codeception/Coverage/Filter.php(Y-src/Codeception/Coverage/Subscriber/Local.phpr(Yre4src/Codeception/Coverage/Subscriber/RemoteServer.php(YKLĖ/src/Codeception/Coverage/Subscriber/Printer.php(Y#3src/Codeception/Coverage/Subscriber/LocalServer.php(Y,+,src/Codeception/Coverage/SuiteSubscriber.php (Y آ!src/Codeception/Template/Unit.php (Y cb src/Codeception/Template/Api.php (Y &src/Codeception/Template/Bootstrap.php(Yr 'src/Codeception/Template/Acceptance.phpA(YAsrc/Codeception/GroupObject.php(YDjf&src/Codeception/Util/StubMarshaler.php (Y pO'src/Codeception/Util/PropertyAccess.php(YN$src/Codeception/Util/sq.php4(Y4&src/Codeception/Util/Maybe.php(Y•.*src/Codeception/Util/Shared/Namespaces.php(Y&'src/Codeception/Util/Shared/Asserts.php)(Y)}-'src/Codeception/Util/ConsecutiveMap.phpX(YX٭f0src/Codeception/Util/ArrayContainsComparator.php(YT;)src/Codeception/Util/ReflectionHelper.php(Ys!src/Codeception/Util/HttpCode.php(Y ʂ#src/Codeception/Util/XmlBuilder.phpH(YHtŘsrc/Codeception/Util/Xml.phpz(Yz~HEsrc/Codeception/Util/Uri.phpO (YO ˶!src/Codeception/Util/Template.php(Y(ssrc/Codeception/Util/Debug.php(Y:/!src/Codeception/Util/Autoload.php(Yl_!src/Codeception/Util/JsonType.php"(Y"j !src/Codeception/Util/Fixtures.php(Y, ߶ src/Codeception/Util/Locator.php%(Y%Y#src/Codeception/Util/FileSystem.php; (Y; W%src/Codeception/Util/PathResolver.php(Yfq%src/Codeception/Util/XmlStructure.phpZ (YZ KPT#src/Codeception/Util/Annotation.php(YkM"src/Codeception/Util/JsonArray.php (Y MEX'src/Codeception/Util/ActionSequence.php (Y 2src/Codeception/Util/Stub.php[(Y[Oo)src/Codeception/Util/Soap.phpn(Yn4uKsrc/Codeception/Actor.php(YEjd׶2src/Codeception/Test/Interfaces/StrictCoverage.php(Y-src/Codeception/Test/Interfaces/Dependent.phpm(YmD=ض)src/Codeception/Test/Interfaces/Plain.phpn(YnE2src/Codeception/Test/Interfaces/ScenarioDriven.phpD(YD&</src/Codeception/Test/Interfaces/Descriptive.php(Yџ/,src/Codeception/Test/Interfaces/Reported.php(YG"+src/Codeception/Test/Test.php(Y#src/Codeception/Test/Descriptor.phpo (Yo csrc/Codeception/Test/Cept.php!(Y!wֶsrc/Codeception/Test/Unit.php(Y\_src/Codeception/Test/Loader.php(YdWPsrc/Codeception/Test/Cest.php=(Y=PEά2src/Codeception/Test/Feature/MetadataCollector.php(YY-src/Codeception/Test/Feature/CodeCoverage.php(YDM;b8src/Codeception/Test/Feature/IgnoreIfMetadataBlocked.php(Ynޠ,src/Codeception/Test/Feature/ErrorLogger.php(Yn/src/Codeception/Test/Feature/ScenarioLoader.phpC(YCN<1src/Codeception/Test/Feature/AssertionCounter.php(Y*QNI$src/Codeception/Test/Loader/Cept.php(YNގ$src/Codeception/Test/Loader/Unit.php(YvՃٶ$src/Codeception/Test/Loader/Cest.php~(Y~ۗ8'src/Codeception/Test/Loader/Gherkin.phpY(YYӶ/src/Codeception/Test/Loader/LoaderInterface.php(YF src/Codeception/Test/Gherkin.php(Ypol!src/Codeception/Test/Metadata.php(Y:Osrc/Codeception/Module.php-!(Y-!0&src/Codeception/Step/Skip.php|(Y|^yGsrc/Codeception/Step/Action.phpX(YXyM-src/Codeception/Step/ConditionalAssertion.php (Y .lӷ"src/Codeception/Step/Condition.phpy(Yyksrc/Codeception/Step/Meta.php(YPf src/Codeception/Step/Comment.php (Y m"src/Codeception/Step/Assertion.phpy(Yy֝?#src/Codeception/Step/Incomplete.php(YO3Ӷ!src/Codeception/Step/Executor.php(Y8 O!src/Codeception/Configuration.phpX(YXd0src/Codeception/Exception/InjectionException.phpX(YXBh-src/Codeception/Exception/RemoteException.php(YI~"src/Codeception/Exception/Skip.phpd(Yd“7src/Codeception/Exception/MalformedLocatorException.php(Y-m2src/Codeception/Exception/TestRuntimeException.phpa(Yaݜ4src/Codeception/Exception/ConfigurationException.php\(Y\L5src/Codeception/Exception/ModuleConflictException.php(Y73src/Codeception/Exception/ModuleConfigException.php(Y4-src/Codeception/Exception/ElementNotFound.phph(Yh 7-src/Codeception/Exception/ContentNotFound.phpr(YrP18src/Codeception/Exception/ConditionalAssertionFailed.php}(Y}},src/Codeception/Exception/ParseException.phpT(YTg¶0src/Codeception/Exception/TestParseException.php'(Y'Kv"src/Codeception/Exception/Fail.phph(Yh-src/Codeception/Exception/ModuleException.php(YJ0src/Codeception/Exception/ExtensionException.php(Y<)1src/Codeception/Exception/ConnectionException.php`(Y`LA(src/Codeception/Exception/Incomplete.phpm(Ym#M4src/Codeception/Exception/ModuleRequireException.php(Y12src/Codeception/Exception/ExternalUrlException.php[(Y[@Ŷ*src/Codeception/CustomCommandInterface.php(Yg(YG>F\src/Codeception/Module/ZF1.php (Y 漪%src/Codeception/Module/Filesystem.php(Y1T)src/Codeception/Module/ZendExpressive.php (Y dksrc/Codeception/Module/Apc.php(Y7%R src/Codeception/Module/Silex.phpW(YWisrc/Codeception/Module/Db.php~?(Y~?4u src/Codeception/Module/Queue.phpl+(Yl+#src/Codeception/Module/Sequence.php (Y 'V#src/Codeception/Module/Facebook.phpE((YE(K|src/Codeception/Codecept.phpx(Yx-zgsrc/Codeception/Suite.phpT(YT@|ext/DotReporter.php (Y |hqext/RunFailed.php(Yext/Logger.php! (Y! J>ext/Recorder.php((Y(_ext/SimpleReporter.php(Y/vendor/facebook/webdriver/lib/WebDriverWait.phpH(YH3<vendor/facebook/webdriver/lib/WebDriverExpectedCondition.php(YI3vendor/facebook/webdriver/lib/WebDriverKeyboard.php(Yb/vendor/facebook/webdriver/lib/WebDriverKeys.php/ (Y/ 1vendor/facebook/webdriver/lib/WebDriverAction.phpo(Yot.z0vendor/facebook/webdriver/lib/WebDriverPoint.phpj(Yjj8vendor/facebook/webdriver/lib/WebDriverTargetLocator.php(YD3vendor/facebook/webdriver/lib/WebDriverUpAction.php(YZ4vendor/facebook/webdriver/lib/Remote/RemoteMouse.php(Y]̛6vendor/facebook/webdriver/lib/Remote/DriverCommand.php(Y{K<vendor/facebook/webdriver/lib/Remote/DesiredCapabilities.php(Y靶6vendor/facebook/webdriver/lib/Remote/ExecuteMethod.php(YP<vendor/facebook/webdriver/lib/Remote/RemoteTargetLocator.phpK(YK#o8vendor/facebook/webdriver/lib/Remote/RemoteWebDriver.php(Y\~@vendor/facebook/webdriver/lib/Remote/WebDriverCapabilityType.php3(Y3{<vendor/facebook/webdriver/lib/Remote/RemoteExecuteMethod.php_(Y_}^>vendor/facebook/webdriver/lib/Remote/Service/DriverService.php(YzYFvendor/facebook/webdriver/lib/Remote/Service/DriverCommandExecutor.php(Ya<vendor/facebook/webdriver/lib/Remote/HttpCommandExecutor.phpP-(YP- <vendor/facebook/webdriver/lib/Remote/UselessFileDetector.php(Yf#:vendor/facebook/webdriver/lib/Remote/WebDriverResponse.php(Y|7vendor/facebook/webdriver/lib/Remote/RemoteKeyboard.php+(Y+l9vendor/facebook/webdriver/lib/Remote/WebDriverCommand.php(Y@_}5vendor/facebook/webdriver/lib/Remote/FileDetector.php(Yؘ9vendor/facebook/webdriver/lib/Remote/RemoteWebElement.php(Y<:vendor/facebook/webdriver/lib/Remote/RemoteTouchScreen.phpG(YG#:vendor/facebook/webdriver/lib/Remote/LocalFileDetector.php(YL=vendor/facebook/webdriver/lib/Remote/WebDriverBrowserType.php(YA\6vendor/facebook/webdriver/lib/Chrome/ChromeOptions.phpu(Yuz&%a<vendor/facebook/webdriver/lib/Chrome/ChromeDriverService.php(Y>A)5vendor/facebook/webdriver/lib/Chrome/ChromeDriver.php(Y$K8vendor/facebook/webdriver/lib/Firefox/FirefoxProfile.phpT(YT)KJ7vendor/facebook/webdriver/lib/Firefox/FirefoxDriver.php(YF~G<vendor/facebook/webdriver/lib/Firefox/FirefoxPreferences.php>(Y>ۖ6vendor/facebook/webdriver/lib/Support/XPathEscaper.php(Y;rPEvendor/facebook/webdriver/lib/Support/Events/EventFiringWebDriver.php(YGxFvendor/facebook/webdriver/lib/Support/Events/EventFiringWebElement.php(Yo=Ovendor/facebook/webdriver/lib/Support/Events/EventFiringWebDriverNavigation.php5(Y5Ϻv+vendor/facebook/webdriver/lib/WebDriver.php(YCNr:vendor/facebook/webdriver/lib/WebDriverSelectInterface.php?(Y?)0vendor/facebook/webdriver/lib/WebDriverAlert.php(YTŶ8vendor/facebook/webdriver/lib/WebDriverEventListener.php(Yu{:vendor/facebook/webdriver/lib/WebDriverHasInputDevices.php(YuJ3vendor/facebook/webdriver/lib/WebDriverTimeouts.php(YQfn80vendor/facebook/webdriver/lib/WebDriverMouse.php+(Y+x?7vendor/facebook/webdriver/lib/WebDriverCapabilities.phpB(YB}e4vendor/facebook/webdriver/lib/JavaScriptExecutor.php(YM;d5vendor/facebook/webdriver/lib/WebDriverNavigation.php(Y2k1vendor/facebook/webdriver/lib/WebDriverWindow.phpg(YgG32vendor/facebook/webdriver/lib/WebDriverOptions.php(Y:!AӶ8vendor/facebook/webdriver/lib/WebDriverSearchContext.php(Y 툶1vendor/facebook/webdriver/lib/WebDriverSelect.php(Y*h3vendor/facebook/webdriver/lib/WebDriverPlatform.php&(Y& Rs@vendor/facebook/webdriver/lib/Exception/NoSuchFrameException.phpw(YwWbdCvendor/facebook/webdriver/lib/Exception/NoScriptResultException.phpz(YzuFvendor/facebook/webdriver/lib/Exception/SessionNotCreatedException.php}(Y}$lJvendor/facebook/webdriver/lib/Exception/MoveTargetOutOfBoundsException.php(Y?öGvendor/facebook/webdriver/lib/Exception/InvalidCoordinatesException.php~(Y~&1[WIvendor/facebook/webdriver/lib/Exception/ElementNotSelectableException.php(Yc7Fvendor/facebook/webdriver/lib/Exception/UnexpectedTagNameException.php?(Y?P̶Evendor/facebook/webdriver/lib/Exception/NoSuchCollectionException.php|(Y|g퉶Dvendor/facebook/webdriver/lib/Exception/NoStringWrapperException.php{(Y{f6Bvendor/facebook/webdriver/lib/Exception/NoSuchElementException.phpy(Yyq=vendor/facebook/webdriver/lib/Exception/NoStringException.phpg(YgCֶBvendor/facebook/webdriver/lib/Exception/UnknownServerException.phpy(YyZCvendor/facebook/webdriver/lib/Exception/NoSuchDocumentException.phpz(Yz`_ƶAvendor/facebook/webdriver/lib/Exception/NoSuchWindowException.phpx(Yx,l]Dvendor/facebook/webdriver/lib/Exception/IMENotAvailableException.php{(Y{U =vendor/facebook/webdriver/lib/Exception/ExpectedException.phpt(Ytb>~ Hvendor/facebook/webdriver/lib/Exception/InvalidCookieDomainException.php(Y)4,ڶ@vendor/facebook/webdriver/lib/Exception/NullPointerException.phpw(YwCvendor/facebook/webdriver/lib/Exception/NoStringLengthException.phpz(YzqPNBvendor/facebook/webdriver/lib/Exception/WebDriverCurlException.phpy(Yy'Cvendor/facebook/webdriver/lib/Exception/UnknownCommandException.phpz(YzN(xHvendor/facebook/webdriver/lib/Exception/UnexpectedAlertOpenException.php(YR<vendor/facebook/webdriver/lib/Exception/TimeOutException.phps(YsQ+]3Jvendor/facebook/webdriver/lib/Exception/StaleElementReferenceException.php(Y+Hvendor/facebook/webdriver/lib/Exception/InvalidElementStateException.php(Y͕Jvendor/facebook/webdriver/lib/Exception/UnrecognizedExceptionException.php(YLģ@vendor/facebook/webdriver/lib/Exception/XPathLookupException.phpw(YwͯIvendor/facebook/webdriver/lib/Exception/UnexpectedJavascriptException.php(Y@xӶNvendor/facebook/webdriver/lib/Exception/IMEEngineActivationFailedException.php(YSAvendor/facebook/webdriver/lib/Exception/NoSuchDriverException.phpx(Yx4LJFvendor/facebook/webdriver/lib/Exception/UnableToSetCookieException.php}(Y}^>vendor/facebook/webdriver/lib/Exception/WebDriverException.php (Y Uı@vendor/facebook/webdriver/lib/Exception/NoAlertOpenException.phpw(YwSzFvendor/facebook/webdriver/lib/Exception/ElementNotVisibleException.php}(Y}+ Dvendor/facebook/webdriver/lib/Exception/InvalidSelectorException.php{(Y{tIvendor/facebook/webdriver/lib/Exception/UnsupportedOperationException.php(YxVAvendor/facebook/webdriver/lib/Exception/NoCollectionException.phpk(Yk?Bvendor/facebook/webdriver/lib/Exception/ScriptTimeoutException.phpy(Yy 57,Evendor/facebook/webdriver/lib/Exception/IndexOutOfBoundsException.phpo(Yox2:vendor/facebook/webdriver/lib/WebDriverCommandExecutor.php(Y#-vendor/facebook/webdriver/lib/WebDriverBy.phph(YhJhGvendor/facebook/webdriver/lib/Interactions/WebDriverCompositeAction.php(Y* ?vendor/facebook/webdriver/lib/Interactions/WebDriverActions.php<(Y<A~ӶDvendor/facebook/webdriver/lib/Interactions/WebDriverTouchActions.phpL (YL Hvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverDownAction.php(YpD}Hvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverMoveAction.php(Y8Mvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverLongPressAction.php (Y `Ivendor/facebook/webdriver/lib/Interactions/Touch/WebDriverTouchScreen.php(YޘжIvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverFlickAction.php(Y\,gIvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverTouchAction.phpz(YzjɶJvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverScrollAction.php(Y΍3Gvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverTapAction.php(Y_yC+Mvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverDoubleTapAction.php (Y  PTvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverFlickFromElementAction.php(Y-ĶUvendor/facebook/webdriver/lib/Interactions/Touch/WebDriverScrollFromElementAction.phpD(YDeSvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverClickAndHoldAction.php&(Y& )ؼPvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverSingleKeyAction.phpV(YV(qOvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverSendKeysAction.php(Y8Svendor/facebook/webdriver/lib/Interactions/Internal/WebDriverMoveToOffsetAction.php(Y Pvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverMouseMoveAction.php (Y Y~ҶTvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverButtonReleaseAction.php%(Y%_kfSvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverContextClickAction.php)(Y)4ӶNvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverKeyDownAction.php-(Y-׌Lvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverMouseAction.php(Y69Lvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverKeyUpAction.php-(Y-ÊRvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverDoubleClickAction.php$(Y$P.$Lvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverClickAction.php(YSRvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverKeysRelatedAction.php(Y3(VLvendor/facebook/webdriver/lib/Interactions/Internal/WebDriverCoordinates.phpn(Yn,k}0vendor/facebook/webdriver/lib/Net/URLChecker.php{(Y{Ͷ5vendor/facebook/webdriver/lib/WebDriverDispatcher.php_(Y_u(2vendor/facebook/webdriver/lib/WebDriverElement.php(Yz=vendor/facebook/webdriver/lib/Internal/WebDriverLocatable.php(Yb4vendor/facebook/webdriver/lib/WebDriverDimension.php(Ya4vendor/facebook/graph-sdk/src/Facebook/polyfills.php(YPp9vendor/facebook/graph-sdk/src/Facebook/FacebookClient.php (Y 5YFvendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphApplication.php(YKj~?vendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphUser.php+(Y+}wq@vendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphAlbum.php(Y:?vendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphList.phpj(Yj1F>vendor/facebook/graph-sdk/src/Facebook/GraphNodes/Birthday.php(Y?vendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphPage.php(Yvζ@vendor/facebook/graph-sdk/src/Facebook/GraphNodes/Collection.php(YѻFvendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphNodeFactory.phpq(YqEGCvendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphLocation.phpb(Yb @`?vendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphEdge.php (Y hu#@vendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphGroup.php(YĺVFvendor/facebook/graph-sdk/src/Facebook/GraphNodes/GraphSessionInfo.php~(Y~(Y>Yz;vendor/fzaninotto/faker/src/Faker/Provider/ko_KR/Person.php (Y rnJ@vendor/fzaninotto/faker/src/Faker/Provider/ko_KR/PhoneNumber.php (Y f@<vendor/fzaninotto/faker/src/Faker/Provider/ko_KR/Company.phpW(YW4:vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Color.php(YT<vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Address.php(Y"9vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Text.php}U(Y}U=vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Internet.php(YŐA;vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Person.php (Y ñ@vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/PhoneNumber.phpp(YpS<vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Company.php(Y[/\<vendor/fzaninotto/faker/src/Faker/Provider/me_ME/Address.php-+(Y-+Zn;vendor/fzaninotto/faker/src/Faker/Provider/me_ME/Person.phpO(YOl+@<vendor/fzaninotto/faker/src/Faker/Provider/me_ME/Payment.php(Ye@vendor/fzaninotto/faker/src/Faker/Provider/me_ME/PhoneNumber.php(Ym<vendor/fzaninotto/faker/src/Faker/Provider/me_ME/Company.php (Y Mw<vendor/fzaninotto/faker/src/Faker/Provider/en_AU/Address.phpa (Ya GQ=vendor/fzaninotto/faker/src/Faker/Provider/en_AU/Internet.phpT(YTO@vendor/fzaninotto/faker/src/Faker/Provider/en_AU/PhoneNumber.phpz(Yz<vendor/fzaninotto/faker/src/Faker/Provider/es_VE/Address.phpw(Ywbf=vendor/fzaninotto/faker/src/Faker/Provider/es_VE/Internet.phpv(YvYǥ;vendor/fzaninotto/faker/src/Faker/Provider/es_VE/Person.php,(Y,ݶ@vendor/fzaninotto/faker/src/Faker/Provider/es_VE/PhoneNumber.php(YL<vendor/fzaninotto/faker/src/Faker/Provider/es_VE/Company.php(Y`<vendor/fzaninotto/faker/src/Faker/Provider/hu_HU/Address.php (Y ~y9vendor/fzaninotto/faker/src/Faker/Provider/hu_HU/Text.phpP(YP%PE;vendor/fzaninotto/faker/src/Faker/Provider/hu_HU/Person.php (Y @vendor/fzaninotto/faker/src/Faker/Provider/hu_HU/PhoneNumber.php(Yx[<vendor/fzaninotto/faker/src/Faker/Provider/hu_HU/Company.php (Y B 5vendor/fzaninotto/faker/src/Faker/Provider/Biased.php(Yv:vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/Color.phpn(Ynض<vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/Address.php (Y Ŝ9vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/Text.phpWI(YWI+=vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/Internet.php(Y-P;vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/Person.php(YQ<vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/Payment.php (Y @vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/PhoneNumber.php(Y&j*<vendor/fzaninotto/faker/src/Faker/Provider/kk_KZ/Company.php(Y<vendor/fzaninotto/faker/src/Faker/Provider/en_PH/Address.php/^(Y/^~8vendor/fzaninotto/faker/src/Faker/Provider/UserAgent.php(YT*<vendor/fzaninotto/faker/src/Faker/Provider/at_AT/Payment.php(Ych3vendor/fzaninotto/faker/src/Faker/Provider/Uuid.php(Y+n<vendor/fzaninotto/faker/src/Faker/Provider/en_NZ/Address.php(Yތ=vendor/fzaninotto/faker/src/Faker/Provider/en_NZ/Internet.php#(Y#m@vendor/fzaninotto/faker/src/Faker/Provider/en_NZ/PhoneNumber.php(YFӶ<vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/Address.phpR(YR-)4=vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/Internet.php(Y ;vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/Person.php: (Y: n~נ@vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/PhoneNumber.php (Y 6/E<vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/Company.php[(Y[QAvendor/fzaninotto/faker/src/Faker/Provider/sr_Cyrl_RS/Address.php=(Y=̆k@vendor/fzaninotto/faker/src/Faker/Provider/sr_Cyrl_RS/Person.phpF.(YF.sLAvendor/fzaninotto/faker/src/Faker/Provider/sr_Cyrl_RS/Payment.php(Y̺<vendor/fzaninotto/faker/src/Faker/Provider/en_CA/Address.phpR(YRI!4w@vendor/fzaninotto/faker/src/Faker/Provider/en_CA/PhoneNumber.php1(Y1,<vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Address.php="(Y="c9vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Text.php6 (Y6 uX=vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Internet.php(Ya僶;vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Person.php(YL7<vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Payment.php(Y~׶@vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/PhoneNumber.php(YL5ض<vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Company.php (Y YB/<vendor/fzaninotto/faker/src/Faker/Provider/en_US/Address.php (Y ܩr9vendor/fzaninotto/faker/src/Faker/Provider/en_US/Text.php5(Y5]X;vendor/fzaninotto/faker/src/Faker/Provider/en_US/Person.php(Yj'ն@vendor/fzaninotto/faker/src/Faker/Provider/en_US/PhoneNumber.phpX(YXu<vendor/fzaninotto/faker/src/Faker/Provider/en_US/Company.php(YD[<vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Address.php(Y.;9vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Text.php(Yrj=vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Internet.php?(Y?֋;vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Person.phpm(Ym X'<vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Payment.php(YՀ@vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/PhoneNumber.php(Y?,=vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/DateTime.php(YY<vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Company.phpV (YV ö<vendor/fzaninotto/faker/src/Faker/Provider/el_GR/Address.phpcF(YcF1+;vendor/fzaninotto/faker/src/Faker/Provider/el_GR/Person.php(Y1Q<vendor/fzaninotto/faker/src/Faker/Provider/el_GR/Payment.php(Yϲȳ@vendor/fzaninotto/faker/src/Faker/Provider/el_GR/PhoneNumber.php0(Y0ы4vendor/fzaninotto/faker/src/Faker/Provider/Color.php (Y q}<vendor/fzaninotto/faker/src/Faker/Provider/fr_CA/Address.phpq(Yqls;vendor/fzaninotto/faker/src/Faker/Provider/fr_CA/Person.phpp(YpKAvendor/fzaninotto/faker/src/Faker/Provider/sr_Latn_RS/Address.php(Y̶@vendor/fzaninotto/faker/src/Faker/Provider/sr_Latn_RS/Person.php)(Y) 9zAvendor/fzaninotto/faker/src/Faker/Provider/sr_Latn_RS/Payment.php(Y8 <vendor/fzaninotto/faker/src/Faker/Provider/ar_JO/Address.phpo!(Yo!k좶9vendor/fzaninotto/faker/src/Faker/Provider/ar_JO/Text.php(Y$Y=vendor/fzaninotto/faker/src/Faker/Provider/ar_JO/Internet.phpN(YN,[;vendor/fzaninotto/faker/src/Faker/Provider/ar_JO/Person.phpH(YH'm<vendor/fzaninotto/faker/src/Faker/Provider/ar_JO/Company.php=(Y='<vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Address.php(Yp=vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Internet.php$(Y$?#};vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Person.php(Y}U <vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Payment.php(Y@vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/PhoneNumber.php(Ymf<vendor/fzaninotto/faker/src/Faker/Provider/Miscellaneous.php(Y*k.<vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Address.php(Y=vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Internet.php(YiMѶ;vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Person.php (Y v<vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Payment.php(Y`!߶@vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/PhoneNumber.php;(Y;YL<vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Company.phpQ(YQ:2<vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Address.phpI(YI* =vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Internet.php4(Y4l;vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Person.phpó(Yó`I<vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Payment.php(Y(a@vendor/fzaninotto/faker/src/Faker/Provider/is_IS/PhoneNumber.php(Y)<vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Company.php(YQK<vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Address.phpI(YI4&=vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Internet.php(Y;vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Person.phpq9(Yq9O@vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/PhoneNumber.phpH(YHD-<vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Company.php(Y;]<vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Address.php(Yk줶=vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Internet.phpr(Yr5;vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Person.phpa,(Ya,sE<vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Payment.php(Y2~@vendor/fzaninotto/faker/src/Faker/Provider/es_ES/PhoneNumber.php(Y[<vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Company.phpM(YMpN6vendor/fzaninotto/faker/src/Faker/Provider/Address.phpb(YbL¶<vendor/fzaninotto/faker/src/Faker/Provider/sv_SE/Address.phpxt(Yxtk;vendor/fzaninotto/faker/src/Faker/Provider/sv_SE/Person.php1(Y1x @vendor/fzaninotto/faker/src/Faker/Provider/sv_SE/PhoneNumber.php(Yf<vendor/fzaninotto/faker/src/Faker/Provider/sv_SE/Company.php(Y+<vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Address.php(Yۈb9vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Text.php4(Y4Wȉ)=vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Internet.php)(Y);vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Person.php(Y<vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Payment.php(Y44@vendor/fzaninotto/faker/src/Faker/Provider/it_IT/PhoneNumber.phpY(YY[Ҷ<vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Company.php(Yۧ<vendor/fzaninotto/faker/src/Faker/Provider/pt_PT/Address.php(Y܍5;vendor/fzaninotto/faker/src/Faker/Provider/pt_PT/Person.php(Yv<}<vendor/fzaninotto/faker/src/Faker/Provider/pt_PT/Payment.php(Y!C2:@vendor/fzaninotto/faker/src/Faker/Provider/pt_PT/PhoneNumber.php(Yz0<vendor/fzaninotto/faker/src/Faker/Provider/ne_NP/Address.php$6(Y$6o_=vendor/fzaninotto/faker/src/Faker/Provider/ne_NP/Internet.php(Ya;vendor/fzaninotto/faker/src/Faker/Provider/ne_NP/Person.php(YbQm̶@vendor/fzaninotto/faker/src/Faker/Provider/ne_NP/PhoneNumber.php(Y3vendor/fzaninotto/faker/src/Faker/Provider/Text.php (Y zo:vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Color.php>(Y><<vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Address.php]((Y](,χ9vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Text.phpb(Yb-8yH=vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Internet.phpQ(YQC~;vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Person.phpG%(YG%Y)<vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Payment.phpb(Ybdgm@vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/PhoneNumber.php(Y^\Ѷ<vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Company.phpO(YO7$<vendor/fzaninotto/faker/src/Faker/Provider/no_NO/Address.php*(Y*حϰ;vendor/fzaninotto/faker/src/Faker/Provider/no_NO/Person.php:k(Y:kO,@vendor/fzaninotto/faker/src/Faker/Provider/no_NO/PhoneNumber.php(Y+<vendor/fzaninotto/faker/src/Faker/Provider/no_NO/Company.php(Y v<vendor/fzaninotto/faker/src/Faker/Provider/en_UG/Address.php(Yu=vendor/fzaninotto/faker/src/Faker/Provider/en_UG/Internet.phpT(YT.T;vendor/fzaninotto/faker/src/Faker/Provider/en_UG/Person.php:$(Y:$7r@vendor/fzaninotto/faker/src/Faker/Provider/en_UG/PhoneNumber.php (Y {7vendor/fzaninotto/faker/src/Faker/Provider/Internet.php.(Y.$~<vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Address.php((Y(pJ9vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Text.php (Y RͶ=vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Internet.php2(Y21c;vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Person.phpoi(Yoi-:<vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Payment.php(Ys@vendor/fzaninotto/faker/src/Faker/Provider/de_DE/PhoneNumber.phpO(YOr@T<vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Company.php9(Y9.Զ<vendor/fzaninotto/faker/src/Faker/Provider/en_GB/Address.php!(Y!J=vendor/fzaninotto/faker/src/Faker/Provider/en_GB/Internet.phpN(YN>;vendor/fzaninotto/faker/src/Faker/Provider/en_GB/Person.phpf(YfXZ<vendor/fzaninotto/faker/src/Faker/Provider/en_GB/Payment.php(Y q@vendor/fzaninotto/faker/src/Faker/Provider/en_GB/PhoneNumber.php(Y"9vendor/fzaninotto/faker/src/Faker/Provider/ka_GE/Text.phpi(Yiv;vendor/fzaninotto/faker/src/Faker/Provider/ka_GE/Person.phpu%(Yu%Yi(<vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Address.phpe0(Ye0aVҶ9vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Text.php(YjKQ<vendor/fzaninotto/faker/src/Faker/Provider/sr_RS/Address.php8(Y8J5;vendor/fzaninotto/faker/src/Faker/Provider/sr_RS/Person.phpF.(YF.+<vendor/fzaninotto/faker/src/Faker/Provider/sr_RS/Payment.php(YǶ5vendor/fzaninotto/faker/src/Faker/Provider/Person.php(Y;6vendor/fzaninotto/faker/src/Faker/Provider/Barcode.php(Yo9<vendor/fzaninotto/faker/src/Faker/Provider/es_AR/Address.php(YH9;vendor/fzaninotto/faker/src/Faker/Provider/es_AR/Person.php((Y(l`杶@vendor/fzaninotto/faker/src/Faker/Provider/es_AR/PhoneNumber.php(Y`N<vendor/fzaninotto/faker/src/Faker/Provider/es_AR/Company.phpU(YU!V6vendor/fzaninotto/faker/src/Faker/Provider/Payment.php(Ymt<vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Address.php (Y U=vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Internet.php(Y^qٶ;vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Person.php (Y 0@vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/PhoneNumber.php(Yjֽ<vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Company.phpM(YMMo<vendor/fzaninotto/faker/src/Faker/Provider/en_ZA/Address.php(YTw=vendor/fzaninotto/faker/src/Faker/Provider/en_ZA/Internet.php+(Y+1af;vendor/fzaninotto/faker/src/Faker/Provider/en_ZA/Person.php}.(Y}.n@vendor/fzaninotto/faker/src/Faker/Provider/en_ZA/PhoneNumber.php (Y =a<vendor/fzaninotto/faker/src/Faker/Provider/sl_SI/Address.php-(Y-2XH=vendor/fzaninotto/faker/src/Faker/Provider/sl_SI/Internet.php=(Y=q⡪;vendor/fzaninotto/faker/src/Faker/Provider/sl_SI/Person.php(YΔ<<vendor/fzaninotto/faker/src/Faker/Provider/sl_SI/Payment.php(Y&@vendor/fzaninotto/faker/src/Faker/Provider/sl_SI/PhoneNumber.php!(Y! 깂3vendor/fzaninotto/faker/src/Faker/Provider/Base.php(Y~ <vendor/fzaninotto/faker/src/Faker/Provider/fi_FI/Address.phpH(YH)վ?=vendor/fzaninotto/faker/src/Faker/Provider/fi_FI/Internet.php7(Y7Bö;vendor/fzaninotto/faker/src/Faker/Provider/fi_FI/Person.php6(Y6% @vendor/fzaninotto/faker/src/Faker/Provider/fi_FI/PhoneNumber.phpI(YIR<vendor/fzaninotto/faker/src/Faker/Provider/fi_FI/Company.php+(Y+xsY:vendor/fzaninotto/faker/src/Faker/Provider/vi_VN/Color.php'(Y'+<vendor/fzaninotto/faker/src/Faker/Provider/vi_VN/Address.php\(Y\L=vendor/fzaninotto/faker/src/Faker/Provider/vi_VN/Internet.phpN(YN%;vendor/fzaninotto/faker/src/Faker/Provider/vi_VN/Person.php%(Y%wd1@vendor/fzaninotto/faker/src/Faker/Provider/vi_VN/PhoneNumber.php(Yp=vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Internet.php6(Y6o+d;vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Person.php{}(Y{} ]߶<vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Payment.php(Y/@vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/PhoneNumber.phpJ(YJ-mu<vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Address.php(Yoq=vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Internet.php(YYնԶ;vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Person.php(Ys<vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Payment.php(Y }@vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/PhoneNumber.php;(Y;B^<vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Company.php@(Y@]p<vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Address.php(Yg\=vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Internet.phpD(YD;vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Person.php)(Y)-<vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Payment.php(Y ƪ@vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/PhoneNumber.php(Y_"t<vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Company.php(Y"or4vendor/fzaninotto/faker/src/Faker/Provider/Image.php-(Y-٫<vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Address.phpu(Yu3u=vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Internet.php(Y>;vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Person.phpht(Yhtr<vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Payment.php(Y8R@vendor/fzaninotto/faker/src/Faker/Provider/de_AT/PhoneNumber.php-(Y-'<vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Company.php'(Y' :vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Color.php( (Y( t<vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Address.php? (Y? g=vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Internet.phpu(Yu;vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Person.php(YP<vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Payment.php(YA砶@vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/PhoneNumber.phpW(YW=vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/DateTime.php(Y|^"d<vendor/fzaninotto/faker/src/Faker/Provider/be_BE/Payment.php(YPg:vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/Color.phpz (Yz \<vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/Address.php@(Y@9vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/Text.php<(Y<mx=vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/Internet.php(YZ~;vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/Person.php(YI<vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/Payment.php(YC@vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/PhoneNumber.php*(Y*?(4=vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/DateTime.php(Y!O9<vendor/fzaninotto/faker/src/Faker/Provider/zh_TW/Company.php (Y g9:vendor/fzaninotto/faker/src/Faker/Provider/PhoneNumber.php(YF9vendor/fzaninotto/faker/src/Faker/Provider/fa_IR/Text.php)$(Y)$#=vendor/fzaninotto/faker/src/Faker/Provider/fa_IR/Internet.php (Y D2A;vendor/fzaninotto/faker/src/Faker/Provider/fa_IR/Person.php;(Y;<vendor/fzaninotto/faker/src/Faker/Provider/id_ID/Address.php)(Y).ֶ=vendor/fzaninotto/faker/src/Faker/Provider/id_ID/Internet.php(YFY;vendor/fzaninotto/faker/src/Faker/Provider/id_ID/Person.php*(Y*c@vendor/fzaninotto/faker/src/Faker/Provider/id_ID/PhoneNumber.php(Y<3t<vendor/fzaninotto/faker/src/Faker/Provider/id_ID/Company.php(Y-7vendor/fzaninotto/faker/src/Faker/Provider/DateTime.php (Y X<vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Address.php(Yjf=vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Internet.phpD(YDY7;vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Person.php (Y <vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Payment.php(Yε@vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/PhoneNumber.php(Y@vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/check_digit.php(Y$<vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Company.php(YZ<vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/Address.php)(Y)0;vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/Person.php3(Y30f@vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/PhoneNumber.phpx(YxS3vendor/fzaninotto/faker/src/Faker/Provider/File.phpZP(YZPp6vendor/fzaninotto/faker/src/Faker/Provider/Company.php(Yro5vendor/fzaninotto/faker/src/Faker/Calculator/Luhn.phpu(YuXޝ(vendor/fzaninotto/faker/src/autoload.php (Y 1*vendor/phpunit/phpunit/src/Util/Getopt.php (Y !_9vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php(Y.XDvendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist (Y 3#9vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php (Y yLe2vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php>(Y>q:vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php?(Y?IW9vendor/phpunit/phpunit/src/Util/InvalidArgumentHelper.php(YDX(vendor/phpunit/phpunit/src/Util/Test.phpPY(YPY/vendor/phpunit/phpunit/src/Util/GlobalState.php (Y w0vendor/phpunit/phpunit/src/Util/Log/TeamCity.phpJ(YJ]) -vendor/phpunit/phpunit/src/Util/Log/JUnit.phpc(Yc׶*vendor/phpunit/phpunit/src/Util/Filter.phpy(Yy3{'vendor/phpunit/phpunit/src/Util/Xml.php (Y ;(vendor/phpunit/phpunit/src/Util/Type.php(Yqq[.vendor/phpunit/phpunit/src/Util/Fileloader.php(Y;+vendor/phpunit/phpunit/src/Util/Printer.php(Y4=vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php(YG9vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php+(Y+|<vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php (Y !=vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php(YQ:vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php(YȺ1vendor/phpunit/phpunit/src/Util/Configuration.phpL(YL:L.vendor/phpunit/phpunit/src/Util/Filesystem.php(YT5vendor/phpunit/phpunit/src/Util/RegularExpression.php_(Y_ݶ-vendor/phpunit/phpunit/src/Util/Blacklist.php(YV;0vendor/phpunit/phpunit/src/Util/ErrorHandler.php(YX3N:vendor/phpunit/phpunit/src/Util/ConfigurationGenerator.php(Y5vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php(Ysw2vendor/phpunit/phpunit/src/Runner/PhptTestCase.phpI(YI %/vendor/phpunit/phpunit/src/Runner/Exception.phpu(Yu=vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php (Y #hGvendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php(Y@P4vendor/phpunit/phpunit/src/Runner/Filter/Factory.php(Yx$Gvendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php(YNѶ?vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php(Y~v@vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.phpO(YO"ʫ4vendor/phpunit/phpunit/src/Runner/BaseTestRunner.phpr(YrՓe-vendor/phpunit/phpunit/src/Runner/Version.php(Y72vendor/phpunit/phpunit/src/Framework/TestSuite.php7(Y7<vendor/phpunit/phpunit/src/Framework/IncompleteTestError.php(Yʢ-vendor/phpunit/phpunit/src/Framework/Test.php(YLt>vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php(Y8N&2vendor/phpunit/phpunit/src/Framework/RiskyTest.phpI(YI"7vendor/phpunit/phpunit/src/Framework/SyntheticError.php(Y߬)7vendor/phpunit/phpunit/src/Framework/IncompleteTest.phpN(YNK9vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php(YBHvendor/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.phpq(YqX޶Evendor/phpunit/phpunit/src/Framework/InvalidCoversTargetException.phpq(YqB/vendor/phpunit/phpunit/src/Framework/Assert.phpʫ(Yʫfōp1vendor/phpunit/phpunit/src/Framework/TestCase.phpX(YXd;2@>vendor/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php(Y=Ivendor/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.phps(YsJ¶>vendor/phpunit/phpunit/src/Framework/CodeCoverageException.php_(Y_XDB9vendor/phpunit/phpunit/src/Framework/Assert/Functions.phpiP(YiP4>vendor/phpunit/phpunit/src/Framework/Constraint/IsWritable.phpW(YW"Uvendor/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegularExpression.phpz(YzMuKvendor/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php(Y*?:vendor/phpunit/phpunit/src/Framework/Constraint/IsNull.php(YC-@;vendor/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php(Y 6Bvendor/phpunit/phpunit/src/Framework/Constraint/StringContains.php(Ys/;vendor/phpunit/phpunit/src/Framework/Constraint/IsFalse.php(YBvendor/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php(Yvr>vendor/phpunit/phpunit/src/Framework/Constraint/LogicalAnd.php(YkQP@vendor/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.phpo(Yo)Svendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php(Y!WGvendor/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php(Y+.>vendor/phpunit/phpunit/src/Framework/Constraint/LogicalNot.phpt(Yt9vendor/phpunit/phpunit/src/Framework/Constraint/Count.php(YM<vendor/phpunit/phpunit/src/Framework/Constraint/SameSize.php(Y>vendor/phpunit/phpunit/src/Framework/Constraint/FileExists.phpW(YWfʷ=vendor/phpunit/phpunit/src/Framework/Constraint/LogicalOr.phpi(Yi떶Kvendor/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php\(Y\Yn89vendor/phpunit/phpunit/src/Framework/Constraint/IsNan.php(Yb<;vendor/phpunit/phpunit/src/Framework/Constraint/IsEqual.php(Yͩ;K>vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php(Y5m?vendor/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php(Y0EF?vendor/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php(YYAvendor/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.phpp(YpD"<vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php(Y=vendor/phpunit/phpunit/src/Framework/Constraint/Attribute.php(Yv7?vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php(Y[d>vendor/phpunit/phpunit/src/Framework/Constraint/IsReadable.phpW(YW՜>vendor/phpunit/phpunit/src/Framework/Constraint/LogicalXor.php(YDvendor/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.phpV(YVzy<vendor/phpunit/phpunit/src/Framework/Constraint/LessThan.php(Y`g?vendor/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php(Y/:vendor/phpunit/phpunit/src/Framework/Constraint/IsTrue.php(Y Evendor/phpunit/phpunit/src/Framework/Constraint/RegularExpression.php(YRvendor/phpunit/phpunit/src/Framework/Constraint/StringMatchesFormatDescription.phpa(Yau򺯶?vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php(Y^_>vendor/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php(YV:vendor/phpunit/phpunit/src/Framework/Constraint/IsType.php(Y>z!Evendor/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php(YH=vendor/phpunit/phpunit/src/Framework/Constraint/Exception.php(YR ٶDvendor/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php(Y%>vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php(Y'Fvendor/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php(YjM=vendor/phpunit/phpunit/src/Framework/Constraint/Composite.php(Y'WҶ:vendor/phpunit/phpunit/src/Framework/Constraint/IsJson.php(Y=I<vendor/phpunit/phpunit/src/Framework/Constraint/IsFinite.php(Y:ȶCvendor/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.phpa(YaU8o4vendor/phpunit/phpunit/src/Framework/OutputError.phpc(Yc1=vendor/phpunit/phpunit/src/Framework/AssertionFailedError.php(YBɶ8vendor/phpunit/phpunit/src/Framework/WarningTestCase.phpM(YMwd:vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php(YN5vendor/phpunit/phpunit/src/Framework/TestListener.php(Y~(̶Cvendor/phpunit/phpunit/src/Framework/ExpectationFailedException.php(Y;˶Hvendor/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.phpr(YrO0vendor/phpunit/phpunit/src/Framework/Warning.php(Y{7vendor/phpunit/phpunit/src/Framework/RiskyTestError.php{(Y{wYƶ9vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php|(Y|NM޶5vendor/phpunit/phpunit/src/Framework/Error/Notice.phpx(Yx+X"06vendor/phpunit/phpunit/src/Framework/Error/Warning.phpy(Yy|Q4vendor/phpunit/phpunit/src/Framework/Error/Error.php;(Y; |9vendor/phpunit/phpunit/src/Framework/SkippedTestError.php(Y=o9vendor/phpunit/phpunit/src/Framework/BaseTestListener.php(Ye۶4vendor/phpunit/phpunit/src/Framework/TestFailure.php(Y8vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php(Y~;2vendor/phpunit/phpunit/src/Framework/Exception.php:(Y:pf4vendor/phpunit/phpunit/src/Framework/SkippedTest.phpJ(YJM3vendor/phpunit/phpunit/src/Framework/TestResult.phpC(YCrj7vendor/phpunit/phpunit/src/Framework/SelfDescribing.phpn(Yn a;vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php(Y9W(vendor/phpunit/phpunit/src/Exception.php>(Y>.03vendor/phpunit/phpunit/src/TextUI/ResultPrinter.phpk((Yk(/20vendor/phpunit/phpunit/src/TextUI/TestRunner.phpt(YtQ-vendor/phpunit/phpunit/src/TextUI/Command.php_(Y_8 -vendor/phpunit/phpunit/build/phar-version.php(YhP 9.vendor/phpunit/phpunit/build/phar-manifest.php(Yw(vendor/phpunit/phpunit/build/version.php(Y[G0vendor/phpunit/php-file-iterator/src/Factory.phpd(Ydmp/vendor/phpunit/php-file-iterator/src/Facade.php(Y?-ɶ1vendor/phpunit/php-file-iterator/src/Iterator.php(YN&vendor/phpunit/php-timer/src/Timer.php(Y{7vendor/phpunit/php-code-coverage/src/Node/Directory.php(YK5vendor/phpunit/php-code-coverage/src/Node/Builder.php (Y 5:vendor/phpunit/php-code-coverage/src/Node/AbstractNode.php (Y XA6vendor/phpunit/php-code-coverage/src/Node/Iterator.php(Y΄Б2vendor/phpunit/php-code-coverage/src/Node/File.php)(Y)뎧/vendor/phpunit/php-code-coverage/src/Filter.php(Y<373vendor/phpunit/php-code-coverage/src/Report/PHP.php(Y4x=vendor/phpunit/php-code-coverage/src/Report/Xml/Directory.phpf(Yfk%a:vendor/phpunit/php-code-coverage/src/Report/Xml/Report.phpm(Ymt@8vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php((Y(Ѵ8vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php(YqL;vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php(Y[i3<vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php(Y":vendor/phpunit/php-code-coverage/src/Report/Xml/Source.phpw(YwrDvendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php(Y#w:vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php(YE@:vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.phpx(YxN:vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php (Y 9vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php(Y[잶8vendor/phpunit/php-code-coverage/src/Report/Xml/File.phpz(Yz(L4vendor/phpunit/php-code-coverage/src/Report/Text.php(Y.6vendor/phpunit/php-code-coverage/src/Report/Clover.php(YEGvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php2(Y2 ‚Gvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php(YFPvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css+(Y+Y`gTvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.cssX%(YX%0,Xvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.cssp(YpX|FMvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js(Y'Ovendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.jsP(YPhbVvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js(Y/jRvendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/nv.d3.min.jsR(YRvendor/phpunit/phpunit-mock-objects/src/Matcher/MethodName.php7(Y76Cvendor/phpunit/phpunit-mock-objects/src/Matcher/InvokedRecorder.phpw(YwM#Fvendor/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtMostCount.php(YBxIvendor/phpunit/phpunit-mock-objects/src/Matcher/ConsecutiveParameters.phpD(YDX%;>vendor/phpunit/phpunit-mock-objects/src/Matcher/Invocation.phpm(YmLzFvendor/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtLeastOnce.php(YP>vendor/phpunit/phpunit-mock-objects/src/Matcher/Parameters.php (Y _I@Avendor/phpunit/phpunit-mock-objects/src/Matcher/AnyParameters.php4(Y4JjBvendor/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtIndex.php(Yo5ƶ6vendor/phpunit/phpunit-mock-objects/src/Invocation.phpr(Yr- Kh7vendor/phpunit/phpunit-mock-objects/src/MockBuilder.php%(Y%"5vendor/phpunit/phpunit-mock-objects/src/Generator.phpV(YV\6vendor/phpunit/phpunit-mock-objects/src/Verifiable.php(Yh0Fvendor/phpunit/phpunit-mock-objects/src/Exception/RuntimeException.php(YyR?vendor/phpunit/phpunit-mock-objects/src/Exception/Exception.phpG(YGl;Lvendor/phpunit/phpunit-mock-objects/src/Exception/BadMethodCallException.php(Y[h@vendor/phpunit/phpunit-mock-objects/src/Stub/ReturnReference.php(YQw?vendor/phpunit/phpunit-mock-objects/src/Stub/ReturnCallback.php7(Y7aq&7vendor/phpunit/phpunit-mock-objects/src/Stub/Return.php(Y ٶ?vendor/phpunit/phpunit-mock-objects/src/Stub/ReturnArgument.php#(Y#!TE;vendor/phpunit/phpunit-mock-objects/src/Stub/ReturnSelf.php(Y 7<?vendor/phpunit/phpunit-mock-objects/src/Stub/ReturnValueMap.phpr(Yr|kOAvendor/phpunit/phpunit-mock-objects/src/Stub/ConsecutiveCalls.php(YbBvendor/phpunit/phpunit-mock-objects/src/Stub/MatcherCollection.php(Y&ݲ:vendor/phpunit/phpunit-mock-objects/src/Stub/Exception.php(Y==vendor/phpunit/phpunit-mock-objects/src/Invocation/Static.php (Y ׶=vendor/phpunit/phpunit-mock-objects/src/Invocation/Object.php(YN5vendor/phpunit/phpunit-mock-objects/src/Invokable.php%(Y%Ƕ0vendor/phpunit/phpunit-mock-objects/src/Stub.php(Yzm-vendor/phpunit/php-token-stream/src/Token.phpH(YHg+4vendor/phpunit/php-token-stream/src/Token/Stream.php"(Y"lCvendor/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php(Y|c1vendor/phpunit/php-text-template/src/Template.php(YI)vendor/vlucas/phpdotenv/src/Validator.php)(Y)=¶&vendor/vlucas/phpdotenv/src/Dotenv.phpF(YF 9&vendor/vlucas/phpdotenv/src/Loader.php)(Y)ʷBvendor/vlucas/phpdotenv/src/Exception/InvalidCallbackException.php(Y۶<vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.phpI(YIO*=vendor/vlucas/phpdotenv/src/Exception/ValidationException.php(Y>vendor/vlucas/phpdotenv/src/Exception/InvalidPathException.php(YO3>vendor/vlucas/phpdotenv/src/Exception/InvalidFileException.php(Y5 vendor/autoload.php(Y2vendor/phar-io/manifest/src/ManifestSerializer.php(YDvendor/phar-io/manifest/src/exceptions/ManifestDocumentException.phpv(YvCvendor/phar-io/manifest/src/exceptions/ManifestElementException.phpu(Yuw/Jvendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php(YŖ@>vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php(Y;@vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php(Y Jvendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php|(Y|A Bvendor/phar-io/manifest/src/exceptions/ManifestLoaderException.phpm(Ym8L:4vendor/phar-io/manifest/src/exceptions/Exception.phpC(YC.vendor/phar-io/manifest/src/ManifestLoader.php(YW0vendor/phar-io/manifest/src/values/Extension.phpc(Yc$QN<vendor/phar-io/manifest/src/values/PhpVersionRequirement.phpn(Yn3.vendor/phar-io/manifest/src/values/Library.phpz(Yz}*vendor/phar-io/manifest/src/values/Url.phpl(YlPv-vendor/phar-io/manifest/src/values/Author.php(YhL.vendor/phar-io/manifest/src/values/License.php%(Y%ssemAvendor/phar-io/manifest/src/values/BundledComponentCollection.php(Yy#;vendor/phar-io/manifest/src/values/CopyrightInformation.phpq(Yq԰R/vendor/phar-io/manifest/src/values/Manifest.php-(Y-i2vendor/phar-io/manifest/src/values/Application.php(Y:+vendor/phar-io/manifest/src/values/Type.php/(Y/Dvendor/phar-io/manifest/src/values/RequirementCollectionIterator.phpX(YXg6vendor/phar-io/manifest/src/values/ApplicationName.php0(Y0Bj?vendor/phar-io/manifest/src/values/AuthorCollectionIterator.php+(Y+ퟶ,vendor/phar-io/manifest/src/values/Email.php(Y=G>vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php(Y5[{Ivendor/phar-io/manifest/src/values/BundledComponentCollectionIterator.php(YP<vendor/phar-io/manifest/src/values/RequirementCollection.php(YD87vendor/phar-io/manifest/src/values/AuthorCollection.php(Y!Qy7vendor/phar-io/manifest/src/values/BundledComponent.phpk(Yk42vendor/phar-io/manifest/src/values/Requirement.phpE(YE31vendor/phar-io/manifest/src/xml/AuthorElement.php(Yu3vendor/phar-io/manifest/src/xml/RequiresElement.php(Yr1Ƕ;vendor/phar-io/manifest/src/xml/AuthorElementCollection.php(YeP4vendor/phar-io/manifest/src/xml/ManifestDocument.php(Y#9Dvendor/phar-io/manifest/src/xml/ManifestDocumentLoadingException.php(YTj=H2vendor/phar-io/manifest/src/xml/LicenseElement.php(Y~e4vendor/phar-io/manifest/src/xml/ComponentElement.php(Y{t>vendor/phar-io/manifest/src/xml/ComponentElementCollection.php(YWz\4vendor/phar-io/manifest/src/xml/ExtensionElement.php(YG48vendor/phar-io/manifest/src/xml/ExtElementCollection.php(Y"Bܶ5vendor/phar-io/manifest/src/xml/ElementCollection.php(Y.vendor/phar-io/manifest/src/xml/PhpElement.phpT(YT^ m4vendor/phar-io/manifest/src/xml/CopyrightElement.php8(Y8y3vendor/phar-io/manifest/src/xml/ManifestElement.php(Y R.vendor/phar-io/manifest/src/xml/ExtElement.php(Yƽ?3vendor/phar-io/manifest/src/xml/ContainsElement.php(Yms2vendor/phar-io/manifest/src/xml/BundlesElement.php(Y8AU߶6vendor/phar-io/manifest/src/ManifestDocumentMapper.php (Y Cv/vendor/phar-io/manifest/examples/example-01.php(Y G/vendor/phar-io/version/src/PreReleaseSuffix.phpC(YCଶ0vendor/phar-io/version/src/VersionConstraint.php(Yl^1,vendor/phar-io/version/src/VersionNumber.php*(Y*K=vendor/phar-io/version/src/SpecificMajorVersionConstraint.phpt(Yti8vendor/phar-io/version/src/AbstractVersionConstraint.php/(Y/jk3vendor/phar-io/version/src/AnyVersionConstraint.php(Ya75vendor/phar-io/version/src/ExactVersionConstraint.php(YjXDvendor/phar-io/version/src/UnsupportedVersionConstraintException.php(Y$H6vendor/phar-io/version/src/InvalidVersionException.phpz(Yzϡ>CEvendor/phar-io/version/src/SpecificMajorAndMinorVersionConstraint.php(YԽ 7vendor/phar-io/version/src/OrVersionConstraintGroup.php(Y_ҸDvendor/phar-io/version/src/GreaterThanOrEqualToVersionConstraint.php(Yq 5vendor/phar-io/version/src/VersionConstraintValue.phpZ(YZP`8vendor/phar-io/version/src/AndVersionConstraintGroup.php(Y6vendor/phar-io/version/src/VersionConstraintParser.php(Y(vendor/phar-io/version/src/Exception.phpB(YB &vendor/phar-io/version/src/Version.php} (Y} m[?vendor/sebastian/resource-operations/src/ResourceOperations.php6(Y6 7vendor/sebastian/resource-operations/build/generate.php(Y{K(#vendor/sebastian/diff/src/Chunk.php"(Y"0Wvendor/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php (Y cC:vendor/sebastian/diff/src/LCS/LongestCommonSubsequence.php(Y(UUvendor/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php(Ym"vendor/sebastian/diff/src/Line.php(YM9ֶ$vendor/sebastian/diff/src/Differ.phpq(Yqj$vendor/sebastian/diff/src/Parser.php(Y6a."vendor/sebastian/diff/src/Diff.php(Y}G+vendor/sebastian/comparator/src/Factory.php#(Y#6}8vendor/sebastian/comparator/src/MockObjectComparator.php(Y5vendor/sebastian/comparator/src/NumericComparator.phpK(YK2vendor/sebastian/comparator/src/TypeComparator.php(YL;4vendor/sebastian/comparator/src/DoubleComparator.php&(Y&U.vendor/sebastian/comparator/src/Comparator.php(YmPw3vendor/sebastian/comparator/src/ArrayComparator.php(YN4vendor/sebastian/comparator/src/ScalarComparator.phpd(Yd$4vendor/sebastian/comparator/src/ObjectComparator.phpf(Yfe¶5vendor/sebastian/comparator/src/ComparisonFailure.php(Ysf6vendor/sebastian/comparator/src/DateTimeComparator.php(Yj)6vendor/sebastian/comparator/src/ResourceComparator.php(YlU>vendor/sebastian/comparator/src/SplObjectStorageComparator.phpn(YnRjY7vendor/sebastian/comparator/src/ExceptionComparator.php(YH7-5vendor/sebastian/comparator/src/DOMNodeComparator.phpa(Ya򋽶4vendor/sebastian/object-enumerator/src/Exception.phpV(YVcCvendor/sebastian/object-enumerator/src/InvalidArgumentException.php(Y5vendor/sebastian/object-enumerator/src/Enumerator.phpi(Yi禷2vendor/sebastian/recursion-context/src/Context.phpR(YR^k_4vendor/sebastian/recursion-context/src/Exception.phpX(YXN3Cvendor/sebastian/recursion-context/src/InvalidArgumentException.php(Y>_F,vendor/sebastian/environment/src/Console.php(YSMӶ,vendor/sebastian/environment/src/Runtime.php(YZ9vendor/sebastian/object-reflector/src/ObjectReflector.phpC(YC"3vendor/sebastian/object-reflector/src/Exception.phpo(YoBvendor/sebastian/object-reflector/src/InvalidArgumentException.php(Y]˶*vendor/sebastian/exporter/src/Exporter.php(Y5orAvendor/sebastian/global-state/src/exceptions/RuntimeException.php(Yxt:vendor/sebastian/global-state/src/exceptions/Exception.phpk(YkSvW.vendor/sebastian/global-state/src/Snapshot.phpg(Yg^[2vendor/sebastian/global-state/src/CodeExporter.php(Y2F.vendor/sebastian/global-state/src/Restorer.phpo (Yo XX/vendor/sebastian/global-state/src/Blacklist.php(Y !L8vendor/sebastian/code-unit-reverse-lookup/src/Wizard.phpZ(YZ(vendor/sebastian/version/src/Version.phpu(Yu*>Pvendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php(Y,A¶Gvendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php(YyGWvendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.phpj(Yjζ]vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php(Yxi]vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php(YpEvendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php(Y/*Avendor/myclabs/deep-copy/src/DeepCopy/TypeMatcher/TypeMatcher.php(Y@PGEvendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php(Y9vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Matcher.phpo(Yo,eնOvendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php(Y:SAvendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyMatcher.phpj(YjoLEvendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php(Y31Bvendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php`(Y`.ڝBvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php(Y'w%Lvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php(Y]"?vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.phpe(YeS<_Fvendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php(Y;vendor/myclabs/deep-copy/src/DeepCopy/Filter/KeepFilter.php(Y;7vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php{(Y{U겶Mvendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php(Y̶Wvendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php(Y Rvendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php((Y(>vendor/myclabs/deep-copy/src/DeepCopy/Filter/ReplaceFilter.php(Y/>vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.phpD(YDvދ2vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php (Y ̋$*vendor/myclabs/deep-copy/doc/deep-copy.png*(Y*i9+vendor/myclabs/deep-copy/doc/deep-clone.png6(Y6JŨ&vendor/myclabs/deep-copy/doc/graph.png$(Y$P϶&vendor/myclabs/deep-copy/doc/clone.png\0(Y\0/vendor/league/factory-muffin/src/Definition.phpm(Ymʐ2vendor/league/factory-muffin/src/FactoryMuffin.php(YuFvendor/league/factory-muffin/src/Exceptions/ModelNotFoundException.php0(Y0YMvendor/league/factory-muffin/src/Exceptions/DeleteMethodNotFoundException.phpm(Ym+>vendor/league/factory-muffin/src/Exceptions/ModelException.phpU(YUu~2zCvendor/league/factory-muffin/src/Exceptions/SaveFailedException.php(Y'0Kvendor/league/factory-muffin/src/Exceptions/SaveMethodNotFoundException.phpi(Yi0GEvendor/league/factory-muffin/src/Exceptions/DeleteFailedException.php0(Y0'Jvendor/league/factory-muffin/src/Exceptions/DirectoryNotFoundException.php(Y҂(Lvendor/league/factory-muffin/src/Exceptions/FlushMethodNotFoundException.phpk(Ykvd Cvendor/league/factory-muffin/src/Exceptions/DefinitionException.phph(Yh_Gvendor/league/factory-muffin/src/Exceptions/DeletingFailedException.php(Y Kvendor/league/factory-muffin/src/Exceptions/DefinitionNotFoundException.php;(Y;†*Qvendor/league/factory-muffin/src/Exceptions/DefinitionAlreadyDefinedException.phpM(YM!PGvendor/league/factory-muffin/src/Exceptions/MethodNotFoundException.phpf(Yf=J?vendor/league/factory-muffin/src/Generators/PrefixInterface.php(Y.]@vendor/league/factory-muffin/src/Generators/FactoryGenerator.php(YiF9@vendor/league/factory-muffin/src/Generators/GeneratorFactory.php(Y:?vendor/league/factory-muffin/src/Generators/EntityGenerator.php2(Y2[Bvendor/league/factory-muffin/src/Generators/GeneratorInterface.php(Y?Avendor/league/factory-muffin/src/Generators/CallableGenerator.php(Y#ė:vendor/league/factory-muffin/src/Stores/StoreInterface.php(Y9vendor/league/factory-muffin/src/Stores/AbstractStore.php(YN o;vendor/league/factory-muffin/src/Stores/RepositoryStore.php(Y3α@6vendor/league/factory-muffin/src/Stores/ModelStore.php(Yb0vendor/league/factory-muffin-faker/src/Faker.php(Ylz1vendor/league/factory-muffin-faker/src/Facade.php(YN(,vendor/guzzlehttp/psr7/src/ServerRequest.php(Y[ -vendor/guzzlehttp/psr7/src/DroppingStream.php/(Y/FE;3vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php8(Y8b+vendor/guzzlehttp/psr7/src/UploadedFile.php(Y>#*vendor/guzzlehttp/psr7/src/UriResolver.php(Y2Ŷ*vendor/guzzlehttp/psr7/src/LimitStream.php(Y0'vendor/guzzlehttp/psr7/src/Response.php (Y g|0vendor/guzzlehttp/psr7/src/functions_include.php](Y]ɶ"vendor/guzzlehttp/psr7/src/Uri.php$(Y$1%vendor/guzzlehttp/psr7/src/Stream.php(Y!5&.vendor/guzzlehttp/psr7/src/MultipartStream.phpH (YH ,vendor/guzzlehttp/psr7/src/StreamWrapper.php(Yz,vendor/guzzlehttp/psr7/src/InflateStream.phpb(YbL(')vendor/guzzlehttp/psr7/src/PumpStream.php_(Y_!IU+vendor/guzzlehttp/psr7/src/MessageTrait.phpk (Yk i~&vendor/guzzlehttp/psr7/src/Request.php.(Y.-vendor/guzzlehttp/psr7/src/LazyOpenStream.php(YbE,vendor/guzzlehttp/psr7/src/CachingStream.phpx(Yxà,vendor/guzzlehttp/psr7/src/UriNormalizer.php (Y 2(vendor/guzzlehttp/psr7/src/functions.php'5(Y'5Aw+vendor/guzzlehttp/psr7/src/NoSeekStream.php:(Y:R!+vendor/guzzlehttp/psr7/src/AppendStream.php (Y ƶ+vendor/guzzlehttp/psr7/src/BufferStream.php)(Y)1'vendor/guzzlehttp/psr7/src/FnStream.phpn(Yno2vendor/guzzlehttp/promises/src/RejectedPromise.php(Y],vendor/guzzlehttp/promises/src/Coroutine.php(Yr3&4vendor/guzzlehttp/promises/src/functions_include.phph(Yhʏ8vendor/guzzlehttp/promises/src/CancellationException.phpc(Yc!0*vendor/guzzlehttp/promises/src/Promise.php(YڻS,vendor/guzzlehttp/promises/src/TaskQueue.php(YC]3vendor/guzzlehttp/promises/src/PromiseInterface.php(Ysö5vendor/guzzlehttp/promises/src/RejectionException.php(Y3.vendor/guzzlehttp/promises/src/EachPromise.php (Y -4vendor/guzzlehttp/promises/src/PromisorInterface.phph(Yhi5vendor/guzzlehttp/promises/src/TaskQueueInterface.php(Yj{,vendor/guzzlehttp/promises/src/functions.php(Y+ 3vendor/guzzlehttp/promises/src/FulfilledPromise.php(YD5vendor/guzzlehttp/promises/src/AggregateException.php(YO]0vendor/guzzlehttp/guzzle/src/ClientInterface.php(Y:Oֶ%vendor/guzzlehttp/guzzle/src/Pool.phpw(Yw3vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php}(Y}ǿ2vendor/guzzlehttp/guzzle/src/functions_include.phpa(Yaj6vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.phpN(YNQ'vendor/guzzlehttp/guzzle/src/Client.phpP(YPH4vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php (Y Ԙܶ.vendor/guzzlehttp/guzzle/src/Handler/Proxy.php(Y˶4vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php,(Y,)^3vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php9(Y9:\6vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php+((Y+(G4vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.phpv(Yv9f9vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php (Y =vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php(Yu+vendor/guzzlehttp/guzzle/src/Middleware.php(Ycö0vendor/guzzlehttp/guzzle/src/RetryMiddleware.php(Y)_-vendor/guzzlehttp/guzzle/src/HandlerStack.php (Y a;vendor/guzzlehttp/guzzle/src/Exception/RequestException.php (Y [k<vendor/guzzlehttp/guzzle/src/Exception/TransferException.phpw(Yw Q:vendor/guzzlehttp/guzzle/src/Exception/ClientException.php`(Y`p:vendor/guzzlehttp/guzzle/src/Exception/ServerException.php`(Y` 8vendor/guzzlehttp/guzzle/src/Exception/SeekException.php(YZM%];vendor/guzzlehttp/guzzle/src/Exception/ConnectException.php(YհDvendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.phpc(Yc߶:vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.phpD(YDD&?vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.phpa(Ya]I*vendor/guzzlehttp/guzzle/src/functions.php(YR/vendor/guzzlehttp/guzzle/src/RequestOptions.php(Ygy1vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.phpA(YA ɶ1vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php(Yo$5vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php(Yζ8vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php(Yms:vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.phpH(YH?I.vendor/guzzlehttp/guzzle/src/TransferStats.php(Y/c1vendor/guzzlehttp/guzzle/src/MessageFormatter.php (Y Zy,vendor/guzzlehttp/guzzle/src/UriTemplate.phpa(Ya.!vendor/predis/predis/autoload.phpW(YWf4C3vendor/predis/predis/src/CommunicationException.php(Y2vendor/predis/predis/src/PubSub/DispatcherLoop.php;(Y;%,vendor/predis/predis/src/PubSub/Consumer.php (Y XW?"4vendor/predis/predis/src/PubSub/AbstractConsumer.php(Y;_8vendor/predis/predis/src/Collection/Iterator/ListKey.php(Y#ذ=vendor/predis/predis/src/Collection/Iterator/SortedSetKey.php(Yv>9vendor/predis/predis/src/Collection/Iterator/Keyspace.php(YRs7vendor/predis/predis/src/Collection/Iterator/SetKey.php(YtDvendor/predis/predis/src/Collection/Iterator/CursorBasedIterator.php(YN@8vendor/predis/predis/src/Collection/Iterator/HashKey.php(Y4 ,vendor/predis/predis/src/ClientInterface.php(YJ6vendor/predis/predis/src/Cluster/StrategyInterface.php?(Y?"2vendor/predis/predis/src/Cluster/RedisStrategy.php(Y mEvendor/predis/predis/src/Cluster/Distributor/DistributorInterface.php(YjCvendor/predis/predis/src/Cluster/Distributor/EmptyRingException.phpl(Yl9vendor/predis/predis/src/Cluster/Distributor/HashRing.php (Y s,;vendor/predis/predis/src/Cluster/Distributor/KetamaRing.phpv(Yvx/vendor/predis/predis/src/Cluster/Hash/CRC16.phpr (Yr dѶ@vendor/predis/predis/src/Cluster/Hash/HashGeneratorInterface.php(Y!)3vendor/predis/predis/src/Cluster/PredisStrategy.php(Ys4vendor/predis/predis/src/Cluster/ClusterStrategy.php#(Y# @3vendor/predis/predis/src/ClientContextInterface.php(YzӶ,vendor/predis/predis/src/ClientException.phpZ(YZl<4vendor/predis/predis/src/Profile/RedisVersion280.php(Y_۶,vendor/predis/predis/src/Profile/Factory.php(Y&r4vendor/predis/predis/src/Profile/RedisVersion220.php(Y}$U4vendor/predis/predis/src/Profile/RedisVersion200.php*(Y*$z4vendor/predis/predis/src/Profile/RedisVersion320.phpF(YFG.C4vendor/predis/predis/src/Profile/RedisVersion300.php(Y֦1vendor/predis/predis/src/Profile/RedisProfile.php(Y;4vendor/predis/predis/src/Profile/RedisVersion240.php((Y(M`D2vendor/predis/predis/src/Profile/RedisUnstable.php(Y͋4vendor/predis/predis/src/Profile/RedisVersion260.php(Y)\R5vendor/predis/predis/src/Profile/ProfileInterface.php\(Y\2z#vendor/predis/predis/src/Client.php(Y-U-vendor/predis/predis/src/Monitor/Consumer.php(Y3c'vendor/predis/predis/src/Autoloader.php(Y윝7vendor/predis/predis/src/Transaction/MultiExecState.php(YC2vendor/predis/predis/src/Transaction/MultiExec.php](Y]Bvendor/predis/predis/src/Transaction/AbortedMultiExecException.php(Y_X6vendor/predis/predis/src/Replication/RoleException.php(Y״K?vendor/predis/predis/src/Replication/MissingMasterException.php(Y<vendor/predis/predis/src/Replication/ReplicationStrategy.php(Yj4vendor/predis/predis/src/Response/ErrorInterface.php(Y`w,vendor/predis/predis/src/Response/Status.phpR(YRp|5vendor/predis/predis/src/Response/ServerException.phpb(Yb#7vendor/predis/predis/src/Response/ResponseInterface.phpQ(YQyZ=vendor/predis/predis/src/Response/Iterator/MultiBulkTuple.php<(Y<r8vendor/predis/predis/src/Response/Iterator/MultiBulk.php(Y#@vendor/predis/predis/src/Response/Iterator/MultiBulkIterator.php(Y&+vendor/predis/predis/src/Response/Error.php(Y&˶,vendor/predis/predis/src/PredisException.php^(Y^7vendor/predis/predis/src/Protocol/ProtocolException.php(Y5@vendor/predis/predis/src/Protocol/RequestSerializerInterface.php(YnEvendor/predis/predis/src/Protocol/Text/CompositeProtocolProcessor.php(Y,$Dvendor/predis/predis/src/Protocol/Text/Handler/MultiBulkResponse.php (Y :dBvendor/predis/predis/src/Protocol/Text/Handler/IntegerResponse.php`(Y`L@vendor/predis/predis/src/Protocol/Text/Handler/ErrorResponse.php;(Y;LI?vendor/predis/predis/src/Protocol/Text/Handler/BulkResponse.php(YW0Avendor/predis/predis/src/Protocol/Text/Handler/StatusResponse.php@(Y@%s|Nvendor/predis/predis/src/Protocol/Text/Handler/StreamableMultiBulkResponse.php(YA-(Kvendor/predis/predis/src/Protocol/Text/Handler/ResponseHandlerInterface.php(Y wQ<vendor/predis/predis/src/Protocol/Text/RequestSerializer.phpQ(YQo<vendor/predis/predis/src/Protocol/Text/ProtocolProcessor.php (Y {9vendor/predis/predis/src/Protocol/Text/ResponseReader.php(Y0p@vendor/predis/predis/src/Protocol/ProtocolProcessorInterface.php_(Y_i"=vendor/predis/predis/src/Protocol/ResponseReaderInterface.php(Y72vendor/predis/predis/src/NotSupportedException.phpa(Yan),vendor/predis/predis/src/Session/Handler.phpC(YC!,vendor/predis/predis/src/Command/SetScan.php(YE5vendor/predis/predis/src/Command/KeyPreciseExpire.php(Y|9vendor/predis/predis/src/Command/ZSetRemoveRangeByLex.php(Yfb+vendor/predis/predis/src/Command/SetPop.php(Y)o¶3vendor/predis/predis/src/Command/StringGetRange.php(Y.vendor/predis/predis/src/Command/StringSet.php(Yp2vendor/predis/predis/src/Command/ServerCommand.php(YkӶ,vendor/predis/predis/src/Command/KeySort.phpY(YY=3vendor/predis/predis/src/Command/ServerFlushAll.php(Yː6vendor/predis/predis/src/Command/StringSetPreserve.php(YF1vendor/predis/predis/src/Command/ServerClient.php(Y5vendor/predis/predis/src/Command/CommandInterface.php(Y'i.vendor/predis/predis/src/Command/KeyDelete.php(Y}aж-vendor/predis/predis/src/Command/SetUnion.php(YX6vendor/predis/predis/src/Command/GeospatialGeoHash.phpl(Ylqa/vendor/predis/predis/src/Command/HashLength.php(Yy<vendor/predis/predis/src/Command/ZSetReverseRangeByScore.php(Y-Ŷ9vendor/predis/predis/src/Command/KeyPreciseTimeToLive.php(Y0&,vendor/predis/predis/src/Command/ZSetAdd.php\(Y\R/vendor/predis/predis/src/Command/KeyPersist.php(Yɽ2vendor/predis/predis/src/Command/ServerSlowlog.php(Yt2vendor/predis/predis/src/Command/ListPushHeadX.php(Y_f|.vendor/predis/predis/src/Command/KeyExpire.php(Y4 4vendor/predis/predis/src/Command/TransactionExec.php(YZ-25.vendor/predis/predis/src/Command/StringGet.php(Y/vendor/predis/predis/src/Command/ServerEval.php(Ye95vendor/predis/predis/src/Command/HashStringLength.php(Y:|r,vendor/predis/predis/src/Command/SetMove.php(YRAS1vendor/predis/predis/src/Command/ServerConfig.phpP(YPr/vendor/predis/predis/src/Command/HashGetAll.php$(Y$Q7vendor/predis/predis/src/Command/KeyPreciseExpireAt.php(Y!1vendor/predis/predis/src/Command/ListPopFirst.php(YL,vendor/predis/predis/src/Command/KeyDump.php(Yj߶3vendor/predis/predis/src/Command/StringBitField.php(YٙӶ8vendor/predis/predis/src/Command/ListPopLastBlocking.php(Y{,1vendor/predis/predis/src/Command/StringBitPos.php(Y3vendor/predis/predis/src/Command/ConnectionQuit.php(YI2vendor/predis/predis/src/Command/SetUnionStore.php(YS-3vendor/predis/predis/src/Command/ZSetRangeByLex.php(Y". 0vendor/predis/predis/src/Command/StringBitOp.php(Y 3vendor/predis/predis/src/Command/ConnectionEcho.php(YBg9vendor/predis/predis/src/Command/SetIntersectionStore.phpZ(YZBW/vendor/predis/predis/src/Command/ListInsert.php(YWtK1vendor/predis/predis/src/Command/ListPushTail.php(Y5vendor/predis/predis/src/Command/ZSetReverseRange.php(Yzh54vendor/predis/predis/src/Command/ZSetCardinality.php(YURٶ8vendor/predis/predis/src/Command/GeospatialGeoRadius.php(Y͐6vendor/predis/predis/src/Command/KeyRenamePreserve.php(Y.LT6vendor/predis/predis/src/Command/StringDecrementBy.php(Y;1vendor/predis/predis/src/Command/StringStrlen.php(Y?/vendor/predis/predis/src/Command/HashDelete.php(Y 4vendor/predis/predis/src/Command/ZSetIncrementBy.php(YK/,vendor/predis/predis/src/Command/KeyMove.php(YX9vendor/predis/predis/src/Command/ServerBackgroundSave.php(YB6vendor/predis/predis/src/Command/StringSetMultiple.php(Yd/vendor/predis/predis/src/Command/ZSetRemove.php(Yz}6vendor/predis/predis/src/Command/StringIncrementBy.php(YuS-vendor/predis/predis/src/Command/ListTrim.php(Y Q5vendor/predis/predis/src/Command/HyperLogLogMerge.php(Yhs9vendor/predis/predis/src/Command/ListPopFirstBlocking.phpf(Yfɝն@vendor/predis/predis/src/Command/GeospatialGeoRadiusByMember.php(Y 1vendor/predis/predis/src/Command/ListPushHead.php(Y94vendor/predis/predis/src/Command/StringIncrement.php(Y);wĶ3vendor/predis/predis/src/Command/SetCardinality.php(Yͯ!2vendor/predis/predis/src/Command/KeyTimeToLive.php(Y<:3vendor/predis/predis/src/Command/ServerInfoV26x.phpc(Ycl1vendor/predis/predis/src/Command/StringAppend.php(Yپ;vendor/predis/predis/src/Command/ZSetRemoveRangeByScore.php(YF5vendor/predis/predis/src/Command/GeospatialGeoPos.phpj(YjYö/vendor/predis/predis/src/Command/RawCommand.php(Y6eA.vendor/predis/predis/src/Command/ZSetRange.php(Y>3/vendor/predis/predis/src/Command/ListLength.php(Yc9/vendor/predis/predis/src/Command/KeyMigrate.php(Y[V/vendor/predis/predis/src/Command/HashExists.php(Y3vendor/predis/predis/src/Command/ServerSentinel.phpf(Yf)b.vendor/predis/predis/src/Command/KeyRename.php(YE߇ζ1vendor/predis/predis/src/Command/StringSubstr.php(Y!6vendor/predis/predis/src/Command/GeospatialGeoDist.php(YNv@/vendor/predis/predis/src/Command/ServerTime.php(Y쒶3vendor/predis/predis/src/Command/ZSetUnionStore.php(Yc/7vendor/predis/predis/src/Command/SetDifferenceStore.php(Y 5vendor/predis/predis/src/Command/HyperLogLogCount.php(Y.=^4vendor/predis/predis/src/Command/StringDecrement.php(YKԶ6vendor/predis/predis/src/Command/PubSubUnsubscribe.php(Y|4vendor/predis/predis/src/Command/HashSetPreserve.php(Yq::3vendor/predis/predis/src/Command/ConnectionAuth.php(YOn4vendor/predis/predis/src/Command/SetRandomMember.php(Y`/8vendor/predis/predis/src/Command/ServerFlushDatabase.php(YG:vendor/predis/predis/src/Command/ZSetReverseRangeByLex.php(YcQ7vendor/predis/predis/src/Command/TransactionUnwatch.php(Y2vendor/predis/predis/src/Command/ServerMonitor.php(Y&#$/vendor/predis/predis/src/Command/HashValues.php(YU F[3vendor/predis/predis/src/Command/ServerLastSave.php(YeWW3vendor/predis/predis/src/Command/ConnectionPing.php(Y<-vendor/predis/predis/src/Command/ZSetRank.php(Yh1vendor/predis/predis/src/Command/ServerScript.php(YAV^8vendor/predis/predis/src/Command/ListPopLastPushHead.php(YŲ3vendor/predis/predis/src/Command/StringSetRange.php(Ye^4vendor/predis/predis/src/Command/SetIntersection.php(Y1vendor/predis/predis/src/Command/StringGetBit.php(YB.vendor/predis/predis/src/Command/SetRemove.php(YWq-vendor/predis/predis/src/Command/ZSetScan.php(Y7j?vendor/predis/predis/src/Command/ServerBackgroundRewriteAOF.php (Y j_Avendor/predis/predis/src/Command/Processor/ProcessorInterface.php(YƐc=vendor/predis/predis/src/Command/Processor/ProcessorChain.php(YcfAvendor/predis/predis/src/Command/Processor/KeyPrefixProcessor.php(YFdo,vendor/predis/predis/src/Command/ListSet.php(Y}9vendor/predis/predis/src/Command/HashIncrementByFloat.php(YN`+2vendor/predis/predis/src/Command/ListPushTailX.php(Ygjܟ1vendor/predis/predis/src/Command/StringGetSet.php(Ym1vendor/predis/predis/src/Command/StringSetBit.php(Y:їB3vendor/predis/predis/src/Command/StringBitCount.php(Yl 1vendor/predis/predis/src/Command/PubSubPubsub.php (Y =vζ0vendor/predis/predis/src/Command/SetIsMember.php(Y5,.vendor/predis/predis/src/Command/KeyRandom.php(Y}3)5vendor/predis/predis/src/Command/GeospatialGeoAdd.phpr(Yr퓭4vendor/predis/predis/src/Command/HashSetMultiple.php(YU-vendor/predis/predis/src/Command/HashScan.php(Y74:vendor/predis/predis/src/Command/ZSetRemoveRangeByRank.php(Y! T,vendor/predis/predis/src/Command/KeyScan.php(Yp4vendor/predis/predis/src/Command/StringSetExpire.php(YL,.vendor/predis/predis/src/Command/KeyExists.php(Yh1vendor/predis/predis/src/Command/ServerObject.php(Y9V/vendor/predis/predis/src/Command/SetMembers.php(Y@4vendor/predis/predis/src/Command/ZSetReverseRank.php(Y,/vendor/predis/predis/src/Command/KeyRestore.php(YfI?޶+vendor/predis/predis/src/Command/SetAdd.php(YÄ.vendor/predis/predis/src/Command/ListIndex.php(Y۬ö,vendor/predis/predis/src/Command/HashGet.php(Y@ض?vendor/predis/predis/src/Command/PubSubUnsubscribeByPattern.php(Ya @@vendor/predis/predis/src/Command/ListPopLastPushHeadBlocking.php(Y=2vendor/predis/predis/src/Command/ServerSlaveOf.php4(Y4JgӶ2vendor/predis/predis/src/Command/ServerEvalSHA.php(YJn/vendor/predis/predis/src/Command/ServerInfo.php(Y4}޶3vendor/predis/predis/src/Command/ServerShutdown.php(Y4vendor/predis/predis/src/Command/HashGetMultiple.php(YП0vendor/predis/predis/src/Command/KeyExpireAt.php(Y^,vendor/predis/predis/src/Command/Command.php(Y4vendor/predis/predis/src/Command/PubSubSubscribe.php(YQv6vendor/predis/predis/src/Command/StringGetMultiple.php(Y)Ͷ2vendor/predis/predis/src/Command/ScriptCommand.php(Y϶4vendor/predis/predis/src/Command/HashIncrementBy.php(YGHo7vendor/predis/predis/src/Command/TransactionDiscard.php(Yt5vendor/predis/predis/src/Command/TransactionWatch.php*(Y*;vendor/predis/predis/src/Command/StringIncrementByFloat.php(Yq놶,vendor/predis/predis/src/Command/HashSet.php(YHK޶5vendor/predis/predis/src/Command/TransactionMulti.php(Y U7vendor/predis/predis/src/Command/ServerDatabaseSize.php(Y85vendor/predis/predis/src/Command/ZSetRangeByScore.phpy(Yy`®=vendor/predis/predis/src/Command/PubSubSubscribeByPattern.php(Yt|;vendor/predis/predis/src/Command/StringPreciseSetExpire.php(Yhv,vendor/predis/predis/src/Command/KeyKeys.php(Y/vendor/predis/predis/src/Command/ListRemove.php(Y'3vendor/predis/predis/src/Command/HyperLogLogAdd.php(Y1vendor/predis/predis/src/Command/ZSetLexCount.php(YJ-ٶ2vendor/predis/predis/src/Command/PubSubPublish.php(Y,vendor/predis/predis/src/Command/KeyType.php(Y:vendor/predis/predis/src/Command/ZSetIntersectionStore.php(Y,_/vendor/predis/predis/src/Command/ServerSave.php(Y{̶?vendor/predis/predis/src/Command/PrefixableCommandInterface.php(Yū2vendor/predis/predis/src/Command/SetDifference.php(Yp5vendor/predis/predis/src/Command/ConnectionSelect.php(Y=3d>vendor/predis/predis/src/Command/StringSetMultiplePreserve.php(Y ɶ.vendor/predis/predis/src/Command/ZSetCount.php(Y/+.vendor/predis/predis/src/Command/ZSetScore.php(YŶ0vendor/predis/predis/src/Command/ListPopLast.php(Y-vendor/predis/predis/src/Command/HashKeys.php(Yd<.vendor/predis/predis/src/Command/ListRange.php(Y϶7vendor/predis/predis/src/Configuration/PrefixOption.php(YF=;vendor/predis/predis/src/Configuration/ExceptionsOption.php-(Y-g;vendor/predis/predis/src/Configuration/OptionsInterface.php(Yp8vendor/predis/predis/src/Configuration/ClusterOption.php(Yf::vendor/predis/predis/src/Configuration/OptionInterface.php(Yv 8vendor/predis/predis/src/Configuration/ProfileOption.php}(Y}"Bvendor/predis/predis/src/Configuration/ConnectionFactoryOption.php(Ym"ƶ<vendor/predis/predis/src/Configuration/ReplicationOption.php(Yʛ2vendor/predis/predis/src/Configuration/Options.phpl(Yl BM3vendor/predis/predis/src/Pipeline/FireAndForget.phpf(Yfj6:vendor/predis/predis/src/Pipeline/ConnectionErrorProof.php (Y 7.vendor/predis/predis/src/Pipeline/Pipeline.php (Y |#v,vendor/predis/predis/src/Pipeline/Atomic.php> (Y> vjw8vendor/predis/predis/src/Connection/WebdisConnection.phpZ(YZR/vendor/predis/predis/src/Connection/Factory.phpD (YD vDvendor/predis/predis/src/Connection/CompositeConnectionInterface.php(Y]>vendor/predis/predis/src/Connection/Aggregate/RedisCluster.php#(Y#qBvendor/predis/predis/src/Connection/Aggregate/ClusterInterface.php(YNFvendor/predis/predis/src/Connection/Aggregate/ReplicationInterface.php(Y!Evendor/predis/predis/src/Connection/Aggregate/SentinelReplication.php2'(Y2'Hvendor/predis/predis/src/Connection/Aggregate/MasterSlaveReplication.phpW(YW.%?vendor/predis/predis/src/Connection/Aggregate/PredisCluster.php (Y T78vendor/predis/predis/src/Connection/StreamConnection.php(YT /8vendor/predis/predis/src/Connection/FactoryInterface.php;(Y;6.5;vendor/predis/predis/src/Connection/ParametersInterface.php(Y Yֶ?vendor/predis/predis/src/Connection/NodeConnectionInterface.phpm(YmiAvendor/predis/predis/src/Connection/PhpiredisStreamConnection.php.(Y.< $Avendor/predis/predis/src/Connection/PhpiredisSocketConnection.php\(Y\:ݶDvendor/predis/predis/src/Connection/AggregateConnectionInterface.php(YlAu;vendor/predis/predis/src/Connection/ConnectionInterface.php(Y^ M2vendor/predis/predis/src/Connection/Parameters.php(Y6a;vendor/predis/predis/src/Connection/ConnectionException.php(Y-Avendor/predis/predis/src/Connection/CompositeStreamConnection.php(Ya:vendor/predis/predis/src/Connection/AbstractConnection.phpQ (YQ <vendor/predis/predis/examples/custom_cluster_distributor.php(Ym)Ӷ(vendor/predis/predis/examples/shared.phpA(YAyE۶7vendor/predis/predis/examples/debuggable_connection.php(Yɶ/vendor/predis/predis/examples/key_prefixing.php (Y F0=vendor/predis/predis/examples/redis_collections_iterators.phpR(YR3oy"7vendor/predis/predis/examples/transaction_using_cas.php (Y /T05vendor/predis/predis/examples/replication_complex.php(YSQ<4vendor/predis/predis/examples/replication_simple.php-(Y-ǿ1vendor/predis/predis/examples/session_handler.php(Y I1vendor/predis/predis/examples/dispatcher_loop.php(Y!11vendor/predis/predis/examples/pubsub_consumer.phpa(Yaϕ6vendor/predis/predis/examples/replication_sentinel.phpL(YL 5vendor/predis/predis/examples/pipelining_commands.phpj(YjҶ:vendor/predis/predis/examples/executing_redis_commands.php((Y(NY2vendor/predis/predis/examples/monitor_consumer.php(YI0;vendor/predis/predis/examples/lua_scripting_abstraction.phpW(YWmcPg&vendor/webmozart/assert/src/Assert.phpE(YEd%F0vendor/psr/http-message/src/RequestInterface.php(YV/vendor/psr/http-message/src/StreamInterface.php(Yh\l1vendor/psr/http-message/src/ResponseInterface.php(Y0vendor/psr/http-message/src/MessageInterface.php(Y)t 5vendor/psr/http-message/src/UploadedFileInterface.phpz(Yz9b,vendor/psr/http-message/src/UriInterface.php|(Y|k6vendor/psr/http-message/src/ServerRequestInterface.phpo(Yo $h)vendor/psr/log/Psr/Log/AbstractLogger.php;(Y;>3[#vendor/psr/log/Psr/Log/LogLevel.php(Yj8&vendor/psr/log/Psr/Log/LoggerTrait.phpi(Yi35޶*vendor/psr/log/Psr/Log/LoggerInterface.php(Ysg3vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php (Y iq0ض%vendor/psr/log/Psr/Log/NullLogger.php(Y+vendor/psr/log/Psr/Log/LoggerAwareTrait.php(YTB3vendor/psr/log/Psr/Log/InvalidArgumentException.php`(Y` X1/vendor/psr/log/Psr/Log/LoggerAwareInterface.php|(Y|$Cvendor/stecman/symfony-console-completion/src/CompletionCommand.phpa (Ya 9RCvendor/stecman/symfony-console-completion/src/CompletionHandler.php(YVöUvendor/stecman/symfony-console-completion/src/Completion/CompletionAwareInterface.phpq(Yq빶Pvendor/stecman/symfony-console-completion/src/Completion/ShellPathCompletion.php(Y Pvendor/stecman/symfony-console-completion/src/Completion/CompletionInterface.php|(Y|cktNvendor/stecman/symfony-console-completion/src/EnvironmentCompletionContext.php(YցCvendor/stecman/symfony-console-completion/src/CompletionContext.php(YQx=vendor/stecman/symfony-console-completion/src/HookFactory.php (Y sߖ2<vendor/stecman/symfony-console-completion/src/Completion.php(Yˏvendor/behat/gherkin/i18n.phpn(Yne vendor/behat/gherkin/libpath.php(YtrnǶ0vendor/behat/gherkin/src/Behat/Gherkin/Lexer.php"(Y"T^ڶ8vendor/behat/gherkin/src/Behat/Gherkin/Node/StepNode.php(YIi;vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleNode.php| (Y| ECvendor/behat/gherkin/src/Behat/Gherkin/Node/TaggedNodeInterface.php(Y%,9vendor/behat/gherkin/src/Behat/Gherkin/Node/TableNode.php2 (Y2 ~V!Dvendor/behat/gherkin/src/Behat/Gherkin/Node/KeywordNodeInterface.php(Yn>vendor/behat/gherkin/src/Behat/Gherkin/Node/BackgroundNode.php(Y1Fvendor/behat/gherkin/src/Behat/Gherkin/Node/StepContainerInterface.php(Y' ~<vendor/behat/gherkin/src/Behat/Gherkin/Node/PyStringNode.php?(Y?O<vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioNode.php(Y BAvendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioInterface.php(YA9p@vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleTableNode.phpj(Yj*kIEvendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioLikeInterface.php(Yx;vendor/behat/gherkin/src/Behat/Gherkin/Node/OutlineNode.phpe(Ye`q;vendor/behat/gherkin/src/Behat/Gherkin/Node/FeatureNode.php(YJC=vendor/behat/gherkin/src/Behat/Gherkin/Node/NodeInterface.php(Yq!zAvendor/behat/gherkin/src/Behat/Gherkin/Node/ArgumentInterface.phpi(Yi\<vendor/behat/gherkin/src/Behat/Gherkin/Cache/MemoryCache.php.(Y.&cm:vendor/behat/gherkin/src/Behat/Gherkin/Cache/FileCache.phpG(YG¶?vendor/behat/gherkin/src/Behat/Gherkin/Cache/CacheInterface.php (Y EGvendor/behat/gherkin/src/Behat/Gherkin/Keywords/CachedArrayKeywords.php(YKPAvendor/behat/gherkin/src/Behat/Gherkin/Keywords/ArrayKeywords.php(YeDvendor/behat/gherkin/src/Behat/Gherkin/Keywords/CucumberKeywords.php(YFBvendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsDumper.php(Y*öEvendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsInterface.phpZ(YZ[D1vendor/behat/gherkin/src/Behat/Gherkin/Parser.php.(Y.? ۶Bvendor/behat/gherkin/src/Behat/Gherkin/Exception/NodeException.php(YtCvendor/behat/gherkin/src/Behat/Gherkin/Exception/LexerException.php(YUDvendor/behat/gherkin/src/Behat/Gherkin/Exception/ParserException.php(YdKCvendor/behat/gherkin/src/Behat/Gherkin/Exception/CacheException.php(YJ񼩶>vendor/behat/gherkin/src/Behat/Gherkin/Exception/Exception.phpK(YKHvendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilterInterface.php$(Y$Q=϶Avendor/behat/gherkin/src/Behat/Gherkin/Filter/NarrativeFilter.php(Y[߶<vendor/behat/gherkin/src/Behat/Gherkin/Filter/LineFilter.phpM(YMБʞAvendor/behat/gherkin/src/Behat/Gherkin/Filter/LineRangeFilter.php(Yrd˶?vendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilter.phpe(Ye <vendor/behat/gherkin/src/Behat/Gherkin/Filter/RoleFilter.phpS(YSF>vendor/behat/gherkin/src/Behat/Gherkin/Filter/SimpleFilter.php(Y<;vendor/behat/gherkin/src/Behat/Gherkin/Filter/TagFilter.php(Y@9Avendor/behat/gherkin/src/Behat/Gherkin/Filter/FilterInterface.php(YsH<vendor/behat/gherkin/src/Behat/Gherkin/Filter/NameFilter.phpY(YYî?=vendor/behat/gherkin/src/Behat/Gherkin/Filter/PathsFilter.php(YT -Hvendor/behat/gherkin/src/Behat/Gherkin/Filter/FeatureFilterInterface.php(Y@vendor/behat/gherkin/src/Behat/Gherkin/Loader/YamlFileLoader.php(Y̯Cvendor/behat/gherkin/src/Behat/Gherkin/Loader/GherkinFileLoader.php(YҶDvendor/behat/gherkin/src/Behat/Gherkin/Loader/AbstractFileLoader.php(YR=vendor/behat/gherkin/src/Behat/Gherkin/Loader/ArrayLoader.php(Y5Avendor/behat/gherkin/src/Behat/Gherkin/Loader/DirectoryLoader.php(YܯGEvendor/behat/gherkin/src/Behat/Gherkin/Loader/FileLoaderInterface.php(Y=Avendor/behat/gherkin/src/Behat/Gherkin/Loader/LoaderInterface.php(YEZb2vendor/behat/gherkin/src/Behat/Gherkin/Gherkin.php(Y$t/vendor/symfony/css-selector/XPath/XPathExpr.php{(Y{0vendor/symfony/css-selector/XPath/Translator.php{(Y{9vendor/symfony/css-selector/XPath/TranslatorInterface.phpV(YV[Avendor/symfony/css-selector/XPath/Extension/AbstractExtension.php(YlDvendor/symfony/css-selector/XPath/Extension/PseudoClassExtension.php(Y =vendor/symfony/css-selector/XPath/Extension/NodeExtension.php(Y5 Jvendor/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php (Y EHAvendor/symfony/css-selector/XPath/Extension/FunctionExtension.php1 (Y1 ODvendor/symfony/css-selector/XPath/Extension/CombinationExtension.phpy(Yy f=vendor/symfony/css-selector/XPath/Extension/HtmlExtension.php(YliBvendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php(Y#.ɶ.vendor/symfony/css-selector/Node/ClassNode.phpo(Yo601vendor/symfony/css-selector/Node/SelectorNode.php(Y,'0vendor/symfony/css-selector/Node/Specificity.php(Y#S1vendor/symfony/css-selector/Node/FunctionNode.php(Y/vendor/symfony/css-selector/Node/PseudoNode.php(Ylɦ 0vendor/symfony/css-selector/Node/ElementNode.php(YqJ1vendor/symfony/css-selector/Node/NegationNode.php(YŹⴶ1vendor/symfony/css-selector/Node/AbstractNode.phpN(YN=-vendor/symfony/css-selector/Node/HashNode.php`(Y`L;N9vendor/symfony/css-selector/Node/CombinedSelectorNode.php(Y'2vendor/symfony/css-selector/Node/NodeInterface.php(Y.y2vendor/symfony/css-selector/Node/AttributeNode.php(Y9W:vendor/symfony/css-selector/Parser/Handler/HashHandler.php(YZ_R@vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.phpN(YNsE?vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php(Y ض<vendor/symfony/css-selector/Parser/Handler/NumberHandler.php (Y ؾP@vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php(Y"Hj=vendor/symfony/css-selector/Parser/Handler/CommentHandler.php(Y!<vendor/symfony/css-selector/Parser/Handler/StringHandler.phpi(YiD6vendor/symfony/css-selector/Parser/ParserInterface.php(Yy9,vendor/symfony/css-selector/Parser/Token.php(YWs=vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php(Y];vendor/symfony/css-selector/Parser/Shortcut/ClassParser.phpz(Yz*"2H:vendor/symfony/css-selector/Parser/Shortcut/HashParser.phpv(YvAvendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php(Y~2vendor/symfony/css-selector/Parser/TokenStream.php_(Y_oٶ-vendor/symfony/css-selector/Parser/Parser.php(Y L3-vendor/symfony/css-selector/Parser/Reader.phpO(YOΑBvendor/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php(YBvendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php(YZL:vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php(Y`8<vendor/symfony/css-selector/Exception/ExceptionInterface.phpm(YmY~Bvendor/symfony/css-selector/Exception/ExpressionErrorException.php(Y6;8vendor/symfony/css-selector/Exception/ParseException.php(Y/X>vendor/symfony/css-selector/Exception/SyntaxErrorException.phpo(Yol%@vendor/symfony/css-selector/Exception/InternalErrorException.php(Y&O4vendor/symfony/css-selector/CssSelectorConverter.php(YG@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php2A(Y2A=@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpA(YA Ͷ.vendor/symfony/polyfill-mbstring/bootstrap.php(Y|Ҷ-vendor/symfony/polyfill-mbstring/Mbstring.php4(Y4^iS)vendor/symfony/debug/ExceptionHandler.php)(Y)G.Yvendor/symfony/debug/Debug.php(Y:z (vendor/symfony/debug/BufferingLogger.phpt(Yt=hܶ)vendor/symfony/debug/DebugClassLoader.php(Y\@Ivendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.phpE(YEUKvendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.phpN(YNo-Evendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php(YĹBVMvendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.phpu(Yuj3vendor/symfony/debug/Exception/FlattenException.phpF(YFڭs=vendor/symfony/debug/Exception/UndefinedFunctionException.php(YGn7vendor/symfony/debug/Exception/OutOfMemoryException.php~(Y~o8vendor/symfony/debug/Exception/ContextErrorException.php(Y- d27vendor/symfony/debug/Exception/SilencedErrorContext.phpU(YUwk;vendor/symfony/debug/Exception/UndefinedMethodException.php(YL:6vendor/symfony/debug/Exception/FatalErrorException.php(Y>06vendor/symfony/debug/Exception/FatalThrowableError.phpD(YD 9vendor/symfony/debug/Exception/ClassNotFoundException.php(Yn%vendor/symfony/debug/ErrorHandler.php_8(Y_8w-vendor/symfony/console/Style/SymfonyStyle.php5(Y5H/vendor/symfony/console/Style/StyleInterface.php(Yj,vendor/symfony/console/Style/OutputStyle.php{(Y{.2vendor/symfony/console/Helper/InputAwareHelper.phpc(Yc|+vendor/symfony/console/Helper/TableCell.php(Y*+vendor/symfony/console/Helper/HelperSet.phpv(Yv3O'vendor/symfony/console/Helper/Table.php,(Y, v۶-vendor/symfony/console/Helper/ProgressBar.php%(Y%3׶,vendor/symfony/console/Helper/TableStyle.php (Y P޵7vendor/symfony/console/Helper/SymfonyQuestionHelper.phpl (Yl L1vendor/symfony/console/Helper/HelperInterface.php(Y0vendor/symfony/console/Helper/QuestionHelper.php(Y B76vendor/symfony/console/Helper/DebugFormatterHelper.phpx(YxNؼ2vendor/symfony/console/Helper/DescriptorHelper.phpz(Yzf3vendor/symfony/console/Helper/ProgressIndicator.phpx(Yx Ŷ(vendor/symfony/console/Helper/Helper.php(YOs1vendor/symfony/console/Helper/FormatterHelper.php(Y/vendor/symfony/console/Helper/ProcessHelper.php (Y a*0vendor/symfony/console/Helper/TableSeparator.php(YQa9vendor/symfony/console/Descriptor/DescriptorInterface.php(YQ3vendor/symfony/console/Descriptor/XmlDescriptor.php(YP$4vendor/symfony/console/Descriptor/TextDescriptor.phpP(YP,˽0vendor/symfony/console/Descriptor/Descriptor.php(YLs8vendor/symfony/console/Descriptor/MarkdownDescriptor.php(Yk.a<vendor/symfony/console/Descriptor/ApplicationDescription.php(Y4vendor/symfony/console/Descriptor/JsonDescriptor.php (Y l&vendor/symfony/console/Application.phpQQ(YQQ2vendor/symfony/console/Question/ChoiceQuestion.php" (Y" _ΰ,vendor/symfony/console/Question/Question.php< (Y< HTn8vendor/symfony/console/Question/ConfirmationQuestion.php(YQ@(vendor/symfony/console/ConsoleEvents.php(Y# G/vendor/symfony/console/Tester/CommandTester.php3(Y3ʧĶ3vendor/symfony/console/Tester/ApplicationTester.php+ (Y+ {q9vendor/symfony/console/Formatter/OutputFormatterStyle.phpL(YL8<Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php(YRڭ>vendor/symfony/console/Formatter/OutputFormatterStyleStack.phpL(YL.=b4vendor/symfony/console/Formatter/OutputFormatter.phpL(YLomhB=vendor/symfony/console/Formatter/OutputFormatterInterface.php(Y(vendor/symfony/console/Output/Output.php (Y 01 /vendor/symfony/console/Output/ConsoleOutput.php(Y,&,vendor/symfony/console/Output/NullOutput.php(YZ8vendor/symfony/console/Output/ConsoleOutputInterface.php(YrN1vendor/symfony/console/Output/OutputInterface.php(Y[B?0vendor/symfony/console/Output/BufferedOutput.phpb(YbA?.vendor/symfony/console/Output/StreamOutput.php(Y핶#vendor/symfony/console/Terminal.php(Ys5vendor/symfony/console/Exception/RuntimeException.php(Y,67vendor/symfony/console/Exception/ExceptionInterface.phpf(YfAB3vendor/symfony/console/Exception/LogicException.php(YO\e=vendor/symfony/console/Exception/CommandNotFoundException.php(Y L;vendor/symfony/console/Exception/InvalidOptionException.php(YH=vendor/symfony/console/Exception/InvalidArgumentException.php(Y̽Z-vendor/symfony/console/Event/ConsoleEvent.php(Yx\6vendor/symfony/console/Event/ConsoleTerminateEvent.phpz(Yz,L4vendor/symfony/console/Event/ConsoleCommandEvent.php(YZk26vendor/symfony/console/Event/ConsoleExceptionEvent.php(Y2.vendor/symfony/console/Command/ListCommand.php((Y($Xɶ0vendor/symfony/console/Command/LockableTrait.phpo(YoHӶ.vendor/symfony/console/Command/HelpCommand.php(Y3m*vendor/symfony/console/Command/Command.php(Yw/vendor/symfony/console/Logger/ConsoleLogger.php (Y ߏ/vendor/symfony/console/Input/InputInterface.php(Y扷k4vendor/symfony/console/Input/InputAwareInterface.php(YjT.vendor/symfony/console/Input/InputArgument.php(Y,vendor/symfony/console/Input/StringInput.phpc(YcW&vendor/symfony/console/Input/Input.phpU (YU Q۶9vendor/symfony/console/Input/StreamableInputInterface.php(YB+vendor/symfony/console/Input/ArrayInput.php/ (Y/ E:0vendor/symfony/console/Input/InputDefinition.php(YU窳*vendor/symfony/console/Input/ArgvInput.php(Y\1e,vendor/symfony/console/Input/InputOption.php (Y vendor/symfony/finder/Glob.phpK(YKjn vendor/symfony/finder/Finder.php (Y rn;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php(Y05vendor/symfony/finder/Iterator/PathFilterIterator.php(YǶ7vendor/symfony/finder/Iterator/CustomFilterIterator.php](Y]t౵=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php (Y Ί=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php/(Y/Y3vendor/symfony/finder/Iterator/SortableIterator.php,(Y,0:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.phpg(Yg!ԗ<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php5(Y5:vendor/symfony/finder/Iterator/DateRangeFilterIterator.phpz(YzT-9vendor/symfony/finder/Iterator/FilenameFilterIterator.phpr(YrtuAvendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php(Y9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php\(Y\p'1vendor/symfony/finder/Iterator/FilterIterator.php(YE6vendor/symfony/finder/Exception/ExceptionInterface.php(YGz-9vendor/symfony/finder/Exception/AccessDeniedException.php(Ys%vendor/symfony/finder/SplFileInfo.php(Y O5vendor/symfony/finder/Comparator/NumberComparator.php~(Y~ x3vendor/symfony/finder/Comparator/DateComparator.php%(Y%LEǶ/vendor/symfony/finder/Comparator/Comparator.php(YwT)vendor/symfony/event-dispatcher/Event.php)(Y)@BMvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php (Y vNBvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php((Y(Ҷ9vendor/symfony/event-dispatcher/Debug/WrappedListener.php (Y UgKvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php3(Y3[ 0vendor/symfony/event-dispatcher/GenericEvent.php(Y:3vendor/symfony/event-dispatcher/EventDispatcher.php (Y oLL<vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php{(Y{%&<vendor/symfony/event-dispatcher/EventSubscriberInterface.php(Yo;yAvendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php (Y 0a<vendor/symfony/event-dispatcher/EventDispatcherInterface.php(Y#vendor/symfony/dom-crawler/Link.php(Yvۮ1vendor/symfony/dom-crawler/AbstractUriElement.php (Y X #vendor/symfony/dom-crawler/Form.php(Y̶3vendor/symfony/dom-crawler/Field/InputFormField.php(YSF\6vendor/symfony/dom-crawler/Field/TextareaFormField.php(Y4^4vendor/symfony/dom-crawler/Field/ChoiceFormField.php(YI2vendor/symfony/dom-crawler/Field/FileFormField.php(YS.vendor/symfony/dom-crawler/Field/FormField.php(Y*kV&vendor/symfony/dom-crawler/Crawler.php.C(Y.C)|y$vendor/symfony/dom-crawler/Image.php(YJѓ0vendor/symfony/dom-crawler/FormFieldRegistry.phpA (YA !vendor/symfony/yaml/Unescaper.php(Y5ZYvendor/symfony/yaml/Escaper.php^(Y^lvendor/symfony/yaml/Parser.phpN(YN4vendor/symfony/yaml/Inline.php[@(Y[@/vendor/symfony/yaml/Dumper.php(Ysn2vendor/symfony/yaml/Exception/RuntimeException.php(Y$4vendor/symfony/yaml/Exception/ExceptionInterface.phpc(Yc۶0vendor/symfony/yaml/Exception/ParseException.phpD(YDX/vendor/symfony/yaml/Exception/DumpException.phps(Ysƶ+vendor/symfony/yaml/Command/LintCommand.php(YDŶvendor/symfony/yaml/Yaml.php(Yζ(vendor/symfony/browser-kit/CookieJar.phpq (Yq |'vendor/symfony/browser-kit/Response.php3(Y3vY~%vendor/symfony/browser-kit/Client.php!(Y!0z%vendor/symfony/browser-kit/Cookie.php(Y&vendor/symfony/browser-kit/Request.php(Y/o&vendor/symfony/browser-kit/History.php(YJ"vendor/symfony/process/Process.phpgZ(YgZ"L#%vendor/symfony/process/PhpProcess.php(YY/.vendor/symfony/process/PhpExecutableFinder.phpN(YNR)vendor/symfony/process/ProcessBuilder.php (Y ,GT+vendor/symfony/process/ExecutableFinder.php(Ym5vendor/symfony/process/Exception/RuntimeException.php(Y:7vendor/symfony/process/Exception/ExceptionInterface.phpf(Yf]>T3vendor/symfony/process/Exception/LogicException.php(Y ;vendor/symfony/process/Exception/ProcessFailedException.phpx(Yxzy=vendor/symfony/process/Exception/ProcessTimedOutException.php(Y. =vendor/symfony/process/Exception/InvalidArgumentException.php(Y+_*vendor/symfony/process/Pipes/UnixPipes.php(YYö/vendor/symfony/process/Pipes/PipesInterface.phpm(Ymu.vendor/symfony/process/Pipes/AbstractPipes.php (Y h3@-vendor/symfony/process/Pipes/WindowsPipes.phpd (Yd "'&vendor/symfony/process/InputStream.php'(Y'Tv9'vendor/symfony/process/ProcessUtils.phpY(YY2Z6vendor/theseer/tokenizer/src/NamespaceUriException.phpr(Yr+A.vendor/theseer/tokenizer/src/XMLSerializer.php(Yqx4&vendor/theseer/tokenizer/src/Token.php(Y̆0vendor/theseer/tokenizer/src/TokenCollection.php(YLն-vendor/theseer/tokenizer/src/NamespaceUri.php(Yu€*vendor/theseer/tokenizer/src/Exception.phpg(YgլX9vendor/theseer/tokenizer/src/TokenCollectionException.phpu(YuBK*vendor/theseer/tokenizer/src/Tokenizer.php(Yvendor/composer/ClassLoader.php(Y k!vendor/composer/autoload_real.php>(Y>g^#vendor/composer/autoload_static.php(YU+'vendor/composer/autoload_namespaces.php(Y&f%vendor/composer/autoload_classmap.phpen(Yen!vendor/composer/autoload_psr4.phpb (Yb k"vendor/composer/autoload_files.php2(Y2յ$6vendor/codeception/specify/src/Codeception/Specify.phpi(YiڶEvendor/codeception/specify/src/Codeception/Specify/ObjectProperty.php;(Y;CYDvendor/codeception/specify/src/Codeception/Specify/ConfigBuilder.phpW(YW=vendor/codeception/specify/src/Codeception/Specify/Config.php(Ymj6{'vendor/codeception/specify/RoboFile.php@(Y@a2Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php1(Y1 1Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php (Y ^ Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php(YfdGvendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.phpC(YCd=vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php(YKxIvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php(YEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php(YFvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php+(Y+G9Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php>(Y>JEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php(Y&@{Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.phpR(YRnBvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php(YlKvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.phps(YsF`%CLvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.phpu(YuxDvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php(Y"ѶCvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.phpa(YaFvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php(YeFvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php8(Y8Ѷ]vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.phpC(YC`xEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.phpm(YmWEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php(Y`Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php(Y5fGvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.phpj(Yj{Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php(YSvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php(YF Ovendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php(YjoDvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.phpA(YAP4GFvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.phpD(YDܙZHvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php(Y1DLvendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.phpK(YK<Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php(Y|UyQ9vendor/phpdocumentor/reflection-docblock/src/DocBlock.php(Yo@<Ivendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php(Y]F @vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php!(Y!h~o Lvendor/phpdocumentor/reflection-docblock/examples/04-adding-your-own-tag.php(Y4[vendor/phpdocumentor/reflection-docblock/examples/playing-with-descriptions/02-escaping.php(YNzRvendor/phpdocumentor/reflection-docblock/examples/03-reconstituting-a-docblock.phpQ(YQxUWvendor/phpdocumentor/reflection-docblock/examples/01-interpreting-a-simple-docblock.phpc(Yc{Jvendor/phpdocumentor/reflection-docblock/examples/02-interpreting-tags.php(Yƶ4vendor/phpdocumentor/reflection-common/src/Fqsen.php(Y+ڶ6vendor/phpdocumentor/reflection-common/src/Project.phpn(Ynj=vendor/phpdocumentor/reflection-common/src/ProjectFactory.php(Y{7vendor/phpdocumentor/reflection-common/src/Location.php(Y@6vendor/phpdocumentor/reflection-common/src/Element.php(Y#3vendor/phpdocumentor/reflection-common/src/File.php(Y6޶8vendor/phpdocumentor/type-resolver/src/Types/String_.php(Yw.?vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php; (Y; `6vendor/phpdocumentor/type-resolver/src/Types/Void_.php(Y%2۶6vendor/phpdocumentor/type-resolver/src/Types/Self_.php(Y{Ķ:vendor/phpdocumentor/type-resolver/src/Types/Callable_.php(Y%G{8vendor/phpdocumentor/type-resolver/src/Types/Boolean.php(Y75vendor/phpdocumentor/type-resolver/src/Types/This.php(YV56vendor/phpdocumentor/type-resolver/src/Types/Mixed.php(Yi8vendor/phpdocumentor/type-resolver/src/Types/Context.php(YX|r8vendor/phpdocumentor/type-resolver/src/Types/Static_.php(YA59vendor/phpdocumentor/type-resolver/src/Types/Resource.php(Y6vendor/phpdocumentor/type-resolver/src/Types/Null_.php(YJ8vendor/phpdocumentor/type-resolver/src/Types/Integer.php(Yt`97vendor/phpdocumentor/type-resolver/src/Types/Float_.php(YW7vendor/phpdocumentor/type-resolver/src/Types/Array_.php(YK18vendor/phpdocumentor/type-resolver/src/Types/Object_.php(Yy7vendor/phpdocumentor/type-resolver/src/Types/Scalar.php(YD,69vendor/phpdocumentor/type-resolver/src/Types/Compound.php(Y*&Ҷ/vendor/phpdocumentor/type-resolver/src/Type.phpg(YgGF:8vendor/phpdocumentor/type-resolver/src/FqsenResolver.php2(Y2hE7vendor/phpdocumentor/type-resolver/src/TypeResolver.php(Y`^vendor/phpdocumentor/type-resolver/examples/06-discovering-the-context-using-file-contents.phpc(Ycbvendor/phpdocumentor/type-resolver/examples/05-discovering-the-context-using-method-reflection.php(Y=IݶIvendor/phpdocumentor/type-resolver/examples/01-resolving-simple-types.php(Yӧ5Dvendor/phpdocumentor/type-resolver/examples/02-resolving-classes.php>(Y>ζavendor/phpdocumentor/type-resolver/examples/04-discovering-the-context-using-class-reflection.php(YF}`6vendor/phpdocumentor/type-resolver/examples/Classy.php(Y9iIvendor/phpdocumentor/type-resolver/examples/03-resolving-all-elements.php(YX7vendor/flow/jsonpath/src/Flow/JSONPath/AccessHelper.php(Yf8vendor/flow/jsonpath/src/Flow/JSONPath/JSONPathLexer.php(YCE[<vendor/flow/jsonpath/src/Flow/JSONPath/JSONPathException.php_(Y_ms>l3vendor/flow/jsonpath/src/Flow/JSONPath/JSONPath.php (Y Y`@vendor/flow/jsonpath/src/Flow/JSONPath/Filters/IndexesFilter.php(YԾDvendor/flow/jsonpath/src/Flow/JSONPath/Filters/QueryResultFilter.php(Y ٶ>vendor/flow/jsonpath/src/Flow/JSONPath/Filters/SliceFilter.php(YCvendor/flow/jsonpath/src/Flow/JSONPath/Filters/QueryMatchFilter.phpD(YDKAvendor/flow/jsonpath/src/Flow/JSONPath/Filters/AbstractFilter.php#(Y# >Bvendor/flow/jsonpath/src/Flow/JSONPath/Filters/RecursiveFilter.php,(Y,`]>vendor/flow/jsonpath/src/Flow/JSONPath/Filters/IndexFilter.php(Yj8vendor/flow/jsonpath/src/Flow/JSONPath/JSONPathToken.php(Yr .vendor/mongodb/mongodb/src/InsertOneResult.php(Y#j.vendor/mongodb/mongodb/src/BulkWriteResult.php(Yf3+vendor/mongodb/mongodb/src/DeleteResult.phpc(YcP;vendor/mongodb/mongodb/src/Model/CollectionInfoIterator.php(Y ?vendor/mongodb/mongodb/src/Model/DatabaseInfoLegacyIterator.php3(Y3g9ȶ.vendor/mongodb/mongodb/src/Model/BSONArray.php(Y ;3vendor/mongodb/mongodb/src/Model/CollectionInfo.php(Y%9vendor/mongodb/mongodb/src/Model/DatabaseInfoIterator.php(YT;Bvendor/mongodb/mongodb/src/Model/CollectionInfoCommandIterator.php(Y aAvendor/mongodb/mongodb/src/Model/CollectionInfoLegacyIterator.php(Ydʬ1vendor/mongodb/mongodb/src/Model/DatabaseInfo.php(Y^6vendor/mongodb/mongodb/src/Model/IndexInfoIterator.php(Y[k/vendor/mongodb/mongodb/src/Model/IndexInput.php(Yǿ>vendor/mongodb/mongodb/src/Model/IndexInfoIteratorIterator.php(Yix.vendor/mongodb/mongodb/src/Model/IndexInfo.php(Ym1vendor/mongodb/mongodb/src/Model/BSONDocument.php(Y P|%vendor/mongodb/mongodb/src/Client.php (Y g4vendor/mongodb/mongodb/src/Operation/DropIndexes.php(Ycc4vendor/mongodb/mongodb/src/Operation/ListIndexes.php(Y5w7vendor/mongodb/mongodb/src/Operation/DropCollection.php(YN޶2vendor/mongodb/mongodb/src/Operation/DeleteOne.php(Y| 6vendor/mongodb/mongodb/src/Operation/FindAndModify.php(Y]ID.vendor/mongodb/mongodb/src/Operation/Count.phpp (Yp r䕶:vendor/mongodb/mongodb/src/Operation/FindOneAndReplace.php(Y 6vendor/mongodb/mongodb/src/Operation/CreateIndexes.php|(Y|@ʦ9vendor/mongodb/mongodb/src/Operation/FindOneAndDelete.php(Yf}~0vendor/mongodb/mongodb/src/Operation/FindOne.phpz(Yz2Lɶ/vendor/mongodb/mongodb/src/Operation/Update.php (Y -6vendor/mongodb/mongodb/src/Operation/ListDatabases.php(Yf8vendor/mongodb/mongodb/src/Operation/ListCollections.php_ (Y_ 3vendor/mongodb/mongodb/src/Operation/InsertMany.php~ (Y~ _(9vendor/mongodb/mongodb/src/Operation/CreateCollection.php(Y6E5vendor/mongodb/mongodb/src/Operation/DropDatabase.php;(Y;?5M¶9vendor/mongodb/mongodb/src/Operation/FindOneAndUpdate.php(YI i3vendor/mongodb/mongodb/src/Operation/DeleteMany.php(YL\2vendor/mongodb/mongodb/src/Operation/BulkWrite.php(Yܐ3vendor/mongodb/mongodb/src/Operation/Executable.php(Yg\3vendor/mongodb/mongodb/src/Operation/UpdateMany.php3(Y3'#-vendor/mongodb/mongodb/src/Operation/Find.php6(Y6397Ҷ2vendor/mongodb/mongodb/src/Operation/Aggregate.php(Y]XͶ2vendor/mongodb/mongodb/src/Operation/InsertOne.php(YO/vendor/mongodb/mongodb/src/Operation/Delete.php(YI!2vendor/mongodb/mongodb/src/Operation/UpdateOne.php3(Y3"8vendor/mongodb/mongodb/src/Operation/DatabaseCommand.php(Y2~ex1vendor/mongodb/mongodb/src/Operation/Distinct.php (Y "[%3vendor/mongodb/mongodb/src/Operation/ReplaceOne.phpU(YU>zY)vendor/mongodb/mongodb/src/Collection.php6(Y6Y)o/vendor/mongodb/mongodb/src/InsertManyResult.php(Y9vendor/mongodb/mongodb/src/Exception/RuntimeException.php(YWS̶Avendor/mongodb/mongodb/src/Exception/UnexpectedValueException.php(Y g2vendor/mongodb/mongodb/src/Exception/Exception.phpi(Yi՝UAvendor/mongodb/mongodb/src/Exception/InvalidArgumentException.php(Y3sQ?vendor/mongodb/mongodb/src/Exception/BadMethodCallException.php(YNʶ(vendor/mongodb/mongodb/src/functions.php (Y /'vendor/mongodb/mongodb/src/Database.php(Y +vendor/mongodb/mongodb/src/UpdateResult.php(Y%vendor/mongodb/mongodb/docs/pretty.jso(YoPѸ2vendor/mongodb/mongodb/docs/img/save-flowchart.png(YƋO autoload.phpm(Ymuácodeceptr(Yr䵾shim.php(Y5uphpunit5-loggers.phpaH(YaHÙ */ final class Events { /** * Private constructor. This class cannot be instantiated. */ private function __construct() { } /** * The MODULE_INIT event occurs before modules are initialized. * * The event listener method receives a {@link Codeception\Event\SuiteEvent} instance. */ const MODULE_INIT = 'module.init'; /** * The SUITE_INIT event occurs when suite is initialized. * Modules are created and initialized, but Actor class is not loaded. * * The event listener method receives a {@link Codeception\Event\SuiteEvent} instance. */ const SUITE_INIT = 'suite.init'; /** * The SUITE_BEFORE event occurs before suite is executed. * * The event listener method receives a {@link Codeception\Event\SuiteEvent} instance. */ const SUITE_BEFORE = 'suite.before'; /** * The SUITE_AFTER event occurs after suite has been executed. * * The event listener method receives a {@link Codeception\Event\SuiteEvent} instance. */ const SUITE_AFTER = 'suite.after'; /** * The event listener method receives a {@link Codeception\Event\TestEvent} instance. */ const TEST_START = 'test.start'; /** * The event listener method receives a {@link Codeception\Event\TestEvent} instance. */ const TEST_BEFORE = 'test.before'; /** * The event listener method receives a {@link Codeception\Event\StepEvent} instance. */ const STEP_BEFORE = 'step.before'; /** * The event listener method receives a {@link Codeception\Event\StepEvent} instance. */ const STEP_AFTER = 'step.after'; /** * The TEST_FAIL event occurs whenever test has failed. * * The event listener method receives a {@link Codeception\Event\FailEvent} instance. */ const TEST_FAIL = 'test.fail'; /** * The TEST_ERROR event occurs whenever test got an error while being executed. * * The event listener method receives a {@link Codeception\Event\FailEvent} instance. */ const TEST_ERROR = 'test.error'; /** * The event listener method receives a {@link Codeception\Event\TestEvent} instance. */ const TEST_PARSED = 'test.parsed'; /** * The event listener method receives a {@link Codeception\Event\FailEvent} instance. */ const TEST_INCOMPLETE = 'test.incomplete'; /** * The event listener method receives a {@link Codeception\Event\FailEvent} instance. */ const TEST_SKIPPED = 'test.skipped'; /** * The event listener method receives a {@link Codeception\Event\TestEvent} instance. */ const TEST_SUCCESS = 'test.success'; /** * The event listener method receives a {@link Codeception\Event\TestEvent} instance. */ const TEST_AFTER = 'test.after'; /** * The event listener method receives a {@link Codeception\Event\TestEvent} instance. */ const TEST_END = 'test.end'; /** * The event listener method receives a {@link Codeception\Event\FailEvent} instance. */ const TEST_FAIL_PRINT = 'test.fail.print'; /** * The event listener method receives a {@link Codeception\Event\PrintResultEvent} instance. */ const RESULT_PRINT_AFTER = 'result.print.after'; } config = array_merge($this->config, $config); $this->options = $options; $this->output = new Output($options); $this->_initialize(); } public static function getSubscribedEvents() { if (!isset(static::$events)) { return [Events::SUITE_INIT => 'receiveModuleContainer']; } if (isset(static::$events[Events::SUITE_INIT])) { if (!is_array(static::$events[Events::SUITE_INIT])) { static::$events[Events::SUITE_INIT] = [[static::$events[Events::SUITE_INIT]]]; } static::$events[Events::SUITE_INIT][] = ['receiveModuleContainer']; } else { static::$events[Events::SUITE_INIT] = 'receiveModuleContainer'; } return static::$events; } public function receiveModuleContainer(SuiteEvent $e) { $this->modules = $e->getSuite()->getModules(); } /** * Pass config variables that should be injected into global config. * * @param array $config */ public function _reconfigure($config = []) { if (is_array($config)) { Config::append($config); } } /** * You can do all preperations here. No need to override constructor. * Also you can skip calling `_reconfigure` if you don't need to. */ public function _initialize() { $this->_reconfigure(); // hook for BC only. } protected function write($message) { if (!$this->options['silent']) { $this->output->write($message); } } protected function writeln($message) { if (!$this->options['silent']) { $this->output->writeln($message); } } public function hasModule($name) { return isset($this->modules[$name]); } public function getCurrentModuleNames() { return array_keys($this->modules); } public function getModule($name) { if (!$this->hasModule($name)) { throw new ModuleRequireException($name, "module is not enabled"); } return $this->modules[$name]; } public function getTestsDir() { return Config::testsDir(); } public function getLogDir() { return Config::outputDir(); } public function getDataDir() { return Config::dataDir(); } public function getRootDir() { return Config::projectDir(); } public function getGlobalConfig() { return Config::config(); } } testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_ERROR; $this->failed++; } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE; $this->failed++; } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param \Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE; $this->incomplete++; } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param \Exception $e * @param float $time * * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_RISKY; $this->risky++; } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param \Exception $e * @param float $time * * @since Method available since Release 3.0.0 */ public function addSkippedTest(PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED; $this->skipped++; } public function startTest(PHPUnit_Framework_Test $test) { $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_PASSED; } } currentTestCase = $this->document->createElement('testcase'); $isStrict = Configuration::config()['settings']['strict_xml']; foreach ($test->getReportFields() as $attr => $value) { if ($isStrict and !in_array($attr, $this->strictAttributes)) { continue; } $this->currentTestCase->setAttribute($attr, $value); } } public function endTest(\PHPUnit_Framework_Test $test, $time) { if ($this->currentTestCase !== null and $test instanceof Test) { $numAssertions = $test->getNumAssertions(); $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', $numAssertions ); } parent::endTest($test, $time); } } string === '') { return true; } foreach ($nodes as $node) { /** @var $node \WebDriverElement * */ if (!$node->isDisplayed()) { continue; } if (parent::matches(htmlspecialchars_decode($node->getText()))) { return true; } } return false; } protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) { if (!count($nodes)) { throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath'); } $output = "Failed asserting that any element by " . Locator::humanReadableString($selector); $output .= $this->uriMessage('on page'); if (count($nodes) < 5) { $output .= "\nElements: "; $output .= $this->nodesList($nodes); } else { $message = new Message("[total %s elements]"); $output .= $message->with(count($nodes)); } $output .= "\ncontains text '" . $this->string . "'"; throw new \PHPUnit_Framework_ExpectationFailedException( $output, $comparisonFailure ); } protected function failureDescription($nodes) { $desc = ''; foreach ($nodes as $node) { $desc .= parent::failureDescription($node->getText()); } return $desc; } protected function nodesList($nodes, $contains = null) { $output = ""; foreach ($nodes as $node) { if ($contains && strpos($node->getText(), $contains) === false) { continue; } /** @var $node \WebDriverElement * */ $message = new Message("\n+ <%s> %s"); $output .= $message->with($node->getTagName(), $node->getText()); } return $output; } } jsonType = $jsonType; $this->match = $match; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $jsonArray Value or object to evaluate. * * @return bool */ protected function matches($jsonArray) { if ($jsonArray instanceof JsonArray) { $jsonArray = $jsonArray->toArray(); } $matched = (new JsonTypeUtil($jsonArray))->matches($this->jsonType); if ($this->match) { if ($matched !== true) { throw new \PHPUnit_Framework_ExpectationFailedException($matched); } } else { if ($matched === true) { throw new \PHPUnit_Framework_ExpectationFailedException('Unexpectedly response matched: ' . json_encode($jsonArray)); } } return true; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { //unused return ''; } protected function failureDescription($other) { //unused return ''; } } string) { throw new \PHPUnit_Framework_ExpectationFailedException( "Element '$selector' was found", $comparisonFailure ); } /** @var $nodes DomCrawler * */ $output = "There was '$selector' element"; $output .= $this->uriMessage('on page'); $output .= $this->nodesList($nodes, $this->string); $output .= "\ncontaining '{$this->string}'"; throw new \PHPUnit_Framework_ExpectationFailedException( $output, $comparisonFailure ); } public function toString() { if ($this->string) { return 'that contains text "' . $this->string . '"'; } } } string) { throw new \PHPUnit_Framework_ExpectationFailedException( "Element $selectorString was found", $comparisonFailure ); } $output = "There was $selectorString element"; $output .= $this->uriMessage("on page"); $output .= $this->nodesList($nodes, $this->string); $output .= "\ncontaining '{$this->string}'"; throw new \PHPUnit_Framework_ExpectationFailedException( $output, $comparisonFailure ); } public function toString() { if ($this->string) { return 'that contains text "' . $this->string . '"'; } } } count()) { return false; } if ($this->string === '') { return true; } foreach ($nodes as $node) { if (parent::matches($node->nodeValue)) { return true; } } return false; } protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) { /** @var $nodes DomCrawler * */ if (!$nodes->count()) { throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath'); } $output = "Failed asserting that any element by '$selector'"; $output .= $this->uriMessage('on page'); $output .= " "; if ($nodes->count() < 10) { $output .= $this->nodesList($nodes); } else { $message = new Message("[total %s elements]"); $output .= $message->with($nodes->count())->getMessage(); } $output .= "\ncontains text '{$this->string}'"; throw new \PHPUnit_Framework_ExpectationFailedException( $output, $comparisonFailure ); } protected function failureDescription($other) { $desc = ''; foreach ($other as $o) { $desc .= parent::failureDescription($o->textContent); } return $desc; } protected function nodesList(DomCrawler $nodes, $contains = null) { $output = ""; foreach ($nodes as $node) { if ($contains && strpos($node->nodeValue, $contains) === false) { continue; } $output .= "\n+ " . $node->C14N(); } return $output; } } expected = $expected; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * * @return bool */ protected function matches($other) { $jsonResponseArray = new JsonArray($other); if (!is_array($jsonResponseArray->toArray())) { throw new \PHPUnit_Framework_AssertionFailedError('JSON response is not an array: ' . $other); } if ($jsonResponseArray->containsArray($this->expected)) { return true; } $comparator = new ArrayComparator(); $comparator->setFactory(new Factory); try { $comparator->assertEquals($this->expected, $jsonResponseArray->toArray()); } catch (ComparisonFailure $failure) { throw new \PHPUnit_Framework_ExpectationFailedException( "Response JSON does not contain the provided JSON\n", $failure ); } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { //unused return ''; } protected function failureDescription($other) { //unused return ''; } } string = $this->normalizeText((string)$string); $this->uri = $uri; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * * @return bool */ protected function matches($other) { $other = $this->normalizeText($other); return mb_stripos($other, $this->string, null, 'UTF-8') !== false; } /** * @param $text * @return string */ private function normalizeText($text) { $text = strtr($text, "\r\n", " "); return trim(preg_replace('/\\s{2,}/', ' ', $text)); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { $string = mb_strtolower($this->string, 'UTF-8'); return sprintf( 'contains "%s"', $string ); } protected function failureDescription($pageContent) { $message = $this->uriMessage('on page'); $message->append("\n--> "); $message->append(substr($pageContent, 0, 300)); if (strlen($pageContent) > 300) { $debugMessage = new Message( "[Content too long to display. See complete response in '" . codecept_output_dir() . "' directory]" ); $message->append("\n")->append($debugMessage); } $message->append("\n--> "); return $message->getMessage() . $this->toString(); } protected function uriMessage($onPage = "") { if (!$this->uri) { return ""; } $message = new Message($this->uri); $message->prepend(" $onPage "); return $message; } } dispatcher = $dispatcher; } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time) { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_FAIL, new FailEvent($test, $time, $e)); } public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_ERROR, new FailEvent($test, $time, $e)); } // This method was added in PHPUnit 6 public function addWarning(\PhpUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) { } public function addIncompleteTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) { if (in_array(spl_object_hash($test), $this->skippedTests)) { return; } $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_INCOMPLETE, new FailEvent($test, $time, $e)); $this->skippedTests[] = spl_object_hash($test); } public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) { if (in_array(spl_object_hash($test), $this->skippedTests)) { return; } $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_SKIPPED, new FailEvent($test, $time, $e)); $this->skippedTests[] = spl_object_hash($test); } public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { $this->dispatcher->dispatch('suite.start', new SuiteEvent($suite)); } public function endTestSuite(\PHPUnit_Framework_TestSuite $suite) { $this->dispatcher->dispatch('suite.end', new SuiteEvent($suite)); } public function startTest(\PHPUnit_Framework_Test $test) { $this->dispatcher->dispatch(Events::TEST_START, new TestEvent($test)); if (!$test instanceof TestInterface) { return; } if ($test->getMetadata()->isBlocked()) { return; } try { $this->startedTests[] = spl_object_hash($test); $this->fire(Events::TEST_BEFORE, new TestEvent($test)); } catch (\PHPUnit_Framework_IncompleteTestError $e) { $test->getTestResultObject()->addFailure($test, $e, 0); } catch (\PHPUnit_Framework_SkippedTestError $e) { $test->getTestResultObject()->addFailure($test, $e, 0); } catch (\Exception $e) { $test->getTestResultObject()->addError($test, $e, 0); } } public function endTest(\PHPUnit_Framework_Test $test, $time) { $hash = spl_object_hash($test); if (!in_array($hash, $this->unsuccessfulTests)) { $this->fire(Events::TEST_SUCCESS, new TestEvent($test, $time)); } if (in_array($hash, $this->startedTests)) { $this->fire(Events::TEST_AFTER, new TestEvent($test, $time)); } $this->dispatcher->dispatch(Events::TEST_END, new TestEvent($test, $time)); } protected function fire($event, TestEvent $eventType) { $test = $eventType->getTest(); if ($test instanceof TestInterface) { foreach ($test->getMetadata()->getGroups() as $group) { $this->dispatcher->dispatch($event . '.' . $group, $eventType); } } $this->dispatcher->dispatch($event, $eventType); } } testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_PASSED); if ($success) { $this->successful++; } if ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) { $status = "\033[41;37mFAIL\033[0m"; } elseif ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED) { $status = 'Skipped'; } elseif ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) { $status = 'Incomplete'; } elseif ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_ERROR) { $status = 'ERROR'; } else { $status = 'Ok'; } if (strlen($name) > 75) { $name = substr($name, 0, 70); } $line = $name . str_repeat('.', 75 - strlen($name)); $line .= $status; $this->write($line . "\n"); } protected function endRun() { $this->write("\nCodeception Results\n"); $this->write(sprintf( "Successful: %s. Failed: %s. Incomplete: %s. Skipped: %s", $this->successful, $this->failed, $this->skipped, $this->incomplete ) . "\n"); } public function printResult(\PHPUnit_Framework_TestResult $result) { } }

{name} {status} ({time}s)

    {action}

{suite} Tests

{fail}

{toggle} {name} {time}s

{steps} {failure} {png} {html}
Test results
{header} {scenarios}

Summary

Successful scenarios: {successfulScenarios}
Failed scenarios: {failedScenarios}
Skipped scenarios: {skippedScenarios}
Incomplete scenarios: {incompleteScenarios}

+ {metaStep}

{steps}
templatePath = sprintf( '%s%stemplate%s', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ); } /** * Handler for 'start class' event. * * @param string $name */ protected function startClass($name) { } public function endTest(\PHPUnit_Framework_Test $test, $time) { $steps = []; $success = ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_PASSED); if ($success) { $this->successful++; } if ($test instanceof ScenarioDriven) { $steps = $test->getScenario()->getSteps(); } $this->timeTaken += $time; switch ($this->testStatus) { case \PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE: $scenarioStatus = 'scenarioFailed'; break; case \PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED: $scenarioStatus = 'scenarioSkipped'; break; case \PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE: $scenarioStatus = 'scenarioIncomplete'; break; case \PHPUnit_Runner_BaseTestRunner::STATUS_ERROR: $scenarioStatus = 'scenarioFailed'; break; default: $scenarioStatus = 'scenarioSuccess'; } $stepsBuffer = ''; $subStepsBuffer = ''; $subStepsRendered = []; foreach ($steps as $step) { if ($step->getMetaStep()) { $subStepsRendered[$step->getMetaStep()->getAction()][] = $this->renderStep($step); } } foreach ($steps as $step) { if ($step->getMetaStep()) { if (! empty($subStepsRendered[$step->getMetaStep()->getAction()])) { $subStepsBuffer = implode('', $subStepsRendered[$step->getMetaStep()->getAction()]); unset($subStepsRendered[$step->getMetaStep()->getAction()]); $stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer); } } else { $stepsBuffer .= $this->renderStep($step); } } $scenarioTemplate = new \Text_Template( $this->templatePath . 'scenario.html' ); $failures = ''; $name = Descriptor::getTestSignature($test); if (isset($this->failures[$name])) { $failTemplate = new \Text_Template( $this->templatePath . 'fail.html' ); foreach ($this->failures[$name] as $failure) { $failTemplate->setVar(['fail' => nl2br($failure)]); $failures .= $failTemplate->render() . PHP_EOL; } } $png = ''; $html = ''; if ($test instanceof TestInterface) { $reports = $test->getMetadata()->getReports(); if (isset($reports['png'])) { $localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir()); $png = "
failure screenshot
"; } if (isset($reports['html'])) { $localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir()); $html = "See HTML snapshot of a failed page"; } } $toggle = $stepsBuffer ? '+' : ''; $testString = preg_replace('~^([\s\w\\\]+):\s~', '$1 » ', ucfirst(Descriptor::getTestAsString($test))); $scenarioTemplate->setVar( [ 'id' => ++$this->id, 'name' => $testString, 'scenarioStatus' => $scenarioStatus, 'steps' => $stepsBuffer, 'toggle' => $toggle, 'failure' => $failures, 'png' => $png, 'html' => $html, 'time' => round($time, 2) ] ); $this->scenarios .= $scenarioTemplate->render(); } public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { $suiteTemplate = new \Text_Template( $this->templatePath . 'suite.html' ); if (!$suite->getName()) { return; } $suiteTemplate->setVar(['suite' => ucfirst($suite->getName())]); $this->scenarios .= $suiteTemplate->render(); } /** * Handler for 'end run' event. */ protected function endRun() { $scenarioHeaderTemplate = new \Text_Template( $this->templatePath . 'scenario_header.html' ); $status = !$this->failed ? 'OK' : 'FAILED'; $scenarioHeaderTemplate->setVar( [ 'name' => 'Codeception Results', 'status' => $status, 'time' => round($this->timeTaken, 1) ] ); $header = $scenarioHeaderTemplate->render(); $scenariosTemplate = new \Text_Template( $this->templatePath . 'scenarios.html' ); $scenariosTemplate->setVar( [ 'header' => $header, 'scenarios' => $this->scenarios, 'successfulScenarios' => $this->successful, 'failedScenarios' => $this->failed, 'skippedScenarios' => $this->skipped, 'incompleteScenarios' => $this->incomplete ] ); $this->write($scenariosTemplate->render()); } /** * An error occurred. * * @param \PHPUnit_Framework_Test $test * @param \Exception $e * @param float $time */ public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->failures[Descriptor::getTestSignature($test)][] = $this->cleanMessage($e); parent::addError($test, $e, $time); } /** * A failure occurred. * * @param \PHPUnit_Framework_Test $test * @param \PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time) { $this->failures[Descriptor::getTestSignature($test)][] = $this->cleanMessage($e); parent::addFailure($test, $e, $time); } /** * @param $step * @return string */ protected function renderStep(Step $step) { $stepTemplate = new \Text_Template($this->templatePath . 'step.html'); $stepTemplate->setVar(['action' => $step->getHtml(), 'error' => $step->hasFailed() ? 'failedStep' : '']); return $stepTemplate->render(); } /** * @param $metaStep * @param $substepsBuffer * @return string */ protected function renderSubsteps(Meta $metaStep, $substepsBuffer) { $metaTemplate = new \Text_Template($this->templatePath . 'substeps.html'); $metaTemplate->setVar(['metaStep' => $metaStep, 'error' => $metaStep->hasFailed() ? 'failedStep' : '', 'steps' => $substepsBuffer, 'id' => uniqid()]); return $metaTemplate->render(); } private function cleanMessage($exception) { $msg = $exception->getMessage(); $msg = str_replace(['','','',''], ['','','',''], $msg); return htmlentities($msg); } } OutputInterface::VERBOSITY_NORMAL, $options['colors'] ? 'always' : 'never'); $this->dispatcher = $dispatcher; } protected function printDefect(\PHPUnit_Framework_TestFailure $defect, $count) { $this->write("\n---------\n"); $this->dispatcher->dispatch( Events::TEST_FAIL_PRINT, new FailEvent($defect->failedTest(), null, $defect->thrownException(), $count) ); } /** * @param \PHPUnit_Framework_TestFailure $defect */ protected function printDefectTrace(\PHPUnit_Framework_TestFailure $defect) { $this->write($defect->getExceptionAsString()); $this->writeNewLine(); $stackTrace = \PHPUnit_Util_Filter::getFilteredStacktrace($defect->thrownException(), false); foreach ($stackTrace as $i => $frame) { if (!isset($frame['file'])) { continue; } $this->write( sprintf( "#%d %s(%s)", $i + 1, $frame['file'], isset($frame['line']) ? $frame['line'] : '?' ) ); $this->writeNewLine(); } } public function startTest(\PHPUnit_Framework_Test $test) { if ($test instanceof Unit) { parent::startTest($test); } } public function endTest(\PHPUnit_Framework_Test $test, $time) { if ($test instanceof \PHPUnit_Framework_TestCase or $test instanceof \Codeception\Test\Test) { $this->numAssertions += $test->getNumAssertions(); } $this->lastTestFailed = false; } public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->lastTestFailed = true; } public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time) { $this->lastTestFailed = true; } public function addIncompleteTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->lastTestFailed = true; } public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) { $this->lastTestFailed = true; } } false, 'html' => false, 'tap' => false, 'json' => false, 'report' => false ]; protected $config = []; protected $logDir = null; public function __construct() { $this->config = Configuration::config(); $this->logDir = Configuration::outputDir(); // prepare log dir $this->phpUnitOverriders(); parent::__construct(); } public function phpUnitOverriders() { require_once __DIR__ . DIRECTORY_SEPARATOR . 'Overrides/Filter.php'; } /** * @return null|\PHPUnit_TextUI_ResultPrinter */ public function getPrinter() { return $this->printer; } public function prepareSuite(\PHPUnit_Framework_Test $suite, array &$arguments) { $this->handleConfiguration($arguments); $filterFactory = new \PHPUnit_Runner_Filter_Factory(); if ($arguments['groups']) { $filterFactory->addFilter( new \ReflectionClass('PHPUnit_Runner_Filter_Group_Include'), $arguments['groups'] ); } if ($arguments['excludeGroups']) { $filterFactory->addFilter( new \ReflectionClass('PHPUnit_Runner_Filter_Group_Exclude'), $arguments['excludeGroups'] ); } if ($arguments['filter']) { $filterFactory->addFilter( new \ReflectionClass('Codeception\PHPUnit\FilterTest'), $arguments['filter'] ); } $suite->injectFilter($filterFactory); } public function doEnhancedRun( \PHPUnit_Framework_Test $suite, \PHPUnit_Framework_TestResult $result, array $arguments = [] ) { unset($GLOBALS['app']); // hook for not to serialize globals $result->convertErrorsToExceptions(false); if (isset($arguments['report_useless_tests'])) { $result->beStrictAboutTestsThatDoNotTestAnything((bool)$arguments['report_useless_tests']); } if (isset($arguments['disallow_test_output'])) { $result->beStrictAboutOutputDuringTests((bool)$arguments['disallow_test_output']); } if (empty(self::$persistentListeners)) { $this->applyReporters($result, $arguments); } if (class_exists('\Symfony\Bridge\PhpUnit\SymfonyTestsListener')) { $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); $arguments['listeners'][] = new \Symfony\Bridge\PhpUnit\SymfonyTestsListener(); } $arguments['listeners'][] = $this->printer; // clean up listeners between suites foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $suite->run($result); unset($suite); foreach ($arguments['listeners'] as $listener) { $result->removeListener($listener); } return $result; } /** * @param \PHPUnit_Framework_TestResult $result * @param array $arguments * * @return array */ protected function applyReporters(\PHPUnit_Framework_TestResult $result, array $arguments) { foreach ($this->defaultListeners as $listener => $value) { if (!isset($arguments[$listener])) { $arguments[$listener] = $value; } } if ($arguments['report']) { self::$persistentListeners[] = $this->instantiateReporter('report'); } if ($arguments['html']) { codecept_debug('Printing HTML report into ' . $arguments['html']); self::$persistentListeners[] = $this->instantiateReporter( 'html', [$this->absolutePath($arguments['html'])] ); } if ($arguments['xml']) { codecept_debug('Printing JUNIT report into ' . $arguments['xml']); self::$persistentListeners[] = $this->instantiateReporter( 'xml', [$this->absolutePath($arguments['xml']), (bool)$arguments['log_incomplete_skipped']] ); } if ($arguments['tap']) { codecept_debug('Printing TAP report into ' . $arguments['tap']); self::$persistentListeners[] = $this->instantiateReporter('tap', [$this->absolutePath($arguments['tap'])]); } if ($arguments['json']) { codecept_debug('Printing JSON report into ' . $arguments['json']); self::$persistentListeners[] = $this->instantiateReporter( 'json', [$this->absolutePath($arguments['json'])] ); } foreach (self::$persistentListeners as $listener) { if ($listener instanceof ConsolePrinter) { $this->printer = $listener; continue; } $result->addListener($listener); } } protected function instantiateReporter($name, $args = []) { if (!isset($this->config['reporters'][$name])) { throw new ConfigurationException("Reporter $name not defined"); } return (new \ReflectionClass($this->config['reporters'][$name]))->newInstanceArgs($args); } private function absolutePath($path) { if ((strpos($path, '/') === 0) or (strpos($path, ':') === 1)) { // absolute path return $path; } return $this->logDir . $path; } } getInnerIterator()->current(); if ($test instanceof \PHPUnit_Framework_TestSuite) { return true; } $name = Descriptor::getTestSignature($test); $accepted = preg_match($this->filter, $name, $matches); if ($accepted && isset($this->filterMax)) { $set = end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return $accepted; } } getPrevious() ? $e->getPrevious()->getTrace() : $e->getTrace(); if ($e instanceof \PHPUnit_Framework_ExceptionWrapper) { $trace = $e->getSerializableTrace(); } foreach ($trace as $step) { if (self::classIsFiltered($step) and $filter) { continue; } if (self::fileIsFiltered($step) and $filter) { continue; } if (!$asString) { $stackTrace[] = $step; continue; } if (!isset($step['file'])) { continue; } $stackTrace .= $step['file'] . ':' . $step['line'] . "\n"; } return $stackTrace; } protected static function classIsFiltered($step) { if (!isset($step['class'])) { return false; } $className = $step['class']; foreach (self::$filteredClassesPattern as $filteredClassName) { if (strpos($className, $filteredClassName) === 0) { return true; } } return false; } protected static function fileIsFiltered($step) { if (!isset($step['file'])) { return false; } if (strpos($step['file'], 'codecept.phar/') !== false) { return true; } if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'phpunit') !== false) { return true; } if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'codeception') !== false) { return true; } $modulePath = 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR . 'Module'; if (strpos($step['file'], $modulePath) !== false) { return false; // don`t filter modules } if (strpos($step['file'], 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR) !== false) { return true; } return false; } } settings = $settings; $this->dispatcher = $dispatcher; $this->di = new Di(); $this->path = $settings['path']; $this->groupManager = new GroupManager($settings['groups']); $this->moduleContainer = new ModuleContainer($this->di, $settings); $modules = Configuration::modules($this->settings); foreach ($modules as $moduleName) { $this->moduleContainer->create($moduleName); } $this->moduleContainer->validateConflicts(); if (isset($settings['current_environment'])) { $this->env = $settings['current_environment']; } $this->suite = $this->createSuite($name); } public function initialize() { $this->dispatcher->dispatch(Events::MODULE_INIT, new Event\SuiteEvent($this->suite, null, $this->settings)); foreach ($this->moduleContainer->all() as $module) { $module->_initialize(); } if ($this->settings['actor'] && !file_exists(Configuration::supportDir() . $this->settings['actor'] . '.php')) { throw new Exception\ConfigurationException( $this->settings['actor'] . " class doesn't exist in suite folder.\nRun the 'build' command to generate it" ); } $this->dispatcher->dispatch(Events::SUITE_INIT, new Event\SuiteEvent($this->suite, null, $this->settings)); ini_set('xdebug.show_exception_trace', 0); // Issue https://github.com/symfony/symfony/issues/7646 } public function loadTests($path = null) { $testLoader = new Loader($this->settings); $testLoader->loadTests($path); $tests = $testLoader->getTests(); if ($this->settings['shuffle']) { shuffle($tests); } foreach ($tests as $test) { $this->addToSuite($test); } $this->suite->reorderDependencies(); } protected function addToSuite($test) { $this->configureTest($test); if ($test instanceof \PHPUnit_Framework_TestSuite_DataProvider) { foreach ($test->tests() as $t) { $this->addToSuite($t); } return; } if ($test instanceof TestInterface) { $this->checkEnvironmentExists($test); if (!$this->isExecutedInCurrentEnvironment($test)) { return; // skip tests from other environments } } $groups = $this->groupManager->groupsForTest($test); $this->suite->addTest($test, $groups); if (!empty($groups) && $test instanceof TestInterface) { $test->getMetadata()->setGroups($groups); } } protected function createSuite($name) { $suite = new Suite(); $suite->setBaseName(preg_replace('~\s.+$~', '', $name)); // replace everything after space (env name) if ($this->settings['namespace']) { $name = $this->settings['namespace'] . ".$name"; } $suite->setName($name); if (isset($this->settings['backup_globals'])) { $suite->setBackupGlobals((bool) $this->settings['backup_globals']); } if (isset($this->settings['be_strict_about_changes_to_global_state']) && method_exists($suite, 'setbeStrictAboutChangesToGlobalState')) { $suite->setbeStrictAboutChangesToGlobalState((bool)$this->settings['be_strict_about_changes_to_global_state']); } $suite->setModules($this->moduleContainer->all()); return $suite; } public function run(PHPUnit\Runner $runner, \PHPUnit_Framework_TestResult $result, $options) { $runner->prepareSuite($this->suite, $options); $this->dispatcher->dispatch(Events::SUITE_BEFORE, new Event\SuiteEvent($this->suite, $result, $this->settings)); $runner->doEnhancedRun($this->suite, $result, $options); $this->dispatcher->dispatch(Events::SUITE_AFTER, new Event\SuiteEvent($this->suite, $result, $this->settings)); } /** * @return \Codeception\Suite */ public function getSuite() { return $this->suite; } /** * @return ModuleContainer */ public function getModuleContainer() { return $this->moduleContainer; } protected function getActor() { if (!$this->settings['actor']) { return null; } return $this->settings['namespace'] ? rtrim($this->settings['namespace'], '\\') . '\\' . $this->settings['actor'] : $this->settings['actor']; } protected function checkEnvironmentExists(TestInterface $test) { $envs = $test->getMetadata()->getEnv(); if (empty($envs)) { return; } if (!isset($this->settings['env'])) { Notification::warning("Environments are not configured", Descriptor::getTestFullName($test)); return; } $availableEnvironments = array_keys($this->settings['env']); $listedEnvironments = explode(',', implode(',', $envs)); foreach ($listedEnvironments as $env) { if (!in_array($env, $availableEnvironments)) { Notification::warning("Environment $env was not configured but used in test", Descriptor::getTestFullName($test)); } } } protected function isExecutedInCurrentEnvironment(TestInterface $test) { $envs = $test->getMetadata()->getEnv(); if (empty($envs)) { return true; } $currentEnvironments = explode(',', $this->env); foreach ($envs as $envList) { $envList = explode(',', $envList); if (count($envList) == count(array_intersect($currentEnvironments, $envList))) { return true; } } return false; } /** * @param $t * @throws Exception\InjectionException */ protected function configureTest($t) { if (!$t instanceof TestInterface) { return; } $t->getMetadata()->setServices([ 'di' => clone($this->di), 'dispatcher' => $this->dispatcher, 'modules' => $this->moduleContainer ]); $t->getMetadata()->setCurrent([ 'actor' => $this->getActor(), 'env' => $this->env, 'modules' => $this->moduleContainer->all() ]); if ($t instanceof ScenarioDriven) { $t->preload(); } } } data = $data; } /** * Whether a offset exists * @link http://php.net/manual/en/arrayaccess.offsetexists.php * @param mixed $offset

* An offset to check for. *

* @return boolean true on success or false on failure. *

*

* The return value will be casted to boolean if non-boolean was returned. * @since 5.0.0 */ public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** * Offset to retrieve * @link http://php.net/manual/en/arrayaccess.offsetget.php * @param mixed $offset

* The offset to retrieve. *

* @return mixed Can return all value types. * @since 5.0.0 */ public function offsetGet($offset) { if (!$this->offsetExists($offset)) { throw new \PHPUnit_Framework_AssertionFailedError("Example $offset doesn't exist"); }; return $this->data[$offset]; } /** * Offset to set * @link http://php.net/manual/en/arrayaccess.offsetset.php * @param mixed $offset

* The offset to assign the value to. *

* @param mixed $value

* The value to set. *

* @return void * @since 5.0.0 */ public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** * Offset to unset * @link http://php.net/manual/en/arrayaccess.offsetunset.php * @param mixed $offset

* The offset to unset. *

* @return void * @since 5.0.0 */ public function offsetUnset($offset) { unset($this->data[$offset]); } /** * Count elements of an object * @link http://php.net/manual/en/countable.count.php * @return int The custom count as an integer. *

*

* The return value is cast to an integer. * @since 5.1.0 */ public function count() { return count($this->data); } /** * Retrieve an external iterator * @link http://php.net/manual/en/iteratoraggregate.getiterator.php * @return Traversable An instance of an object implementing Iterator or * Traversable * @since 5.0.0 */ public function getIterator() { return new \ArrayIterator($this->data); } } 'registerSuiteExtensions', Events::SUITE_AFTER => 'stopSuiteExtensions' ]; protected $config; protected $options = []; protected $globalExtensions = []; protected $suiteExtensions = []; /** * @var EventDispatcher */ protected $dispatcher; public function __construct(EventDispatcher $dispatcher) { $this->dispatcher = $dispatcher; $this->config = Configuration::config(); } public function bootGlobalExtensions($options) { $this->options = $options; $this->globalExtensions = $this->bootExtensions($this->config); } public function registerGlobalExtensions() { foreach ($this->globalExtensions as $extension) { $this->dispatcher->addSubscriber($extension); } } public function registerSuiteExtensions(SuiteEvent $e) { $suiteConfig = $e->getSettings(); $extensions = $this->bootExtensions($suiteConfig); $this->suiteExtensions = []; foreach ($extensions as $extension) { $extensionClass = get_class($extension); if (isset($this->globalExtensions[$extensionClass])) { continue; // already globally enabled } $this->dispatcher->addSubscriber($extension); $this->suiteExtensions[$extensionClass] = $extension; } } public function stopSuiteExtensions() { foreach ($this->suiteExtensions as $extension) { $this->dispatcher->removeSubscriber($extension); } $this->suiteExtensions = []; } protected function bootExtensions($config) { $extensions = []; foreach ($config['extensions']['enabled'] as $extensionClass) { if (is_array($extensionClass)) { $extensionClass = key($extensionClass); } if (!class_exists($extensionClass)) { throw new ConfigurationException( "Class `$extensionClass` is not defined. Autoload it or include into " . "'_bootstrap.php' file of 'tests' directory" ); } $extensionConfig = $this->getExtensionConfig($extensionClass, $config); $extension = new $extensionClass($extensionConfig, $this->options); if (!$extension instanceof EventSubscriberInterface) { throw new ConfigurationException( "Class $extensionClass is not an EventListener. Please create it as Extension or GroupObject." ); } $extensions[get_class($extension)] = $extension; } return $extensions; } private function getExtensionConfig($extension, $config) { $extensionConfig = isset($config['extensions']['config'][$extension]) ? $config['extensions']['config'][$extension] : []; if (!isset($config['extensions']['enabled'])) { return $extensionConfig; } if (!is_array($config['extensions']['enabled'])) { return $extensionConfig; } foreach ($config['extensions']['enabled'] as $enabledExtensionsConfig) { if (!is_array($enabledExtensionsConfig)) { continue; } $enabledExtension = key($enabledExtensionsConfig); if ($enabledExtension === $extension) { return Configuration::mergeConfigs(reset($enabledExtensionsConfig), $extensionConfig); } } return $extensionConfig; } } 'updateActor' ]; public function updateActor(SuiteEvent $e) { $settings = $e->getSettings(); if (!$settings['actor']) { codecept_debug('actor is empty'); return; // no actor } $modules = $e->getSuite()->getModules(); $actorActionsFile = Configuration::supportDir() . '_generated' . DIRECTORY_SEPARATOR . $settings['actor'] . 'Actions.php'; if (!file_exists($actorActionsFile)) { codecept_debug("Generating {$settings['actor']}Actions..."); $this->generateActorActions($actorActionsFile, $settings); return; } // load actor class to see hash $handle = @fopen($actorActionsFile, "r"); if ($handle and is_writable($actorActionsFile)) { $line = @fgets($handle); if (preg_match('~\[STAMP\] ([a-f0-9]*)~', $line, $matches)) { $hash = $matches[1]; $currentHash = Actions::genHash($modules, $settings); // regenerate actor class when hashes do not match if ($hash != $currentHash) { codecept_debug("Rebuilding {$settings['actor']}..."); @fclose($handle); $this->generateActorActions($actorActionsFile, $settings); return; } } @fclose($handle); } } protected function generateActorActions($actorActionsFile, $settings) { if (!file_exists(Configuration::supportDir() . '_generated')) { @mkdir(Configuration::supportDir() . '_generated'); } $actionsGenerator = new Actions($settings); $generated = $actionsGenerator->produce(); @file_put_contents($actorActionsFile, $generated); } } 'beforeSuite', Events::SUITE_AFTER => 'afterSuite', Events::TEST_START => 'startTest', Events::TEST_END => 'endTest', Events::STEP_BEFORE => 'beforeStep', Events::STEP_AFTER => 'afterStep', Events::TEST_SUCCESS => 'testSuccess', Events::TEST_FAIL => 'testFail', Events::TEST_ERROR => 'testError', Events::TEST_INCOMPLETE => 'testIncomplete', Events::TEST_SKIPPED => 'testSkipped', Events::TEST_FAIL_PRINT => 'printFail', Events::RESULT_PRINT_AFTER => 'afterResult', ]; /** * @var Step */ protected $metaStep; /** * @var Message */ protected $message = null; protected $steps = true; protected $debug = false; protected $ansi = true; protected $silent = false; protected $lastTestFailed = false; protected $printedTest = null; protected $rawStackTrace = false; protected $traceLength = 5; protected $width; /** * @var OutputInterface */ protected $output; protected $conditionalFails = []; protected $failedStep; protected $reports = []; protected $namespace = ''; protected $chars = ['success' => '+', 'fail' => 'x', 'of' => ':']; protected $options = [ 'debug' => false, 'ansi' => false, 'steps' => true, 'verbosity' => 0, 'xml' => null, 'html' => null, 'tap' => null, 'json' => null, ]; /** * @var MessageFactory */ protected $messageFactory; public function __construct($options) { $this->prepareOptions($options); $this->output = new Output($options); $this->messageFactory = new MessageFactory($this->output); if ($this->debug) { Debug::setOutput($this->output); } $this->detectWidth(); if ($this->options['ansi'] && !$this->isWin()) { $this->chars['success'] = '✔'; $this->chars['fail'] = '✖'; } foreach (['html', 'xml', 'tap', 'json'] as $report) { if (!$this->options[$report]) { continue; } $path = $this->absolutePath($this->options[$report]); $this->reports[] = sprintf( "- %s report generated in file://%s", strtoupper($report), $path ); } } // triggered for scenario based tests: cept, cest public function beforeSuite(SuiteEvent $e) { $this->namespace = ""; $settings = $e->getSettings(); if (isset($settings['namespace'])) { $this->namespace = $settings['namespace']; } $this->message("%s Tests (%d) ") ->with(ucfirst($e->getSuite()->getName()), $e->getSuite()->count()) ->style('bold') ->width($this->width, '-') ->prepend("\n") ->writeln(); if ($e->getSuite() instanceof Suite) { $message = $this->message( implode( ', ', array_map( function ($module) { return $module->_getName(); }, $e->getSuite()->getModules() ) ) ); $message->style('info') ->prepend('Modules: ') ->writeln(OutputInterface::VERBOSITY_VERBOSE); } $this->message('')->width($this->width, '-')->writeln(OutputInterface::VERBOSITY_VERBOSE); } // triggered for all tests public function startTest(TestEvent $e) { $this->conditionalFails = []; $test = $e->getTest(); $this->printedTest = $test; $this->message = null; if (!$this->output->isInteractive() and !$this->isDetailed($test)) { return; } $this->writeCurrentTest($test); if ($this->isDetailed($test)) { $this->output->writeln(''); $this->message(Descriptor::getTestSignature($test)) ->style('info') ->prepend('Signature: ') ->writeln(); $this->message(codecept_relative_path(Descriptor::getTestFullName($test))) ->style('info') ->prepend('Test: ') ->writeln(); if ($this->steps) { $this->message('Scenario --')->style('comment')->writeln(); $this->output->waitForDebugOutput = false; } } } public function afterStep(StepEvent $e) { $step = $e->getStep(); if (!$step->hasFailed()) { return; } if ($step instanceof Step\ConditionalAssertion) { $this->conditionalFails[] = $step; return; } $this->failedStep = $step; } /** * @param PrintResultEvent $event */ public function afterResult(PrintResultEvent $event) { $result = $event->getResult(); if ($result->skippedCount() + $result->notImplementedCount() > 0 and $this->options['verbosity'] < OutputInterface::VERBOSITY_VERBOSE) { $this->output->writeln("run with `-v` to get more info about skipped or incomplete tests"); } foreach ($this->reports as $message) { $this->output->writeln($message); } } private function absolutePath($path) { if ((strpos($path, '/') === 0) or (strpos($path, ':') === 1)) { // absolute path return $path; } return codecept_output_dir() . $path; } public function testSuccess(TestEvent $e) { if ($this->isDetailed($e->getTest())) { $this->message('PASSED')->center(' ')->style('ok')->append("\n")->writeln(); return; } $this->writelnFinishedTest($e, $this->message($this->chars['success'])->style('ok')); } public function endTest(TestEvent $e) { $this->metaStep = null; $this->printedTest = null; } public function testFail(FailEvent $e) { if ($this->isDetailed($e->getTest())) { $this->message('FAIL')->center(' ')->style('fail')->append("\n")->writeln(); return; } $this->writelnFinishedTest($e, $this->message($this->chars['fail'])->style('fail')); } public function testError(FailEvent $e) { if ($this->isDetailed($e->getTest())) { $this->message('ERROR')->center(' ')->style('fail')->append("\n")->writeln(); return; } $this->writelnFinishedTest($e, $this->message('E')->style('fail')); } public function testSkipped(FailEvent $e) { if ($this->isDetailed($e->getTest())) { $msg = $e->getFail()->getMessage(); $this->message('SKIPPED')->append($msg ? ": $msg" : '')->center(' ')->style('pending')->writeln(); return; } $this->writelnFinishedTest($e, $this->message('S')->style('pending')); } public function testIncomplete(FailEvent $e) { if ($this->isDetailed($e->getTest())) { $msg = $e->getFail()->getMessage(); $this->message('INCOMPLETE')->append($msg ? ": $msg" : '')->center(' ')->style('pending')->writeln(); return; } $this->writelnFinishedTest($e, $this->message('I')->style('pending')); } protected function isDetailed($test) { if ($test instanceof ScenarioDriven && $this->steps) { return true; } return false; } public function beforeStep(StepEvent $e) { if (!$this->steps or !$e->getTest() instanceof ScenarioDriven) { return; } $metaStep = $e->getStep()->getMetaStep(); if ($metaStep and $this->metaStep != $metaStep) { $this->message(' ' . $metaStep->getPrefix()) ->style('bold') ->append($metaStep->__toString()) ->writeln(); } $this->metaStep = $metaStep; $this->printStep($e->getStep()); } private function printStep(Step $step) { if ($step instanceof Comment and $step->__toString() == '') { return; // don't print empty comments } $msg = $this->message(' '); if ($this->metaStep) { $msg->append(' '); } $msg->append($step->getPrefix()); $prefixLength = $msg->getLength(); if (!$this->metaStep) { $msg->style('bold'); } $maxLength = $this->width - $prefixLength; $msg->append(OutputFormatter::escape($step->toString($maxLength))); if ($this->metaStep) { $msg->style('info'); } $msg->writeln(); } public function afterSuite(SuiteEvent $e) { $this->message()->width($this->width, '-')->writeln(); $messages = Notification::all(); foreach (array_count_values($messages) as $message => $count) { if ($count > 1) { $message = $count . 'x ' . $message; } $this->output->notification($message); } } public function printFail(FailEvent $e) { $failedTest = $e->getTest(); $fail = $e->getFail(); $this->output->write($e->getCount() . ") "); $this->writeCurrentTest($failedTest, false); $this->output->writeln(''); $this->message(" Test ") ->append(codecept_relative_path(Descriptor::getTestFullName($failedTest))) ->write(); if ($failedTest instanceof ScenarioDriven) { $this->printScenarioFail($failedTest, $fail); return; } $this->printException($fail); $this->printExceptionTrace($fail); } public function printException($e, $cause = null) { if ($e instanceof \PHPUnit_Framework_SkippedTestError or $e instanceof \PHPUnit_Framework_IncompleteTestError) { if ($e->getMessage()) { $this->message(OutputFormatter::escape($e->getMessage()))->prepend("\n")->writeln(); } return; } $class = $e instanceof \PHPUnit_Framework_ExceptionWrapper ? $e->getClassname() : get_class($e); if (strpos($class, 'Codeception\Exception') === 0) { $class = substr($class, strlen('Codeception\Exception\\')); } $this->output->writeln(''); $message = $this->message(OutputFormatter::escape($e->getMessage())); if ($e instanceof \PHPUnit_Framework_ExpectationFailedException) { $comparisonFailure = $e->getComparisonFailure(); if ($comparisonFailure) { $message->append($this->messageFactory->prepareComparisonFailureMessage($comparisonFailure)); } } $isFailure = $e instanceof \PHPUnit_Framework_AssertionFailedError || $class === 'PHPUnit_Framework_ExpectationFailedException' || $class === 'PHPUnit_Framework_AssertionFailedError'; if (!$isFailure) { $message->prepend("[$class] ")->block('error'); } if ($isFailure && $cause) { $cause = OutputFormatter::escape(ucfirst($cause)); $message->prepend(" Step $cause\n Fail "); } $message->writeln(); } public function printScenarioFail(ScenarioDriven $failedTest, $fail) { if ($this->conditionalFails) { $failedStep = (string) array_shift($this->conditionalFails); } else { $failedStep = (string) $failedTest->getScenario()->getMetaStep(); if ($failedStep === '') { $failedStep = (string)$this->failedStep; } } $this->printException($fail, $failedStep); $this->printScenarioTrace($failedTest); if ($this->output->getVerbosity() == OutputInterface::VERBOSITY_DEBUG) { $this->printExceptionTrace($fail); return; } if (!$fail instanceof \PHPUnit_Framework_AssertionFailedError) { $this->printExceptionTrace($fail); return; } } public function printExceptionTrace(\Exception $e) { static $limit = 10; if ($e instanceof \PHPUnit_Framework_SkippedTestError or $e instanceof \PHPUnit_Framework_IncompleteTestError) { return; } if ($this->rawStackTrace) { $this->message(OutputFormatter::escape(\PHPUnit_Util_Filter::getFilteredStacktrace($e, true, false)))->writeln(); return; } $trace = \PHPUnit_Util_Filter::getFilteredStacktrace($e, false); $i = 0; foreach ($trace as $step) { if ($i >= $limit) { break; } $i++; $message = $this->message($i)->prepend('#')->width(4); if (!isset($step['file'])) { foreach (['class', 'type', 'function'] as $info) { if (!isset($step[$info])) { continue; } $message->append($step[$info]); } $message->writeln(); continue; } $message->append($step['file'] . ':' . $step['line']); $message->writeln(); } $prev = $e->getPrevious(); if ($prev) { $this->printExceptionTrace($prev); } } /** * @param $failedTest */ public function printScenarioTrace(ScenarioDriven $failedTest) { $trace = array_reverse($failedTest->getScenario()->getSteps()); $length = $stepNumber = count($trace); if (!$length) { return; } $this->message("\nScenario Steps:\n")->style('comment')->writeln(); foreach ($trace as $step) { /** * @var $step Step */ if (!$step->__toString()) { continue; } $message = $this ->message($stepNumber) ->prepend(' ') ->width(strlen($length)) ->append(". "); $message->append(OutputFormatter::escape($step->getPhpCode($this->width - $message->getLength()))); if ($step->hasFailed()) { $message->style('bold'); } $line = $step->getLine(); if ($line and (!$step instanceof Comment)) { $message->append(" at $line"); } $stepNumber--; $message->writeln(); if (($length - $stepNumber - 1) >= $this->traceLength) { break; } } $this->output->writeln(""); } public function detectWidth() { $this->width = 60; if (!$this->isWin() && (php_sapi_name() === "cli") && (getenv('TERM')) && (getenv('TERM') != 'unknown') ) { // try to get terminal width from ENV variable (bash), see also https://github.com/Codeception/Codeception/issues/3788 if (getenv('COLUMNS')) { $this->width = getenv('COLUMNS'); } else { $this->width = (int) (`command -v tput >> /dev/null 2>&1 && tput cols`) - 2; } } elseif ($this->isWin() && (php_sapi_name() === "cli")) { exec('mode con', $output); if (isset($output[4])) { preg_match('/^ +.* +(\d+)$/', $output[4], $matches); if (!empty($matches[1])) { $this->width = (int) $matches[1]; } } } return $this->width; } private function isWin() { return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; } /** * @param \PHPUnit_Framework_SelfDescribing $test * @param bool $inProgress */ protected function writeCurrentTest(\PHPUnit_Framework_SelfDescribing $test, $inProgress = true) { $prefix = ($this->output->isInteractive() and !$this->isDetailed($test) and $inProgress) ? '- ' : ''; $testString = Descriptor::getTestAsString($test); $testString = preg_replace('~^([^:]+):\s~', "$1{$this->chars['of']} ", $testString); $this ->message($testString) ->prepend($prefix) ->write(); } protected function writelnFinishedTest(TestEvent $event, Message $result) { $test = $event->getTest(); if ($this->isDetailed($test)) { return; } if ($this->output->isInteractive()) { $this->output->write("\x0D"); } $result->append(' ')->write(); $this->writeCurrentTest($test, false); $conditionalFailsMessage = ""; $numFails = count($this->conditionalFails); if ($numFails == 1) { $conditionalFailsMessage = "[F]"; } elseif ($numFails) { $conditionalFailsMessage = "{$numFails}x[F]"; } $conditionalFailsMessage = "$conditionalFailsMessage "; $this->message($conditionalFailsMessage)->write(); $this->writeTimeInformation($event); $this->output->writeln(''); } /** * @param $string * @return Message */ private function message($string = '') { return $this->messageFactory->message($string); } /** * @param TestEvent $event */ protected function writeTimeInformation(TestEvent $event) { $time = $event->getTime(); if ($time) { $this ->message(number_format(round($time, 2), 2)) ->prepend('(') ->append('s)') ->style('info') ->write(); } } /** * @param $options */ private function prepareOptions($options) { $this->options = array_merge($this->options, $options); $this->debug = $this->options['debug'] || $this->options['verbosity'] >= OutputInterface::VERBOSITY_VERY_VERBOSE; $this->steps = $this->debug || $this->options['steps']; $this->rawStackTrace = ($this->options['verbosity'] === OutputInterface::VERBOSITY_DEBUG); } } 'before', Events::TEST_AFTER => 'after', Events::STEP_BEFORE => 'beforeStep', Events::STEP_AFTER => 'afterStep', Events::TEST_FAIL => 'failed', Events::TEST_ERROR => 'failed', Events::SUITE_BEFORE => 'beforeSuite', Events::SUITE_AFTER => 'afterSuite' ]; protected $modules = []; public function beforeSuite(SuiteEvent $e) { $suite = $e->getSuite(); if (!$suite instanceof Suite) { return; } $this->modules = $suite->getModules(); foreach ($this->modules as $module) { $module->_beforeSuite($e->getSettings()); } } public function afterSuite() { foreach ($this->modules as $module) { $module->_afterSuite(); } } public function before(TestEvent $event) { if (!$event->getTest() instanceof TestInterface) { return; } foreach ($this->modules as $module) { $module->_resetConfig(); $module->_before($event->getTest()); } } public function after(TestEvent $e) { if (!$e->getTest() instanceof TestInterface) { return; } foreach ($this->modules as $module) { $module->_after($e->getTest()); } } public function failed(FailEvent $e) { if (!$e->getTest() instanceof TestInterface) { return; } foreach ($this->modules as $module) { $module->_failed($e->getTest(), $e->getFail()); } } public function beforeStep(StepEvent $e) { foreach ($this->modules as $module) { $module->_beforeStep($e->getStep(), $e->getTest()); } } public function afterStep(StepEvent $e) { foreach ($this->modules as $module) { $module->_afterStep($e->getStep(), $e->getTest()); } } } 'loadBootstrap', ]; public function loadBootstrap(SuiteEvent $e) { $settings = $e->getSettings(); if (!isset($settings['bootstrap'])) { return; } if (!$settings['bootstrap']) { return; } $bootstrap = $settings['path'] . $settings['bootstrap']; if (!is_file($bootstrap)) { throw new ConfigurationException("Bootstrap file $bootstrap can't be loaded"); } require_once $bootstrap; } } 'testStart', Events::TEST_SUCCESS => 'testSuccess' ]; protected $successfulTests = []; public function testStart(TestEvent $event) { $test = $event->getTest(); if (!$test instanceof Dependent) { return; } $testSignatures = $test->getDependencies(); foreach ($testSignatures as $signature) { if (!in_array($signature, $this->successfulTests)) { $test->getMetadata()->setSkip("This test depends on $signature to pass"); return; } } } public function testSuccess(TestEvent $event) { $test = $event->getTest(); if (!$test instanceof TestInterface) { return; } $this->successfulTests[] = Descriptor::getTestSignature($test); } } 'stopOnFail', ]; public function stopOnFail(SuiteEvent $e) { $e->getResult()->stopOnError(true); $e->getResult()->stopOnFailure(true); } } suiteEvent = $event; } public function terminate() { if ($this->suiteEvent) { $this->suiteEvent->getResult()->stopOnError(true); $this->suiteEvent->getResult()->stopOnFailure(true); } throw new \RuntimeException( "\n\n---------------------------\nTESTS EXECUTION TERMINATED\n---------------------------\n" ); } public static function getSubscribedEvents() { if (!function_exists(self::SIGNAL_FUNC)) { return []; } return [Events::SUITE_BEFORE => 'handleSuite']; } } 'handle' ]; /** * @var bool $stopped to keep shutdownHandler from possible looping. */ private $stopped = false; /** * @var bool $initialized to avoid double error handler substitution */ private $initialized = false; private $deprecationsInstalled = false; private $oldHandler; /** * @var int stores bitmask for errors */ private $errorLevel; public function __construct() { $this->errorLevel = E_ALL & ~E_STRICT & ~E_DEPRECATED; } public function handle(SuiteEvent $e) { $settings = $e->getSettings(); if ($settings['error_level']) { $this->errorLevel = eval("return {$settings['error_level']};"); } error_reporting($this->errorLevel); if ($this->initialized) { return; } // We must register shutdown function before deprecation error handler to restore previous error handler // and silence DeprecationErrorHandler yelling about 'THE ERROR HANDLER HAS CHANGED!' register_shutdown_function([$this, 'shutdownHandler']); $this->registerDeprecationErrorHandler(); $this->oldHandler = set_error_handler([$this, 'errorHandler']); $this->initialized = true; } public function errorHandler($errno, $errstr, $errfile, $errline, $context) { if (E_USER_DEPRECATED === $errno) { $this->handleDeprecationError($errno, $errstr, $errfile, $errline, $context); return; } if (!(error_reporting() & $errno)) { // This error code is not included in error_reporting return false; } if (strpos($errstr, 'Cannot modify header information') !== false) { return false; } throw new \PHPUnit_Framework_Exception($errstr, $errno); } public function shutdownHandler() { if ($this->deprecationsInstalled) { restore_error_handler(); } if ($this->stopped) { return; } $this->stopped = true; $error = error_get_last(); if (!is_array($error)) { return; } if (error_reporting() === 0) { return; } // not fatal if ($error['type'] > 1) { return; } echo "\n\n\nFATAL ERROR. TESTS NOT FINISHED.\n"; echo sprintf("%s \nin %s:%d\n", $error['message'], $error['file'], $error['line']); } private function registerDeprecationErrorHandler() { if (class_exists('\Symfony\Bridge\PhpUnit\DeprecationErrorHandler')) { // DeprecationErrorHandler only will be installed if array('PHPUnit_Util_ErrorHandler', 'handleError') // is installed or no other error handlers are installed. // So we will remove Symfony\Component\Debug\ErrorHandler if it's installed. $old = set_error_handler('var_dump'); restore_error_handler(); if ($old && is_array($old) && count($old) > 0 && is_object($old[0]) && get_class($old[0]) === 'Symfony\Component\Debug\ErrorHandler' ) { restore_error_handler(); } $this->deprecationsInstalled = true; \Symfony\Bridge\PhpUnit\DeprecationErrorHandler::register(getenv('SYMFONY_DEPRECATIONS_HELPER')); } } private function handleDeprecationError($type, $message, $file, $line, $context) { if (!($this->errorLevel & $type)) { return; } if ($this->deprecationsInstalled && $this->oldHandler) { call_user_func($this->oldHandler, $type, $message, $file, $line, $context); return; } Notification::deprecate("$message", "$file:$line"); } } 'beforeClass', Events::SUITE_AFTER => ['afterClass', 100] ]; protected $hooks = []; protected $startedTests = []; protected $unsuccessfulTests = []; public function beforeClass(SuiteEvent $e) { foreach ($e->getSuite()->tests() as $test) { /** @var $test \PHPUnit_Framework_Test * */ if ($test instanceof \PHPUnit_Framework_TestSuite_DataProvider) { $potentialTestClass = strstr($test->getName(), '::', true); $this->hooks[$potentialTestClass] = \PHPUnit_Util_Test::getHookMethods($potentialTestClass); } $testClass = get_class($test); $this->hooks[$testClass] = \PHPUnit_Util_Test::getHookMethods($testClass); } $this->runHooks('beforeClass'); } public function afterClass(SuiteEvent $e) { $this->runHooks('afterClass'); } protected function runHooks($hookName) { foreach ($this->hooks as $className => $hook) { foreach ($hook[$hookName] as $method) { if (is_callable([$className, $method])) { call_user_func([$className, $method]); } } } } } input = $input; $this->addStyles($output); $this->output = $output; } /** * Change the directory where Codeception should be installed. */ public function initDir($workDir) { $this->checkInstalled($workDir); $this->sayInfo("Initializing Codeception in $workDir"); $this->createDirectoryFor($workDir); chdir($workDir); $this->workDir = $workDir; } /** * Override this class to create customized setup. * @return mixed */ abstract public function setup(); /** * ```php * ask('select the browser of your choice', 'firefox'); * * // propose firefox or chrome possible options * $this->ask('select the browser of your choice', ['firefox', 'chrome']); * * // ask true/false question * $this->ask('do you want to proceed (y/n)', true); * ``` * * @param $question * @param null $answer * @return mixed|string */ protected function ask($question, $answer = null) { $question = "? $question"; $dialog = new QuestionHelper(); if (is_array($answer)) { $question .= " (" . $answer[0] . ") "; return $dialog->ask($this->input, $this->output, new ChoiceQuestion($question, $answer, 0)); } if (is_bool($answer)) { $question .= " (y/n) "; return $dialog->ask($this->input, $this->output, new ConfirmationQuestion($question, $answer)); } if ($answer) { $question .= " ($answer)"; } return $dialog->ask($this->input, $this->output, new Question("$question ", $answer)); } /** * Print a message to console. * * ```php * say('Welcome to Setup'); * ``` * * * @param string $message */ protected function say($message = '') { $this->output->writeln($message); } /** * Print a successful message * @param $message */ protected function saySuccess($message) { $this->say(" $message "); } /** * Print warning message * @param $message */ protected function sayWarning($message) { $this->say(" $message "); } /** * Print info message * @param $message */ protected function sayInfo($message) { $this->say("> $message"); } /** * Create a helper class inside a directory * * @param $name * @param $directory */ protected function createHelper($name, $directory) { $file = $this->createDirectoryFor( $dir = $directory . DIRECTORY_SEPARATOR . "Helper", "$name.php" ) . "$name.php"; $gen = new Lib\Generator\Helper($name, $this->namespace); // generate helper $this->createFile( $file, $gen->produce() ); require_once $file; $this->sayInfo("$name helper has been created in $dir"); } /** * Create an empty directory and add a placeholder file into it * @param $dir */ protected function createEmptyDirectory($dir) { $this->createDirectoryFor($dir); $this->createFile($dir . DIRECTORY_SEPARATOR . '.gitkeep', ''); } protected function gitIgnore($path) { if (file_exists(self::GIT_IGNORE)) { file_put_contents($path . DIRECTORY_SEPARATOR . self::GIT_IGNORE, "*\n!" . self::GIT_IGNORE); } } protected function checkInstalled($dir = '.') { if (file_exists($dir . DIRECTORY_SEPARATOR . 'codeception.yml') || file_exists($dir . DIRECTORY_SEPARATOR . 'codeception.dist.yml')) { throw new \Exception("Codeception is already installed in this directory"); } } /** * Create an Actor class and generate actions for it. * Requires a suite config as array in 3rd parameter. * * @param $name * @param $directory * @param $suiteConfig */ protected function createActor($name, $directory, $suiteConfig) { $file = $this->createDirectoryFor( $directory, $name ) . $this->getShortClassName($name); $file .= '.php'; $suiteConfig['namespace'] = $this->namespace; $config = Configuration::mergeConfigs(Configuration::$defaultSuiteSettings, $suiteConfig); $actorGenerator = new Lib\Generator\Actor($config); $content = $actorGenerator->produce(); $this->createFile($file, $content); $this->sayInfo("$name actor has been created in $directory"); $actionsGenerator = new Lib\Generator\Actions($config); $content = $actionsGenerator->produce(); $generatedDir = $directory . DIRECTORY_SEPARATOR . '_generated'; $this->createDirectoryFor($generatedDir, 'Actions.php'); $this->createFile($generatedDir . DIRECTORY_SEPARATOR . $actorGenerator->getActorName() . 'Actions.php', $content); $this->sayInfo("Actions have been loaded"); } } readCustomCommandsFromConfig(); } catch (ConfigurationException $e) { if ($e->getCode() === 404) { return; } $this->renderException($e, new ConsoleOutput()); exit(1); } catch (\Exception $e) { $this->renderException($e, new ConsoleOutput()); exit(1); } } /** * Search custom commands and register them. * * @throws ConfigurationException */ protected function readCustomCommandsFromConfig() { $this->getCoreArguments(); // Maybe load outside configfile $config = Configuration::config(); if (empty($config['extensions']['commands'])) { return; } foreach ($config['extensions']['commands'] as $commandClass) { $commandName = $this->getCustomCommandName($commandClass); $this->add(new $commandClass($commandName)); } } /** * Validate and get the name of the command * * @param CustomCommandInterface $commandClass * * @throws ConfigurationException * * @return string */ protected function getCustomCommandName($commandClass) { if (!class_exists($commandClass)) { throw new ConfigurationException("Extension: Command class $commandClass not found"); } $interfaces = class_implements($commandClass); if (!in_array('Codeception\CustomCommandInterface', $interfaces)) { throw new ConfigurationException("Extension: Command {$commandClass} must implement " . "the interface `Codeception\\CustomCommandInterface`"); } return $commandClass::getCommandName(); } /** * To cache Class ArgvInput * * @inheritDoc */ public function run(InputInterface $input = null, OutputInterface $output = null) { if ($input === null) { $input = $this->getCoreArguments(); } return parent::run($input, $output); } /** * Add global a --config option. * * @return InputDefinition */ protected function getDefaultInputDefinition() { $inputDefinition = parent::getDefaultInputDefinition(); $inputDefinition->addOption( new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config') ); return $inputDefinition; } /** * Search for --config Option and if found will be loaded * * example: * -c file.yml|dir * -cfile.yml|dir * --config file.yml|dir * --config=file.yml|dir * * @return ArgvInput */ protected function getCoreArguments() { if ($this->coreArguments !== null) { return $this->coreArguments; } $argv = $_SERVER['argv']; $argvWithoutConfig = array(); for ($i = 0; $i < count($argv); $i++) { if (preg_match('/^(?:-([^c-]*)?c|--config(?:=|$))(.*)$/', $argv[$i], $match)) { if (!empty($match[2])) { //same index $this->preloadConfigration($match[2]); } elseif (isset($argv[$i + 1])) { //next index $this->preloadConfigration($argv[++$i]); } if (!empty($match[1])) { $argvWithoutConfig[] = "-".$match[1]; //rest comands } continue; } $argvWithoutConfig[] = $argv[$i]; } return $this->coreArguments = new ArgvInput($argvWithoutConfig); } /** * Pre load Configuration, the config option is use. * * @param string $configFile Path to Configuration * * @throws ConfigurationException */ protected function preloadConfigration($configFile) { try { Configuration::config($configFile); } catch (ConfigurationException $e) { if ($e->getCode() == 404) { throw new ConfigurationException("Your configuration file `{$configFile}` could not be found.", 405); } throw $e; } } } internalDomains = null; } } getModule('{{MODULE_NAME}}')->_findElements('.items'); * $els = $this->getModule('{{MODULE_NAME}}')->_findElements(['name' => 'username']); * * $editLinks = $this->getModule('{{MODULE_NAME}}')->_findElements(['link' => 'Edit']); * // now you can iterate over $editLinks and check that all them have valid hrefs * ``` * * WebDriver module returns `Facebook\WebDriver\Remote\RemoteWebElement` instances * PhpBrowser and Framework modules return `Symfony\Component\DomCrawler\Crawler` instances * * @api * @param $locator * @return array of interactive elements */ public function _findElements($locator); } amOnSubdomain('user'); * $I->amOnPage('/'); * // moves to http://user.mysite.com/ * ?> * ``` * * @param $subdomain * * @return mixed */ public function amOnSubdomain($subdomain); /** * Open web page at the given absolute URL and sets its hostname as the base host. * * ``` php * amOnUrl('http://codeception.com'); * $I->amOnPage('/quickstart'); // moves to http://codeception.com/quickstart * ?> * ``` */ public function amOnUrl($url); public function _getUrl(); } getModule('{{MODULE_NAME}}')->_savePageSource(codecept_output_dir().'page.html'); * ``` * @api * @param $filename */ public function _savePageSource($filename); } loadSessionSnapshot('login')) return; * * // logging in * $I->amOnPage('/login'); * $I->fillField('name', 'jon'); * $I->fillField('password', '123345'); * $I->click('Login'); * * // saving snapshot * $I->saveSessionSnapshot('login'); * } * ?> * ``` * * @param $name * @return mixed */ public function saveSessionSnapshot($name); /** * Loads cookies from saved snapshot. * * @param $name * @see saveSessionSnapshot * @return mixed */ public function loadSessionSnapshot($name); } amOnPage('/'); * // opens /register page * $I->amOnPage('/register'); * ``` * * @param $page */ public function amOnPage($page); /** * Checks that the current page contains the given string (case insensitive). * * You can specify a specific HTML element (via CSS or XPath) as the second * parameter to only search within that element. * * ``` php * see('Logout'); // I can suppose user is logged in * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page * $I->see('Sign Up', '//body/h1'); // with XPath * $I->see('Sign Up', ['css' => 'body h1']); // with strict CSS locator * ``` * * Note that the search is done after stripping all HTML tags from the body, * so `$I->see('strong')` will return true for strings like: * * - `

I am Stronger than thou

` * - `` * * But will *not* be true for strings like: * * - `Home` * - `
Home` * - `` * * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector */ public function see($text, $selector = null); /** * Checks that the current page doesn't contain the text specified (case insensitive). * Give a locator as the second parameter to match a specific region. * * ```php * dontSee('Login'); // I can suppose user is already logged in * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page * $I->dontSee('Sign Up','//body/h1'); // with XPath * $I->dontSee('Sign Up', ['css' => 'body h1']); // with strict CSS locator * ``` * * Note that the search is done after stripping all HTML tags from the body, * so `$I->dontSee('strong')` will fail on strings like: * * - `

I am Stronger than thou

` * - `` * * But will ignore strings like: * * - `Home` * - `
Home` * - `` * * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector */ public function dontSee($text, $selector = null); /** * Checks that the current page contains the given string in its * raw source code. * * ``` php * seeInSource('

Green eggs & ham

'); * ``` * * @param $raw */ public function seeInSource($raw); /** * Checks that the current page contains the given string in its * raw source code. * * ```php * dontSeeInSource('

Green eggs & ham

'); * ``` * * @param $raw */ public function dontSeeInSource($raw); /** * Submits the given form on the page, optionally with the given form * values. Pass the form field's values as an array in the second * parameter. * * Although this function can be used as a short-hand version of * `fillField()`, `selectOption()`, `click()` etc. it has some important * differences: * * * Only field *names* may be used, not CSS/XPath selectors nor field labels * * If a field is sent to this function that does *not* exist on the page, * it will silently be added to the HTTP request. This is helpful for testing * some types of forms, but be aware that you will *not* get an exception * like you would if you called `fillField()` or `selectOption()` with * a missing field. * * Fields that are not provided will be filled by their values from the page, * or from any previous calls to `fillField()`, `selectOption()` etc. * You don't need to click the 'Submit' button afterwards. * This command itself triggers the request to form's action. * * You can optionally specify which button's value to include * in the request with the last parameter (as an alternative to * explicitly setting its value in the second parameter), as * button values are not otherwise included in the request. * * Examples: * * ``` php * submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ]); * // or * $I->submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ], 'submitButtonName'); * * ``` * * For example, given this sample "Sign Up" form: * * ``` html *
* Login: *
* Password: *
* Do you agree to our terms? *
* Select pricing plan: * * *
* ``` * * You could write the following to submit it: * * ``` php * submitForm( * '#userForm', * [ * 'user' => [ * 'login' => 'Davert', * 'password' => '123456', * 'agree' => true * ] * ], * 'submitButton' * ); * ``` * Note that "2" will be the submitted value for the "plan" field, as it is * the selected option. * * You can also emulate a JavaScript submission by not specifying any * buttons in the third parameter to submitForm. * * ```php * submitForm( * '#userForm', * [ * 'user' => [ * 'login' => 'Davert', * 'password' => '123456', * 'agree' => true * ] * ] * ); * ``` * * This function works well when paired with `seeInFormFields()` * for quickly testing CRUD interfaces and form validation logic. * * ``` php * 'value', * 'field2' => 'another value', * 'checkbox1' => true, * // ... * ]; * $I->submitForm('#my-form', $form, 'submitButton'); * // $I->amOnPage('/path/to/form-page') may be needed * $I->seeInFormFields('#my-form', $form); * ``` * * Parameter values can be set to arrays for multiple input fields * of the same name, or multi-select combo boxes. For checkboxes, * you can use either the string value or boolean `true`/`false` which will * be replaced by the checkbox's value in the DOM. * * ``` php * submitForm('#my-form', [ * 'field1' => 'value', * 'checkbox' => [ * 'value of first checkbox', * 'value of second checkbox', * ], * 'otherCheckboxes' => [ * true, * false, * false * ], * 'multiselect' => [ * 'first option value', * 'second option value' * ] * ]); * ``` * * Mixing string and boolean values for a checkbox's value is not supported * and may produce unexpected results. * * Field names ending in `[]` must be passed without the trailing square * bracket characters, and must contain an array for its value. This allows * submitting multiple values with the same name, consider: * * ```php * submitForm('#my-form', [ * 'field[]' => 'value', * 'field[]' => 'another value', // 'field[]' is already a defined key * ]); * ``` * * The solution is to pass an array value: * * ```php * submitForm('#my-form', [ * 'field' => [ * 'value', * 'another value', * ] * ]); * ``` * * @param $selector * @param $params * @param $button */ public function submitForm($selector, array $params, $button = null); /** * Perform a click on a link or a button, given by a locator. * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string. * For buttons, the "value" attribute, "name" attribute, and inner text are searched. * For links, the link text is searched. * For images, the "alt" attribute and inner text of any parent links are searched. * * The second parameter is a context (CSS or XPath locator) to narrow the search. * * Note that if the locator matches a button of type `submit`, the form will be submitted. * * ``` php * click('Logout'); * // button of form * $I->click('Submit'); * // CSS button * $I->click('#form input[type=submit]'); * // XPath * $I->click('//form/*[@type=submit]'); * // link in context * $I->click('Logout', '#nav'); * // using strict locator * $I->click(['link' => 'Login']); * ?> * ``` * * @param $link * @param $context */ public function click($link, $context = null); /** * Checks that there's a link with the specified text. * Give a full URL as the second parameter to match links with that exact URL. * * ``` php * seeLink('Logout'); // matches Logout * $I->seeLink('Logout','/logout'); // matches Logout * ?> * ``` * * @param $text * @param null $url */ public function seeLink($text, $url = null); /** * Checks that the page doesn't contain a link with the given string. * If the second parameter is given, only links with a matching "href" attribute will be checked. * * ``` php * dontSeeLink('Logout'); // I suppose user is not logged in * $I->dontSeeLink('Checkout now', '/store/cart.php'); * ?> * ``` * * @param $text * @param null $url */ public function dontSeeLink($text, $url = null); /** * Checks that current URI contains the given string. * * ``` php * seeInCurrentUrl('home'); * // to match: /users/1 * $I->seeInCurrentUrl('/users/'); * ?> * ``` * * @param $uri */ public function seeInCurrentUrl($uri); /** * Checks that the current URL is equal to the given string. * Unlike `seeInCurrentUrl`, this only matches the full URL. * * ``` php * seeCurrentUrlEquals('/'); * ?> * ``` * * @param $uri */ public function seeCurrentUrlEquals($uri); /** * Checks that the current URL matches the given regular expression. * * ``` php * seeCurrentUrlMatches('~$/users/(\d+)~'); * ?> * ``` * * @param $uri */ public function seeCurrentUrlMatches($uri); /** * Checks that the current URI doesn't contain the given string. * * ``` php * dontSeeInCurrentUrl('/users/'); * ?> * ``` * * @param $uri */ public function dontSeeInCurrentUrl($uri); /** * Checks that the current URL doesn't equal the given string. * Unlike `dontSeeInCurrentUrl`, this only matches the full URL. * * ``` php * dontSeeCurrentUrlEquals('/'); * ?> * ``` * * @param $uri */ public function dontSeeCurrentUrlEquals($uri); /** * Checks that current url doesn't match the given regular expression. * * ``` php * dontSeeCurrentUrlMatches('~$/users/(\d+)~'); * ?> * ``` * * @param $uri */ public function dontSeeCurrentUrlMatches($uri); /** * Executes the given regular expression against the current URI and returns the first match. * If no parameters are provided, the full URI is returned. * * ``` php * grabFromCurrentUrl('~$/user/(\d+)/~'); * $uri = $I->grabFromCurrentUrl(); * ?> * ``` * * @param null $uri * * @return mixed */ public function grabFromCurrentUrl($uri = null); /** * Checks that the specified checkbox is checked. * * ``` php * seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); * ?> * ``` * * @param $checkbox */ public function seeCheckboxIsChecked($checkbox); /** * Check that the specified checkbox is unchecked. * * ``` php * dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. * ?> * ``` * * @param $checkbox */ public function dontSeeCheckboxIsChecked($checkbox); /** * Checks that the given input field or textarea contains the given value. * For fuzzy locators, fields are matched by label text, the "name" attribute, CSS, and XPath. * * ``` php * seeInField('Body','Type your comment here'); * $I->seeInField('form textarea[name=body]','Type your comment here'); * $I->seeInField('form input[type=hidden]','hidden_value'); * $I->seeInField('#searchform input','Search'); * $I->seeInField('//form/*[@name=search]','Search'); * $I->seeInField(['name' => 'search'], 'Search'); * ?> * ``` * * @param $field * @param $value */ public function seeInField($field, $value); /** * Checks that an input field or textarea doesn't contain the given value. * For fuzzy locators, the field is matched by label text, CSS and XPath. * * ``` php * dontSeeInField('Body','Type your comment here'); * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); * $I->dontSeeInField('form input[type=hidden]','hidden_value'); * $I->dontSeeInField('#searchform input','Search'); * $I->dontSeeInField('//form/*[@name=search]','Search'); * $I->dontSeeInField(['name' => 'search'], 'Search'); * ?> * ``` * * @param $field * @param $value */ public function dontSeeInField($field, $value); /** * Checks if the array of form parameters (name => value) are set on the form matched with the * passed selector. * * ``` php * seeInFormFields('form[name=myform]', [ * 'input1' => 'value', * 'input2' => 'other value', * ]); * ?> * ``` * * For multi-select elements, or to check values of multiple elements with the same name, an * array may be passed: * * ``` php * seeInFormFields('.form-class', [ * 'multiselect' => [ * 'value1', * 'value2', * ], * 'checkbox[]' => [ * 'a checked value', * 'another checked value', * ], * ]); * ?> * ``` * * Additionally, checkbox values can be checked with a boolean. * * ``` php * seeInFormFields('#form-id', [ * 'checkbox1' => true, // passes if checked * 'checkbox2' => false, // passes if unchecked * ]); * ?> * ``` * * Pair this with submitForm for quick testing magic. * * ``` php * 'value', * 'field2' => 'another value', * 'checkbox1' => true, * // ... * ]; * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); * // $I->amOnPage('/path/to/form-page') may be needed * $I->seeInFormFields('//form[@id=my-form]', $form); * ?> * ``` * * @param $formSelector * @param $params */ public function seeInFormFields($formSelector, array $params); /** * Checks if the array of form parameters (name => value) are not set on the form matched with * the passed selector. * * ``` php * dontSeeInFormFields('form[name=myform]', [ * 'input1' => 'non-existent value', * 'input2' => 'other non-existent value', * ]); * ?> * ``` * * To check that an element hasn't been assigned any one of many values, an array can be passed * as the value: * * ``` php * dontSeeInFormFields('.form-class', [ * 'fieldName' => [ * 'This value shouldn\'t be set', * 'And this value shouldn\'t be set', * ], * ]); * ?> * ``` * * Additionally, checkbox values can be checked with a boolean. * * ``` php * dontSeeInFormFields('#form-id', [ * 'checkbox1' => true, // fails if checked * 'checkbox2' => false, // fails if unchecked * ]); * ?> * ``` * * @param $formSelector * @param $params */ public function dontSeeInFormFields($formSelector, array $params); /** * Selects an option in a select tag or in radio button group. * * ``` php * selectOption('form select[name=account]', 'Premium'); * $I->selectOption('form input[name=payment]', 'Monthly'); * $I->selectOption('//form/select[@name=account]', 'Monthly'); * ?> * ``` * * Provide an array for the second argument to select multiple options: * * ``` php * selectOption('Which OS do you use?', array('Windows','Linux')); * ?> * ``` * * Or provide an associative array for the second argument to specifically define which selection method should be used: * * ``` php * selectOption('Which OS do you use?', array('text' => 'Windows')); // Only search by text 'Windows' * $I->selectOption('Which OS do you use?', array('value' => 'windows')); // Only search by value 'windows' * ?> * ``` * * @param $select * @param $option */ public function selectOption($select, $option); /** * Ticks a checkbox. For radio buttons, use the `selectOption` method instead. * * ``` php * checkOption('#agree'); * ?> * ``` * * @param $option */ public function checkOption($option); /** * Unticks a checkbox. * * ``` php * uncheckOption('#notify'); * ?> * ``` * * @param $option */ public function uncheckOption($option); /** * Fills a text field or textarea with the given string. * * ``` php * fillField("//input[@type='text']", "Hello World!"); * $I->fillField(['name' => 'email'], 'jon@mail.com'); * ?> * ``` * * @param $field * @param $value */ public function fillField($field, $value); /** * Attaches a file relative to the Codeception data directory to the given file upload field. * * ``` php * attachFile('input[@type="file"]', 'prices.xls'); * ?> * ``` * * @param $field * @param $filename */ public function attachFile($field, $filename); /** * Finds and returns the text contents of the given element. * If a fuzzy locator is used, the element is found using CSS, XPath, * and by matching the full page source by regular expression. * * ``` php * grabTextFrom('h1'); * $heading = $I->grabTextFrom('descendant-or-self::h1'); * $value = $I->grabTextFrom('~ * ``` * * @param $cssOrXPathOrRegex * * @return mixed */ public function grabTextFrom($cssOrXPathOrRegex); /** * Finds the value for the given form field. * If a fuzzy locator is used, the field is found by field name, CSS, and XPath. * * ``` php * grabValueFrom('Name'); * $name = $I->grabValueFrom('input[name=username]'); * $name = $I->grabValueFrom('descendant-or-self::form/descendant::input[@name = 'username']'); * $name = $I->grabValueFrom(['name' => 'username']); * ?> * ``` * * @param $field * * @return mixed */ public function grabValueFrom($field); /** * Grabs the value of the given attribute value from the given element. * Fails if element is not found. * * ``` php * grabAttributeFrom('#tooltip', 'title'); * ?> * ``` * * * @param $cssOrXpath * @param $attribute * * @return mixed */ public function grabAttributeFrom($cssOrXpath, $attribute); /** * Grabs either the text content, or attribute values, of nodes * matched by $cssOrXpath and returns them as an array. * * ```html * First * Second * Third * ``` * * ```php * grabMultiple('a'); * * // would return ['#first', '#second', '#third'] * $aLinks = $I->grabMultiple('a', 'href'); * ?> * ``` * * @param $cssOrXpath * @param $attribute * @return string[] */ public function grabMultiple($cssOrXpath, $attribute = null); /** * Checks that the given element exists on the page and is visible. * You can also specify expected attributes of this element. * * ``` php * seeElement('.error'); * $I->seeElement('//form/input[1]'); * $I->seeElement('input', ['name' => 'login']); * $I->seeElement('input', ['value' => '123456']); * * // strict locator in first arg, attributes in second * $I->seeElement(['css' => 'form input'], ['name' => 'login']); * ?> * ``` * * @param $selector * @param array $attributes * @return */ public function seeElement($selector, $attributes = []); /** * Checks that the given element is invisible or not present on the page. * You can also specify expected attributes of this element. * * ``` php * dontSeeElement('.error'); * $I->dontSeeElement('//form/input[1]'); * $I->dontSeeElement('input', ['name' => 'login']); * $I->dontSeeElement('input', ['value' => '123456']); * ?> * ``` * * @param $selector * @param array $attributes */ public function dontSeeElement($selector, $attributes = []); /** * Checks that there are a certain number of elements matched by the given locator on the page. * * ``` php * seeNumberOfElements('tr', 10); * $I->seeNumberOfElements('tr', [0,10]); //between 0 and 10 elements * ?> * ``` * @param $selector * @param mixed $expected : * - string: strict number * - array: range of numbers [0,10] */ public function seeNumberOfElements($selector, $expected); /** * Checks that the given option is selected. * * ``` php * seeOptionIsSelected('#form input[name=payment]', 'Visa'); * ?> * ``` * * @param $selector * @param $optionText * * @return mixed */ public function seeOptionIsSelected($selector, $optionText); /** * Checks that the given option is not selected. * * ``` php * dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); * ?> * ``` * * @param $selector * @param $optionText * * @return mixed */ public function dontSeeOptionIsSelected($selector, $optionText); /** * Checks that the page title contains the given string. * * ``` php * seeInTitle('Blog - Post #1'); * ?> * ``` * * @param $title * * @return mixed */ public function seeInTitle($title); /** * Checks that the page title does not contain the given string. * * @param $title * * @return mixed */ public function dontSeeInTitle($title); /** * Checks that a cookie with the given name is set. * You can set additional cookie params like `domain`, `path` as array passed in last argument. * * ``` php * seeCookie('PHPSESSID'); * ?> * ``` * * @param $cookie * @param array $params * @return mixed */ public function seeCookie($cookie, array $params = []); /** * Checks that there isn't a cookie with the given name. * You can set additional cookie params like `domain`, `path` as array passed in last argument. * * @param $cookie * * @param array $params * @return mixed */ public function dontSeeCookie($cookie, array $params = []); /** * Sets a cookie with the given name and value. * You can set additional cookie params like `domain`, `path`, `expires`, `secure` in array passed as last argument. * * ``` php * setCookie('PHPSESSID', 'el4ukv0kqbvoirg7nkp4dncpk3'); * ?> * ``` * * @param $name * @param $val * @param array $params * * @return mixed */ public function setCookie($name, $val, array $params = []); /** * Unsets cookie with the given name. * You can set additional cookie params like `domain`, `path` in array passed as last argument. * * @param $cookie * * @param array $params * @return mixed */ public function resetCookie($cookie, array $params = []); /** * Grabs a cookie value. * You can set additional cookie params like `domain`, `path` in array passed as last argument. * * @param $cookie * * @param array $params * @return mixed */ public function grabCookie($cookie, array $params = []); /** * Grabs current page source code. * * @return string Current page source code. */ public function grabPageSource(); } errorMessage * ] * @return mixed */ public function _depends(); } getModule('{{MODULE_NAME}}')->_saveScreenshot(codecept_output_dir().'screenshot_1.png'); * ``` * @api * @param $filename */ public function _saveScreenshot($filename); } seeInDatabase('users', array('name' => 'Davert', 'email' => 'davert@mail.com')); * ``` * Fails if no such user found. * * @param string $table * @param array $criteria */ public function seeInDatabase($table, $criteria = []); /** * Effect is opposite to ->seeInDatabase * * Asserts that there is no record with the given column values in a database. * Provide table name and column values. * * ``` php * dontSeeInDatabase('users', array('name' => 'Davert', 'email' => 'davert@mail.com')); * ``` * Fails if such user was found. * * @param string $table * @param array $criteria */ public function dontSeeInDatabase($table, $criteria = []); /** * Fetches a single column value from a database. * Provide table name, desired column and criteria. * * ``` php * grabFromDatabase('users', 'email', array('name' => 'Davert')); * ``` * * @param string $table * @param string $column * @param array $criteria * * @return mixed */ public function grabFromDatabase($table, $column, $criteria = []); } fallback = $fallback; } public function get($className) { // normalize namespace $className = ltrim($className, '\\'); return isset($this->container[$className]) ? $this->container[$className] : null; } public function set($class) { $this->container[get_class($class)] = $class; } /** * @param string $className * @param array $constructorArgs * @param string $injectMethodName Method which will be invoked after object creation; * Resolved dependencies will be passed to it as arguments * @throws InjectionException * @return null|object */ public function instantiate( $className, $constructorArgs = null, $injectMethodName = self::DEFAULT_INJECT_METHOD_NAME ) { // normalize namespace $className = ltrim($className, '\\'); // get class from container if (isset($this->container[$className])) { if ($this->container[$className] instanceof $className) { return $this->container[$className]; } else { throw new InjectionException("Failed to resolve cyclic dependencies for class '$className'"); } } // get class from parent container if ($this->fallback) { if ($class = $this->fallback->get($className)) { return $class; } } $this->container[$className] = false; // flag that object is being instantiated $reflectedClass = new \ReflectionClass($className); if (!$reflectedClass->isInstantiable()) { return null; } $reflectedConstructor = $reflectedClass->getConstructor(); if (is_null($reflectedConstructor)) { $object = new $className; } else { try { if (!$constructorArgs) { $constructorArgs = $this->prepareArgs($reflectedConstructor); } } catch (\Exception $e) { throw new InjectionException("Failed to create instance of '$className'. " . $e->getMessage()); } $object = $reflectedClass->newInstanceArgs($constructorArgs); } if ($injectMethodName) { $this->injectDependencies($object, $injectMethodName); } $this->container[$className] = $object; return $object; } /** * @param $object * @param string $injectMethodName Method which will be invoked with resolved dependencies as its arguments * @throws InjectionException */ public function injectDependencies($object, $injectMethodName = self::DEFAULT_INJECT_METHOD_NAME, $defaults = []) { if (!is_object($object)) { return; } $reflectedObject = new \ReflectionObject($object); if (!$reflectedObject->hasMethod($injectMethodName)) { return; } $reflectedMethod = $reflectedObject->getMethod($injectMethodName); try { $args = $this->prepareArgs($reflectedMethod, $defaults); } catch (\Exception $e) { $msg = $e->getMessage(); if ($e->getPrevious()) { // injection failed because PHP code is invalid. See #3869 $msg .= '; '. $e->getPrevious(); } throw new InjectionException( "Failed to inject dependencies in instance of '{$reflectedObject->name}'. $msg" ); } if (!$reflectedMethod->isPublic()) { $reflectedMethod->setAccessible(true); } $reflectedMethod->invokeArgs($object, $args); } /** * @param \ReflectionMethod $method * @param $defaults * @throws InjectionException * @return array */ protected function prepareArgs(\ReflectionMethod $method, $defaults = []) { $args = []; $parameters = $method->getParameters(); foreach ($parameters as $k => $parameter) { $dependency = $parameter->getClass(); if (is_null($dependency)) { if (!$parameter->isOptional()) { if (!isset($defaults[$k])) { throw new InjectionException("Parameter '$parameter->name' must have default value."); } $args[] = $defaults[$k]; continue; } $args[] = $parameter->getDefaultValue(); } else { $arg = $this->instantiate($dependency->name); if (is_null($arg)) { throw new InjectionException("Failed to resolve dependency '{$dependency->name}'."); } $args[] = $arg; } } return $args; } } haveBinding('My\Interface', 'My\Implementation'); * ?> * ``` * * @param $abstract * @param $concrete */ public function haveBinding($abstract, $concrete) { $this->client->haveBinding($abstract, $concrete); } /** * Add a singleton binding to the Laravel service container. * (https://laravel.com/docs/master/container) * * ``` php * haveSingleton('My\Interface', 'My\Singleton'); * ?> * ``` * * @param $abstract * @param $concrete */ public function haveSingleton($abstract, $concrete) { $this->client->haveBinding($abstract, $concrete, true); } /** * Add a contextual binding to the Laravel service container. * (https://laravel.com/docs/master/container) * * ``` php * haveContextualBinding('My\Class', '$variable', 'value'); * * // This is similar to the following in your Laravel application * $app->when('My\Class') * ->needs('$variable') * ->give('value'); * ?> * ``` * * @param $concrete * @param $abstract * @param $implementation */ public function haveContextualBinding($concrete, $abstract, $implementation) { $this->client->haveContextualBinding($concrete, $abstract, $implementation); } /** * Add an instance binding to the Laravel service container. * (https://laravel.com/docs/master/container) * * ``` php * haveInstance('My\Class', new My\Class()); * ?> * ``` * * @param $abstract * @param $instance */ public function haveInstance($abstract, $instance) { $this->client->haveInstance($abstract, $instance); } /** * Register a handler than can be used to modify the Laravel application object after it is initialized. * The Laravel application object will be passed as an argument to the handler. * * ``` php * haveApplicationHandler(function($app) { * $app->make('config')->set(['test_value' => '10']); * }); * ?> * ``` * * @param $handler */ public function haveApplicationHandler($handler) { $this->client->haveApplicationHandler($handler); } /** * Clear the registered application handlers. * * ``` php * clearApplicationHandlers(); * ?> * ``` * */ public function clearApplicationHandlers() { $this->client->clearApplicationHandlers(); } } settings = $settings; $this->name = $this->removeSuffix($name, 'Test'); } public function produce() { $actor = $this->settings['actor']; if ($this->settings['namespace']) { $actor = $this->settings['namespace'] . '\\' . $actor; } $ns = $this->getNamespaceHeader($this->settings['namespace'] . '\\' . $this->name); return (new Template($this->template)) ->place('namespace', $ns) ->place('name', $this->getShortClassName($this->name)) ->place('actorClass', $actor) ->place('actor', lcfirst(Configuration::config()['actor_suffix'])) ->produce(); } } getPattern(); $path = $settings['path']; if (!empty($test)) { $path = $settings['path'].'/'.$test; if (preg_match($pattern, $test)) { $path = dirname($path); $pattern = basename($test); } } $finder = Finder::create() ->files() ->sortByName() ->in($path) ->followLinks() ->name($pattern); foreach ($finder as $file) { $pathname = str_replace("//", "/", $file->getPathname()); $loader->loadTests($pathname); } $availableSteps = $loader->getSteps(); $allSteps = []; foreach ($availableSteps as $stepGroup) { $allSteps = array_merge($allSteps, $stepGroup); } foreach ($loader->getTests() as $test) { /** @var $test \Codeception\Test\Gherkin **/ $steps = $test->getScenarioNode()->getSteps(); if ($test->getFeatureNode()->hasBackground()) { $steps = array_merge($steps, $test->getFeatureNode()->getBackground()->getSteps()); } foreach ($steps as $step) { $matched = false; $text = $step->getText(); if (self::stepHasPyStringArgument($step)) { // pretend it is inline argument $text .= ' ""'; } foreach (array_keys($allSteps) as $pattern) { if (preg_match($pattern, $text)) { $matched = true; break; } } if (!$matched) { $this->addSnippet($step); $file = str_ireplace($settings['path'], '', $test->getFeatureNode()->getFile()); if (!in_array($file, $this->features)) { $this->features[] = $file; } } } } } public function addSnippet(StepNode $step) { $args = []; $pattern = $step->getText(); // match numbers (not in quotes) if (preg_match_all('~([\d\.])(?=([^"]*"[^"]*")*[^"]*$)~', $pattern, $matches)) { foreach ($matches[1] as $num => $param) { $num++; $args[] = '$num' . $num; $pattern = str_replace($param, ":num$num", $pattern); } } // match quoted string if (preg_match_all('~"(.*?)"~', $pattern, $matches)) { foreach ($matches[1] as $num => $param) { $num++; $args[] = '$arg' . $num; $pattern = str_replace('"'.$param.'"', ":arg$num", $pattern); } } // Has multiline argument at the end of step? if (self::stepHasPyStringArgument($step)) { $num = count($args) + 1; $pattern .= " :arg$num"; $args[] = '$arg' . $num; } if (in_array($pattern, $this->processed)) { return; } $methodName = preg_replace('~(\s+?|\'|\"|\W)~', '', ucwords(preg_replace('~"(.*?)"|\d+~', '', $step->getText()))); if (empty($methodName)) { $methodName = 'step_' . substr(sha1($pattern), 0, 9); } $this->snippets[] = (new Template($this->template)) ->place('type', $step->getKeywordType()) ->place('text', $pattern) ->place('methodName', lcfirst($methodName)) ->place('params', implode(', ', $args)) ->produce(); $this->processed[] = $pattern; } public function getSnippets() { return $this->snippets; } public function getFeatures() { return $this->features; } public static function stepHasPyStringArgument(StepNode $step) { if ($step->hasArguments()) { $stepArgs = $step->getArguments(); if ($stepArgs[count($stepArgs) - 1]->getNodeType() == "PyString") { return true; } } return false; } } settings = $settings; $this->name = $name; $this->namespace = $this->getNamespaceString($this->settings['namespace'] . '\\Group\\' . $name); } public function produce() { $ns = $this->getNamespaceString($this->settings['namespace'] . '\\' . $this->name); return (new Template($this->template)) ->place('class', ucfirst($this->name)) ->place('name', $this->name) ->place('namespace', $this->namespace) ->place('groupName', strtolower($this->name)) ->produce(); } } {{actor}} = \$I; } EOF; protected $actions = ''; protected $settings; protected $name; protected $namespace; public function __construct($settings, $name) { $this->settings = $settings; $this->name = $this->getShortClassName($name); $this->namespace = $this->getNamespaceString($this->settings['namespace'] . '\\Page\\' . $name); } public function produce() { return (new Template($this->template)) ->place('namespace', $this->namespace) ->place('actions', $this->produceActions()) ->place('class', $this->name) ->produce(); } protected function produceActions() { if (!isset($this->settings['actor'])) { return ''; // global pageobject } $actor = lcfirst($this->settings['actor']); $actorClass = $this->settings['actor']; if (!empty($this->settings['namespace'])) { $actorClass = rtrim($this->settings['namespace'], '\\') . '\\' . $actorClass; } return (new Template($this->actionsTemplate)) ->place('actorClass', $actorClass) ->place('actor', $actor) ->place('pageObject', $this->name) ->produce(); } } settings = $settings; $this->name = $this->getShortClassName($name); $this->namespace = $this->getNamespaceString($this->settings['namespace'] . '\\Step\\' . $name); } public function produce() { $actor = $this->settings['actor']; if (!$actor) { throw new ConfigurationException("Steps can't be created for suite without an actor"); } $ns = $this->getNamespaceString($this->settings['namespace'] . '\\' . $actor . '\\' . $this->name); $ns = ltrim($ns, '\\'); $extended = '\\' . ltrim('\\' . $this->settings['namespace'] . '\\' . $actor, '\\'); return (new Template($this->template)) ->place('namespace', $this->namespace) ->place('name', $this->name) ->place('actorClass', $extended) ->place('actions', $this->actions) ->produce(); } public function createAction($action) { $this->actions .= (new Template($this->actionTemplate)) ->place('action', $action) ->produce(); } } wantTo('perform actions and see result'); EOF; protected $settings; public function __construct($settings) { $this->settings = $settings; } public function produce() { $actor = $this->settings['actor']; if (!$actor) { throw new ConfigurationException("Cept can't be created for suite without an actor. Add `actor: SomeTester` to suite config"); } $use = ''; if (! empty($this->settings['namespace'])) { $namespace = rtrim($this->settings['namespace'], '\\'); $use = "use {$namespace}\\$actor;"; } return (new Template($this->template)) ->place('actor', $actor) ->place('use', $use) ->produce(); } } name = $name; } public function produce() { return (new Template($this->template)) ->place('name', $this->name) ->produce(); } } settings = $settings; $this->di = new Di(); $this->moduleContainer = new ModuleContainer($this->di, $settings); $modules = Configuration::modules($this->settings); foreach ($modules as $moduleName) { $this->moduleContainer->create($moduleName); } $this->modules = $this->moduleContainer->all(); $this->actions = $this->moduleContainer->getActions(); } public function produce() { $namespace = rtrim($this->settings['namespace'], '\\'); if (!isset($this->settings['actor']) && isset($this->settings['class_name'])) { $this->settings['actor'] = $this->settings['class_name']; } return (new Template($this->template)) ->place('hasNamespace', $namespace ? "namespace $namespace;" : '') ->place('actor', $this->settings['actor']) ->place('inheritedMethods', $this->prependAbstractActorDocBlocks()) ->produce(); } protected function prependAbstractActorDocBlocks() { $inherited = []; $class = new \ReflectionClass('\Codeception\\Actor'); $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { if ($method->name == '__call') { continue; } // skipping magic if ($method->name == '__construct') { continue; } // skipping magic $returnType = 'void'; if ($method->name == 'haveFriend') { $returnType = '\Codeception\Lib\Friend'; } $params = $this->getParamsString($method); $inherited[] = (new Template($this->inheritedMethodTemplate)) ->place('method', $method->name) ->place('params', $params) ->place('return', $returnType) ->produce(); } return implode("\n", $inherited); } /** * @param \ReflectionMethod $refMethod * @return array */ protected function getParamsString(\ReflectionMethod $refMethod) { $params = []; foreach ($refMethod->getParameters() as $param) { if ($param->isOptional()) { $params[] = '$' . $param->name . ' = '.$this->getDefaultValue($param); } else { $params[] = '$' . $param->name; }; } return implode(', ', $params); } public function getActorName() { return $this->settings['actor']; } public function getModules() { return array_keys($this->modules); } /** * Infer default parameter from the reflection object and format it as PHP (code) string * * @param \ReflectionParameter $param * * @return string */ private function getDefaultValue(\ReflectionParameter $param) { if ($param->isDefaultValueAvailable()) { if (method_exists($param, 'isDefaultValueConstant') && $param->isDefaultValueConstant()) { $constName = $param->getDefaultValueConstantName(); if (false !== strpos($constName, '::')) { list($class, $const) = explode('::', $constName); if (in_array($class, ['self', 'static'])) { $constName = $param->getDeclaringClass()->getName().'::'.$const; } } return $constName; } return $this->phpEncodeValue($param->getDefaultValue()); } return 'null'; } /** * PHP encoded a value * * @param mixed $value * * @return string */ private function phpEncodeValue($value) { if (is_array($value)) { return $this->phpEncodeArray($value); } if (is_string($value)) { return json_encode($value); } return var_export($value, true); } /** * Recursively PHP encode an array * * @param array $array * * @return string */ private function phpEncodeArray(array $array) { $isPlainArray = function (array $value) { return ((count($value) === 0) || ( (array_keys($value) === range(0, count($value) - 1)) && (0 === count(array_filter(array_keys($value), 'is_string')))) ); }; if ($isPlainArray($array)) { return '['.implode(', ', array_map([$this, 'phpEncodeValue'], $array)).']'; } return '['.implode(', ', array_map(function ($key) use ($array) { return $this->phpEncodeValue($key).' => '.$this->phpEncodeValue($array[$key]); }, array_keys($array))).']'; } } name = $this->removeSuffix($className, 'Cest'); $this->settings = $settings; } public function produce() { $actor = $this->settings['actor']; if (!$actor) { throw new ConfigurationException("Cept can't be created for suite without an actor. Add `actor: SomeTester` to suite config"); } $namespace = rtrim($this->settings['namespace'], '\\'); $ns = $this->getNamespaceHeader($namespace.'\\'.$this->name); if ($ns) { $ns .= "use ".$this->settings['namespace'].'\\'.$actor.";"; } return (new Template($this->template)) ->place('name', $this->getShortClassName($this->name)) ->place('namespace', $ns) ->place('actor', $actor) ->produce(); } } getScenario()->runStep(new \Codeception\Step\{{step}}('{{method}}', func_get_args())); } EOF; protected $name; protected $settings; protected $modules = []; protected $actions; protected $numMethods = 0; public function __construct($settings) { $this->name = $settings['actor']; $this->settings = $settings; $this->di = new Di(); $modules = Configuration::modules($this->settings); $this->moduleContainer = new ModuleContainer($this->di, $settings); foreach ($modules as $moduleName) { $this->modules[$moduleName] = $this->moduleContainer->create($moduleName); } $this->actions = $this->moduleContainer->getActions(); } public function produce() { $namespace = rtrim($this->settings['namespace'], '\\'); $uses = []; foreach ($this->modules as $module) { $uses[] = "use " . get_class($module) . ";"; } $methods = []; $code = []; foreach ($this->actions as $action => $moduleName) { if (in_array($action, $methods)) { continue; } $class = new \ReflectionClass($this->modules[$moduleName]); $method = $class->getMethod($action); $code[] = $this->addMethod($method); $methods[] = $action; $this->numMethods++; } return (new Template($this->template)) ->place('namespace', $namespace ? $namespace . '\\' : '') ->place('hash', self::genHash($this->modules, $this->settings)) ->place('name', $this->name) ->place('use', implode("\n", $uses)) ->place('methods', implode("\n\n ", $code)) ->produce(); } protected function addMethod(\ReflectionMethod $refMethod) { $class = $refMethod->getDeclaringClass(); $params = $this->getParamsString($refMethod); $module = $class->getName(); $body = ''; $doc = $this->addDoc($class, $refMethod); $doc = str_replace('/**', '', $doc); $doc = trim(str_replace('*/', '', $doc)); if (!$doc) { $doc = "*"; } $conditionalDoc = $doc . "\n * Conditional Assertion: Test won't be stopped on fail"; $methodTemplate = (new Template($this->methodTemplate)) ->place('module', $module) ->place('method', $refMethod->name) ->place('params', $params); // generate conditional assertions if (0 === strpos($refMethod->name, 'see')) { $type = 'Assertion'; $body .= $methodTemplate ->place('doc', $conditionalDoc) ->place('action', 'can' . ucfirst($refMethod->name)) ->place('step', 'ConditionalAssertion') ->produce(); // generate negative assertion } elseif (0 === strpos($refMethod->name, 'dontSee')) { $type = 'Assertion'; $body .= $methodTemplate ->place('doc', $conditionalDoc) ->place('action', str_replace('dont', 'cant', $refMethod->name)) ->place('step', 'ConditionalAssertion') ->produce(); } elseif (0 === strpos($refMethod->name, 'am')) { $type = 'Condition'; } else { $type = 'Action'; } $body .= $methodTemplate ->place('doc', $doc) ->place('action', $refMethod->name) ->place('step', $type) ->produce(); return $body; } /** * @param \ReflectionMethod $refMethod * @return array */ protected function getParamsString(\ReflectionMethod $refMethod) { $params = []; foreach ($refMethod->getParameters() as $param) { if ($param->isOptional()) { $params[] = '$' . $param->name . ' = null'; } else { $params[] = '$' . $param->name; }; } return implode(', ', $params); } /** * @param \ReflectionClass $class * @param \ReflectionMethod $refMethod * @return string */ protected function addDoc(\ReflectionClass $class, \ReflectionMethod $refMethod) { $doc = $refMethod->getDocComment(); if (!$doc) { $interfaces = $class->getInterfaces(); foreach ($interfaces as $interface) { $i = new \ReflectionClass($interface->name); if ($i->hasMethod($refMethod->name)) { $doc = $i->getMethod($refMethod->name)->getDocComment(); break; } } } if (!$doc and $class->getParentClass()) { $parent = new \ReflectionClass($class->getParentClass()->name); if ($parent->hasMethod($refMethod->name)) { $doc = $parent->getMethod($refMethod->name)->getDocComment(); return $doc; } return $doc; } return $doc; } public static function genHash($modules, $settings) { $actions = []; foreach ($modules as $moduleName => $module) { $actions[$moduleName] = get_class_methods(get_class($module)); } return md5(Codecept::VERSION . serialize($actions) . serialize($settings['modules'])); } public function getNumMethods() { return $this->numMethods; } } namespace = $namespace; $this->name = $name; } public function produce() { return (new Template($this->template)) ->place('namespace', $this->getNamespaceHeader($this->namespace . '\\Helper\\' . $this->name)) ->place('name', $this->getShortClassName($this->name)) ->produce(); } public function getHelperName() { return rtrim('\\' . $this->namespace, '\\') . '\\Helper\\' . $this->name; } } $message $location"; } return $message; } public static function all() { $messages = self::$messages; self::$messages = []; return $messages; } } paramsFile = null; $this->paramStorage = $paramStorage; if (is_array($paramStorage)) { return $this->loadArray(); } if ($paramStorage === 'env' || $paramStorage === 'environment') { return $this->loadEnvironmentVars(); } $this->paramsFile = codecept_root_dir($paramStorage); if (!file_exists($this->paramsFile)) { throw new ConfigurationException("Params file {$this->paramsFile} not found"); } try { if (preg_match('~\.yml$~', $paramStorage)) { return $this->loadYamlFile(); } if (preg_match('~\.ini$~', $paramStorage)) { return $this->loadIniFile(); } if (preg_match('~\.php$~', $paramStorage)) { return $this->loadPhpFile(); } if (preg_match('~(\.env(\.|$))~', $paramStorage)) { return $this->loadDotEnvFile(); } } catch (\Exception $e) { throw new ConfigurationException("Failed loading params from $paramStorage\n" . $e->getMessage()); } throw new ConfigurationException("Params can't be loaded from `$paramStorage`."); } public function loadArray() { return $this->paramStorage; } protected function loadIniFile() { return parse_ini_file($this->paramsFile); } protected function loadPhpFile() { return require $this->paramsFile; } protected function loadYamlFile() { $params = Yaml::parse(file_get_contents($this->paramsFile)); if (isset($params['parameters'])) { // Symfony style $params = $params['parameters']; } return $params; } protected function loadDotEnvFile() { if (!class_exists('Dotenv\Dotenv')) { throw new ConfigurationException( "`vlucas/phpdotenv` library is required to parse .env files.\n" . "Please install it via composer: composer require vlucas/phpdotenv" ); } $dotEnv = new \Dotenv\Dotenv(codecept_root_dir(), $this->paramStorage); $dotEnv->load(); return $_ENV; } protected function loadEnvironmentVars() { return $_SERVER; } } config = $config; $command = $this->config['populator']; $this->builtCommand = $this->buildCommand((string) $command); } /** * Builds out a command replacing any found `$key` with its value if found in the given configuration. * * Process any $key found in the configuration array as a key of the array and replaces it with * the found value for the key. Example: * * ```php * 'Mauro']; * * // With the above parameters it will return `'Hello Mauro'`. * ``` * * @param string $command The command to be evaluated using the given config * @param array $config The configuration values used to replace any found $keys with values from this array. * @return string The resulting command string after evaluating any configuration's key */ protected function buildCommand($command) { $dsn = isset($this->config['dsn']) ? $this->config['dsn'] : ''; $dsnVars = []; $dsnWithoutDriver = preg_replace('/^[a-z]+:/i', '', $dsn); foreach (explode(';', $dsnWithoutDriver) as $item) { $keyValueTuple = explode('=', $item); if (count($keyValueTuple) > 1) { list($k, $v) = array_values($keyValueTuple); $dsnVars[$k] = $v; } } $vars = array_merge($dsnVars, $this->config); foreach ($vars as $key => $value) { $vars['$'.$key] = $value; unset($vars[$key]); } return str_replace(array_keys($vars), array_values($vars), $command); } /** * Executes the command built using the Db module configuration. * * Uses the PHP `exec` to spin off a child process for the built command. * * @return bool */ public function run() { $command = $this->getBuiltCommand(); codecept_debug("[Db] Executing Populator: `$command`"); exec($command, $output, $exitCode); if (0 !== $exitCode) { throw new \RuntimeException( "The populator command did not end successfully: \n" . " Exit code: $exitCode \n" . " Output:" . implode("\n", $output) ); } codecept_debug("[Db] Populator Finished."); return true; } public function getBuiltCommand() { return $this->builtCommand; } } di = $di; $this->di->set($this); $this->config = $config; } /** * Create a module. * * @param string $moduleName * @param bool $active * @return \Codeception\Module * @throws \Codeception\Exception\ConfigurationException * @throws \Codeception\Exception\ModuleException * @throws \Codeception\Exception\ModuleRequireException * @throws \Codeception\Exception\InjectionException */ public function create($moduleName, $active = true) { $this->active[$moduleName] = $active; $moduleClass = $this->getModuleClass($moduleName); if (!class_exists($moduleClass)) { throw new ConfigurationException("Module $moduleName could not be found and loaded"); } $config = $this->getModuleConfig($moduleName); if (empty($config) && !$active) { // For modules that are a dependency of other modules we want to skip the validation of the config. // This config validation is performed in \Codeception\Module::__construct(). // Explicitly setting $config to null skips this validation. $config = null; } $this->modules[$moduleName] = $module = $this->di->instantiate($moduleClass, [$this, $config], false); if ($this->moduleHasDependencies($module)) { $this->injectModuleDependencies($moduleName, $module); } // If module is not active its actions should not be included in the actor class $actions = $active ? $this->getActionsForModule($module, $config) : []; foreach ($actions as $action) { $this->actions[$action] = $moduleName; }; return $module; } /** * Does a module have dependencies? * * @param \Codeception\Module $module * @return bool */ private function moduleHasDependencies($module) { if (!$module instanceof DependsOnModule) { return false; } return (bool) $module->_depends(); } /** * Get the actions of a module. * * @param \Codeception\Module $module * @param array $config * @return array */ private function getActionsForModule($module, $config) { $reflectionClass = new \ReflectionClass($module); // Only public methods can be actions $methods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC); // Should this module be loaded partially? $configuredParts = null; if ($module instanceof PartedModule && isset($config['part'])) { $configuredParts = is_array($config['part']) ? $config['part'] : [$config['part']]; } $actions = []; foreach ($methods as $method) { if ($this->includeMethodAsAction($module, $method, $configuredParts)) { $actions[] = $method->name; } } return $actions; } /** * Should a method be included as an action? * * @param \Codeception\Module $module * @param \ReflectionMethod $method * @param array|null $configuredParts * @return bool */ private function includeMethodAsAction($module, $method, $configuredParts = null) { // Filter out excluded actions if (in_array($method->name, $module::$excludeActions)) { return false; } // Keep only the $onlyActions if they are specified if ($module::$onlyActions && !in_array($method->name, $module::$onlyActions)) { return false; } // Do not include inherited actions if the static $includeInheritedActions property is set to false. // However, if an inherited action is also specified in the static $onlyActions property // it should be included as an action. if (!$module::$includeInheritedActions && !in_array($method->name, $module::$onlyActions) && $method->getDeclaringClass()->getName() != get_class($module) ) { return false; } // Do not include hidden methods, methods with a name starting with an underscore if (strpos($method->name, '_') === 0) { return false; }; // If a part is configured for the module, only include actions from that part if ($configuredParts) { $moduleParts = Annotation::forMethod($module, $method->name)->fetchAll('part'); if (!array_uintersect($moduleParts, $configuredParts, 'strcasecmp')) { return false; } } return true; } /** * Is the module a helper? * * @param string $moduleName * @return bool */ private function isHelper($moduleName) { return strpos($moduleName, '\\') !== false; } /** * Get the fully qualified class name for a module. * * @param string $moduleName * @return string */ private function getModuleClass($moduleName) { if ($this->isHelper($moduleName)) { return $moduleName; } return self::MODULE_NAMESPACE . $moduleName; } /** * Is a module instantiated in this ModuleContainer? * * @param string $moduleName * @return bool */ public function hasModule($moduleName) { return isset($this->modules[$moduleName]); } /** * Get a module from this ModuleContainer. * * @param string $moduleName * @return \Codeception\Module * @throws \Codeception\Exception\ModuleException */ public function getModule($moduleName) { if (!$this->hasModule($moduleName)) { throw new ModuleException(__CLASS__, "Module $moduleName couldn't be connected"); } return $this->modules[$moduleName]; } /** * Get the module for an action. * * @param string $action * @return \Codeception\Module|null This method returns null if there is no module for $action */ public function moduleForAction($action) { if (!isset($this->actions[$action])) { return null; } return $this->modules[$this->actions[$action]]; } /** * Get all actions. * * @return array An array with actions as keys and module names as values. */ public function getActions() { return $this->actions; } /** * Get all modules. * * @return array An array with module names as keys and modules as values. */ public function all() { return $this->modules; } /** * Mock a module in this ModuleContainer. * * @param string $moduleName * @param object $mock */ public function mock($moduleName, $mock) { $this->modules[$moduleName] = $mock; } /** * Inject the dependencies of a module. * * @param string $moduleName * @param \Codeception\Lib\Interfaces\DependsOnModule $module * @throws \Codeception\Exception\ModuleException * @throws \Codeception\Exception\ModuleRequireException */ private function injectModuleDependencies($moduleName, DependsOnModule $module) { $this->checkForMissingDependencies($moduleName, $module); if (!method_exists($module, '_inject')) { throw new ModuleException($module, 'Module requires method _inject to be defined to accept dependencies'); } $dependencies = array_map(function ($dependency) { return $this->create($dependency, false); }, $this->getConfiguredDependencies($moduleName)); call_user_func_array([$module, '_inject'], $dependencies); } /** * Check for missing dependencies. * * @param string $moduleName * @param \Codeception\Lib\Interfaces\DependsOnModule $module * @throws \Codeception\Exception\ModuleException * @throws \Codeception\Exception\ModuleRequireException */ private function checkForMissingDependencies($moduleName, DependsOnModule $module) { $dependencies = $this->getModuleDependencies($module); $configuredDependenciesCount = count($this->getConfiguredDependencies($moduleName)); if ($configuredDependenciesCount < count($dependencies)) { $missingDependency = array_keys($dependencies)[$configuredDependenciesCount]; $message = sprintf( "\nThis module depends on %s\n\n\n%s", $missingDependency, $this->getErrorMessageForDependency($module, $missingDependency) ); throw new ModuleRequireException($moduleName, $message); } } /** * Get the dependencies of a module. * * @param \Codeception\Lib\Interfaces\DependsOnModule $module * @return array * @throws \Codeception\Exception\ModuleException */ private function getModuleDependencies(DependsOnModule $module) { $depends = $module->_depends(); if (!$depends) { return []; } if (!is_array($depends)) { $message = sprintf("Method _depends of module '%s' must return an array", get_class($module)); throw new ModuleException($module, $message); } return $depends; } /** * Get the configured dependencies for a module. * * @param string $moduleName * @return array */ private function getConfiguredDependencies($moduleName) { $config = $this->getModuleConfig($moduleName); if (!isset($config['depends'])) { return []; } return is_array($config['depends']) ? $config['depends'] : [$config['depends']]; } /** * Get the error message for a module dependency that is missing. * * @param \Codeception\Module $module * @param string $missingDependency * @return string */ private function getErrorMessageForDependency($module, $missingDependency) { $depends = $module->_depends(); return $depends[$missingDependency]; } /** * Get the configuration for a module. * * A module with name $moduleName can be configured at two paths in a configuration file: * - modules.config.$moduleName * - modules.enabled.$moduleName * * This method checks both locations for configuration. If there is configuration at both locations * this method merges them, where the configuration at modules.enabled.$moduleName takes precedence * over modules.config.$moduleName if the same parameters are configured at both locations. * * @param string $moduleName * @return array */ private function getModuleConfig($moduleName) { $config = isset($this->config['modules']['config'][$moduleName]) ? $this->config['modules']['config'][$moduleName] : []; if (!isset($this->config['modules']['enabled'])) { return $config; } if (!is_array($this->config['modules']['enabled'])) { return $config; } foreach ($this->config['modules']['enabled'] as $enabledModuleConfig) { if (!is_array($enabledModuleConfig)) { continue; } $enabledModuleName = key($enabledModuleConfig); if ($enabledModuleName === $moduleName) { return Configuration::mergeConfigs(reset($enabledModuleConfig), $config); } } return $config; } /** * Check if there are conflicting modules in this ModuleContainer. * * @throws \Codeception\Exception\ModuleConflictException */ public function validateConflicts() { $canConflict = []; foreach ($this->modules as $moduleName => $module) { $parted = $module instanceof PartedModule && $module->_getConfig('part'); if ($this->active[$moduleName] && !$parted) { $canConflict[] = $module; } } foreach ($canConflict as $module) { foreach ($canConflict as $otherModule) { $this->validateConflict($module, $otherModule); } } } /** * Check if the modules passed as arguments to this method conflict with each other. * * @param \Codeception\Module $module * @param \Codeception\Module $otherModule * @throws \Codeception\Exception\ModuleConflictException */ private function validateConflict($module, $otherModule) { if ($module === $otherModule || !$module instanceof ConflictsWithModule) { return; } $conflicts = $this->normalizeConflictSpecification($module->_conflicts()); if ($otherModule instanceof $conflicts) { throw new ModuleConflictException($module, $otherModule); } } /** * Normalize the return value of ConflictsWithModule::_conflicts() to a class name. * This is necessary because it can return a module name instead of the name of a class or interface. * * @param string $conflicts * @return string */ private function normalizeConflictSpecification($conflicts) { if (interface_exists($conflicts) || class_exists($conflicts)) { return $conflicts; } if ($this->hasModule($conflicts)) { return $this->getModule($conflicts); } return $conflicts; } } comment('I expect to ' . $prediction); } public function expect($prediction) { return $this->comment('I expect ' . $prediction); } public function amGoingTo($argumentation) { return $this->comment('I am going to ' . $argumentation); } public function am($role) { $role = trim($role); if (stripos('aeiou', $role[0]) !== false) { return $this->comment('As an ' . $role); } return $this->comment('As a ' . $role); } public function lookForwardTo($achieveValue) { return $this->comment('So that I ' . $achieveValue); } public function comment($description) { $this->getScenario()->comment($description); return $this; } } friends[$name])) { $actor = $actorClass === null ? $this : new $actorClass($this->getScenario()); $this->friends[$name] = new LibFriend($name, $actor, $this->getScenario()->current('modules')); } return $this->friends[$name]; } } scenario = $scenario; $this->metadata = $metadata; } public function prepareToRun($code) { $this->parseFeature($code); $this->parseScenarioOptions($code); } public function parseFeature($code) { $matches = []; $code = $this->stripComments($code); $res = preg_match("~\\\$I->wantTo\\(\s*?['\"](.*?)['\"]\s*?\\);~", $code, $matches); if ($res) { $this->scenario->setFeature($matches[1]); return; } $res = preg_match("~\\\$I->wantToTest\\(['\"](.*?)['\"]\\);~", $code, $matches); if ($res) { $this->scenario->setFeature("test " . $matches[1]); return; } } public function parseScenarioOptions($code) { $comments = $this->matchComments($code); $this->attachMetadata($comments); } public function attachMetadata($comments) { $this->metadata->setGroups(Annotation::fetchAllFromComment('group', $comments)); $this->metadata->setEnv(Annotation::fetchAllFromComment('env', $comments)); $this->metadata->setDependencies(Annotation::fetchAllFromComment('depends', $comments)); $this->metadata->setSkip($this->firstOrNull(Annotation::fetchAllFromComment('skip', $comments))); $this->metadata->setIncomplete($this->firstOrNull(Annotation::fetchAllFromComment('incomplete', $comments))); } private function firstOrNull($array) { if (empty($array)) { return null; } return (string)$array[0]; } public function parseSteps($code) { // parse per line $friends = []; $lines = explode("\n", $code); $isFriend = false; foreach ($lines as $line) { // friends if (preg_match("~\\\$I->haveFriend\((.*?)\);~", $line, $matches)) { $friends[] = trim($matches[1], '\'"'); } // friend's section start if (preg_match("~\\\$(.*?)->does\(~", $line, $matches)) { $friend = $matches[1]; if (!in_array($friend, $friends)) { continue; } $isFriend = true; $this->addCommentStep("\n----- $friend does -----"); continue; } // actions if (preg_match("~\\\$I->(.*)\((.*?)\);~", $line, $matches)) { $this->addStep($matches); } // friend's section ends if ($isFriend && strpos($line, '}') !== false) { $this->addCommentStep("-------- back to me\n"); $isFriend = false; } } } protected function addStep($matches) { list($m, $action, $params) = $matches; if (in_array($action, ['wantTo', 'wantToTest'])) { return; } $this->scenario->addStep(new Step\Action($action, explode(',', $params))); } protected function addCommentStep($comment) { $this->scenario->addStep(new \Codeception\Step\Comment($comment, [])); } public static function validate($file) { $config = Configuration::config(); if (empty($config['settings']['lint'])) { // lint disabled in config return; } if (!function_exists('exec')) { //exec function is disabled #3324 return; } exec("php -l " . escapeshellarg($file) . " 2>&1", $output, $code); if ($code !== 0) { throw new TestParseException($file, implode("\n", $output)); } } public static function load($file) { if (PHP_MAJOR_VERSION < 7) { self::validate($file); } try { self::includeFile($file); } catch (\ParseError $e) { throw new TestParseException($file, $e->getMessage()); } catch (\Exception $e) { // file is valid otherwise } } public static function getClassesFromFile($file) { $sourceCode = file_get_contents($file); $classes = []; $tokens = token_get_all($sourceCode); $tokenCount = count($tokens); $namespace = ''; for ($i = 0; $i < $tokenCount; $i++) { if ($tokens[$i][0] === T_NAMESPACE) { $namespace = ''; for ($j = $i + 1; $j < $tokenCount; $j++) { if ($tokens[$j][0] === T_STRING) { $namespace .= $tokens[$j][1] . '\\'; } else { if ($tokens[$j] === '{' || $tokens[$j] === ';') { break; } } } } if ($tokens[$i][0] === T_CLASS) { if (!isset($tokens[$i - 2])) { $classes[] = $namespace . $tokens[$i + 2][1]; continue; } if ($tokens[$i - 2][0] === T_NEW) { continue; } if ($tokens[$i - 1][0] === T_WHITESPACE and $tokens[$i - 2][0] === T_DOUBLE_COLON) { continue; } if ($tokens[$i - 1][0] === T_DOUBLE_COLON) { continue; } $classes[] = $namespace . $tokens[$i + 2][1]; } } return $classes; } /* * Include in different scope to prevent included file from affecting $file variable */ private static function includeFile($file) { include_once $file; } /** * @param $code * @return mixed */ protected function stripComments($code) { $code = preg_replace('~\/\/.*?$~m', '', $code); // remove inline comments $code = preg_replace('~\/*\*.*?\*\/~ms', '', $code); return $code; // remove block comment } protected function matchComments($code) { $matches = []; $comments = ''; $hasLineComment = preg_match_all('~\/\/(.*?)$~m', $code, $matches); if ($hasLineComment) { foreach ($matches[1] as $line) { $comments .= $line."\n"; } } $hasBlockComment = preg_match('~\/*\*(.*?)\*\/~ms', $code, $matches); if ($hasBlockComment) { $comments .= $matches[1]."\n"; } return $comments; } } $line"; break; case '-': $line = "$line"; break; } $colorizedMessage .= $line . "\n"; } return trim($colorizedMessage); } } true, 'verbosity' => self::VERBOSITY_NORMAL, 'interactive' => true ]; /** * @var \Symfony\Component\Console\Helper\FormatterHelper */ public $formatHelper; public $waitForDebugOutput = true; protected $isInteractive = false; public function __construct($config) { $this->config = array_merge($this->config, $config); // enable interactive output mode for CLI $this->isInteractive = $this->config['interactive'] && isset($_SERVER['TERM']) && php_sapi_name() == 'cli' && $_SERVER['TERM'] != 'linux'; $formatter = new OutputFormatter($this->config['colors']); $formatter->setStyle('default', new OutputFormatterStyle()); $formatter->setStyle('bold', new OutputFormatterStyle(null, null, ['bold'])); $formatter->setStyle('focus', new OutputFormatterStyle('magenta', null, ['bold'])); $formatter->setStyle('ok', new OutputFormatterStyle('green', null, ['bold'])); $formatter->setStyle('error', new OutputFormatterStyle('white', 'red', ['bold'])); $formatter->setStyle('fail', new OutputFormatterStyle('red', null, ['bold'])); $formatter->setStyle('pending', new OutputFormatterStyle('yellow', null, ['bold'])); $formatter->setStyle('debug', new OutputFormatterStyle('cyan')); $formatter->setStyle('comment', new OutputFormatterStyle('yellow')); $formatter->setStyle('info', new OutputFormatterStyle('green')); $this->formatHelper = new FormatterHelper(); parent::__construct($this->config['verbosity'], $this->config['colors'], $formatter); } public function isInteractive() { return $this->isInteractive; } protected function clean($message) { // clear json serialization $message = str_replace('\/', '/', $message); return $message; } public function debug($message) { $message = print_r($message, true); $message = str_replace("\n", "\n ", $message); $message = $this->clean($message); $message = OutputFormatter::escape($message); if ($this->waitForDebugOutput) { $this->writeln(''); $this->waitForDebugOutput = false; } $this->writeln(" $message"); } public function message($message) { $message = call_user_func_array('sprintf', func_get_args()); return new Message($message, $this); } public function exception(\Exception $e) { $class = get_class($e); $this->writeln(""); $this->writeln("(![ $class ]!)"); $this->writeln($e->getMessage()); $this->writeln(""); } public function notification($message) { $this->writeln("$message"); } } output = $output; $this->diffFactory = new DiffFactory(); $this->colorizer = new Colorizer(); } /** * @param string $text * @return Message */ public function message($text = '') { return new Message($text, $this->output); } /** * @param ComparisonFailure $failure * @return string */ public function prepareComparisonFailureMessage(ComparisonFailure $failure) { $diff = $this->diffFactory->createDiff($failure); if (!$diff) { return ''; } $diff = $this->colorizer->colorize($diff); return "\n- Expected | + Actual\n$diff"; } } message = $message; $this->output = $output; } public function with($param) { $args = array_merge([$this->message], func_get_args()); $this->message = call_user_func_array('sprintf', $args); return $this; } public function style($name) { $this->message = sprintf('<%s>%s', $name, $this->message, $name); return $this; } public function width($length, $char = ' ') { $message_length = $this->getLength(); if ($message_length < $length) { $this->message .= str_repeat($char, $length - $message_length); } return $this; } public function cut($length) { $this->message = mb_substr($this->message, 0, $length, 'utf-8'); return $this; } public function write($verbose = OutputInterface::VERBOSITY_NORMAL) { if ($verbose > $this->output->getVerbosity()) { return; } $this->output->write($this->message); } public function writeln($verbose = OutputInterface::VERBOSITY_NORMAL) { if ($verbose > $this->output->getVerbosity()) { return; } $this->output->writeln($this->message); } public function prepend($string) { if ($string instanceof Message) { $string = $string->getMessage(); } $this->message = $string . $this->message; return $this; } public function append($string) { if ($string instanceof Message) { $string = $string->getMessage(); } $this->message .= $string; return $this; } public function apply($func) { $this->message = call_user_func($func, $this->message); return $this; } public function center($char) { $this->message = $char . $this->message . $char; return $this; } /** * @return mixed */ public function getMessage() { return $this->message; } public function block($style) { $this->message = $this->output->formatHelper->formatBlock($this->message, $style, true); return $this; } public function getLength($includeTags = false) { return mb_strwidth($includeTags ? $this->message : strip_tags($this->message), 'utf-8'); } public static function ucfirst($text) { return mb_strtoupper(mb_substr($text, 0, 1, 'utf-8'), 'utf-8') . mb_substr($text, 1, null, 'utf-8'); } public function __toString() { return $this->message; } } getDiff($failure->getExpectedAsString(), $failure->getActualAsString()); if (!$diff) { return null; } return $diff; } /** * @param string $expected * @param string $actual * @return string */ private function getDiff($expected = '', $actual = '') { if (!$actual && !$expected) { return ''; } $differ = new Differ(''); return $differ->diff($expected, $actual); } } null, 'path' => '/', 'domain' => '', 'secure' => false]; protected $internalDomains = null; public function _failed(TestInterface $test, $fail) { if (!$this->client || !$this->client->getInternalResponse()) { return; } $filename = preg_replace('~\W~', '.', Descriptor::getTestSignature($test)); $filename = mb_strcut($filename, 0, 244, 'utf-8') . '.fail.html'; $this->_savePageSource($report = codecept_output_dir() . $filename); $test->getMetadata()->addReport('html', $report); } public function _after(TestInterface $test) { $this->client = null; $this->crawler = null; $this->forms = []; $this->headers = []; } public function _conflicts() { return 'Codeception\Lib\Interfaces\Web'; } public function _findElements($locator) { return $this->match($locator); } /** * Send custom request to a backend using method, uri, parameters, etc. * Use it in Helpers to create special request actions, like accessing API * Returns a string with response body. * * ```php * getModule('{{MODULE_NAME}}')->_request('POST', '/api/v1/users', ['name' => $name]); * $user = json_decode($userData); * return $user->id; * } * ?> * ``` * Does not load the response into the module so you can't interact with response page (click, fill forms). * To load arbitrary page for interaction, use `_loadPage` method. * * @api * @param $method * @param $uri * @param array $parameters * @param array $files * @param array $server * @param null $content * @return mixed|Crawler * @throws ExternalUrlException * @see `_loadPage` */ public function _request( $method, $uri, array $parameters = [], array $files = [], array $server = [], $content = null ) { $this->clientRequest($method, $uri, $parameters, $files, $server, $content, true); return $this->_getResponseContent(); } /** * Returns content of the last response * Use it in Helpers when you want to retrieve response of request performed by another module. * * ```php * assertContains($text, $this->getModule('{{MODULE_NAME}}')->_getResponseContent(), "response contains"); * } * ?> * ``` * * @api * @return string * @throws ModuleException */ public function _getResponseContent() { return (string)$this->getRunningClient()->getInternalResponse()->getContent(); } protected function clientRequest($method, $uri, array $parameters = [], array $files = [], array $server = [], $content = null, $changeHistory = true) { $this->debugSection("Request Headers", $this->headers); foreach ($this->headers as $header => $val) { // moved from REST module if (!$val) { continue; } $header = str_replace('-', '_', strtoupper($header)); $server["HTTP_$header"] = $val; // Issue #827 - symfony foundation requires 'CONTENT_TYPE' without HTTP_ if ($this instanceof Framework && $header === 'CONTENT_TYPE') { $server[$header] = $val; } } $server['REQUEST_TIME'] = time(); $server['REQUEST_TIME_FLOAT'] = microtime(true); if ($this instanceof Framework) { if (preg_match('#^(//|https?://(?!localhost))#', $uri)) { $hostname = parse_url($uri, PHP_URL_HOST); if (!$this->isInternalDomain($hostname)) { throw new ExternalUrlException(get_class($this) . " can't open external URL: " . $uri); } } if ($method !== 'GET' && $content === null && !empty($parameters)) { $content = http_build_query($parameters); } } if (!ReflectionHelper::readPrivateProperty($this->client, 'followRedirects')) { $result = $this->client->request($method, $uri, $parameters, $files, $server, $content, $changeHistory); $this->debugResponse($uri); return $result; } $maxRedirects = ReflectionHelper::readPrivateProperty($this->client, 'maxRedirects', 'Symfony\Component\BrowserKit\Client'); $this->client->followRedirects(false); $result = $this->client->request($method, $uri, $parameters, $files, $server, $content, $changeHistory); $this->debugResponse($uri); return $this->redirectIfNecessary($result, $maxRedirects, 0); } protected function isInternalDomain($domain) { if ($this->internalDomains === null) { $this->internalDomains = $this->getInternalDomains(); } foreach ($this->internalDomains as $pattern) { if (preg_match($pattern, $domain)) { return true; } } return false; } /** * Opens a page with arbitrary request parameters. * Useful for testing multi-step forms on a specific step. * * ```php * getModule('{{MODULE_NAME}}')->_loadPage('POST', '/checkout/step2', ['order' => $orderId]); * } * ?> * ``` * * @api * @param $method * @param $uri * @param array $parameters * @param array $files * @param array $server * @param null $content */ public function _loadPage( $method, $uri, array $parameters = [], array $files = [], array $server = [], $content = null ) { $this->crawler = $this->clientRequest($method, $uri, $parameters, $files, $server, $content); $this->forms = []; } /** * @return Crawler * @throws ModuleException */ private function getCrawler() { if (!$this->crawler) { throw new ModuleException($this, 'Crawler is null. Perhaps you forgot to call "amOnPage"?'); } return $this->crawler; } private function getRunningClient() { if ($this->client->getInternalRequest() === null) { throw new ModuleException( $this, "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it" ); } return $this->client; } public function _savePageSource($filename) { file_put_contents($filename, $this->_getResponseContent()); } /** * Authenticates user for HTTP_AUTH * * @param $username * @param $password */ public function amHttpAuthenticated($username, $password) { $this->client->setServerParameter('PHP_AUTH_USER', $username); $this->client->setServerParameter('PHP_AUTH_PW', $password); } /** * Sets the HTTP header to the passed value - which is used on * subsequent HTTP requests through PhpBrowser. * * Example: * ```php * setHeader('X-Requested-With', 'Codeception'); * $I->amOnPage('test-headers.php'); * ?> * ``` * * @param string $name the name of the request header * @param string $value the value to set it to for subsequent * requests */ public function haveHttpHeader($name, $value) { $name = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $name))))); $this->headers[$name] = $value; } /** * Deletes the header with the passed name. Subsequent requests * will not have the deleted header in its request. * * Example: * ```php * haveHttpHeader('X-Requested-With', 'Codeception'); * $I->amOnPage('test-headers.php'); * // ... * $I->deleteHeader('X-Requested-With'); * $I->amOnPage('some-other-page.php'); * ?> * ``` * * @param string $name the name of the header to delete. */ public function deleteHeader($name) { $name = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $name))))); unset($this->headers[$name]); } public function amOnPage($page) { $this->_loadPage('GET', $page); } public function click($link, $context = null) { if ($context) { $this->crawler = $this->match($context); } if (is_array($link)) { $this->clickByLocator($link); return; } $anchor = $this->strictMatch(['link' => $link]); if (!count($anchor)) { $anchor = $this->getCrawler()->selectLink($link); } if (count($anchor)) { $this->openHrefFromDomNode($anchor->getNode(0)); return; } $buttonText = str_replace('"', "'", $link); $button = $this->crawler->selectButton($buttonText); if (count($button) && $this->clickButton($button->getNode(0))) { return; } try { $this->clickByLocator($link); } catch (MalformedLocatorException $e) { throw new ElementNotFound("name=$link", "'$link' is invalid CSS and XPath selector and Link or Button"); } } /** * @param $link * @return bool */ protected function clickByLocator($link) { $nodes = $this->match($link); if (!$nodes->count()) { throw new ElementNotFound($link, 'Link or Button by name or CSS or XPath'); } foreach ($nodes as $node) { $tag = $node->tagName; $type = $node->getAttribute('type'); if ($tag === 'a') { $this->openHrefFromDomNode($node); return true; } elseif (in_array($tag, ['input', 'button']) && in_array($type, ['submit', 'image'])) { return $this->clickButton($node); } } } /** * Clicks the link or submits the form when the button is clicked * @param \DOMNode $node * @return boolean clicked something */ private function clickButton(\DOMNode $node) { $formParams = []; $buttonName = (string)$node->getAttribute('name'); $buttonValue = $node->getAttribute('value'); if ($buttonName !== '' && $buttonValue !== null) { $formParams = [$buttonName => $buttonValue]; } while ($node->parentNode !== null) { $node = $node->parentNode; if (!isset($node->tagName)) { // this is the top most node, it has no parent either break; } if ($node->tagName === 'a') { $this->openHrefFromDomNode($node); return true; } elseif ($node->tagName === 'form') { $this->proceedSubmitForm( new Crawler($node, $this->getAbsoluteUrlFor($this->_getCurrentUri()), $this->getBaseUrl()), $formParams ); return true; } } codecept_debug('Button is not inside a link or a form'); return false; } private function openHrefFromDomNode(\DOMNode $node) { $link = new Link($node, $this->getBaseUrl()); $this->amOnPage(preg_replace('/#.*/', '', $link->getUri())); } private function getBaseUrl() { $baseUrl = ''; $baseHref = $this->crawler->filter('base'); if (count($baseHref) > 0) { $baseUrl = $baseHref->getNode(0)->getAttribute('href'); } if ($baseUrl == '') { $baseUrl = $this->_getCurrentUri(); } return $this->getAbsoluteUrlFor($baseUrl); } public function see($text, $selector = null) { if (!$selector) { $this->assertPageContains($text); return; } $nodes = $this->match($selector); $this->assertDomContains($nodes, $this->stringifySelector($selector), $text); } public function dontSee($text, $selector = null) { if (!$selector) { $this->assertPageNotContains($text); return; } $nodes = $this->match($selector); $this->assertDomNotContains($nodes, $this->stringifySelector($selector), $text); } public function seeInSource($raw) { $this->assertPageSourceContains($raw); } public function dontSeeInSource($raw) { $this->assertPageSourceNotContains($raw); } public function seeLink($text, $url = null) { $crawler = $this->getCrawler()->selectLink($text); if ($crawler->count() === 0) { $this->fail("No links containing text '$text' were found in page " . $this->_getCurrentUri()); } if ($url) { $crawler = $crawler->filterXPath(sprintf('.//a[contains(@href, %s)]', Crawler::xpathLiteral($url))); if ($crawler->count() === 0) { $this->fail("No links containing text '$text' and URL '$url' were found in page " . $this->_getCurrentUri()); } } } public function dontSeeLink($text, $url = null) { $crawler = $this->getCrawler()->selectLink($text); if (!$url) { if ($crawler->count() > 0) { $this->fail("Link containing text '$text' was found in page " . $this->_getCurrentUri()); } } $crawler = $crawler->filterXPath(sprintf('.//a[contains(@href, %s)]', Crawler::xpathLiteral($url))); if ($crawler->count() > 0) { $this->fail("Link containing text '$text' and URL '$url' was found in page " . $this->_getCurrentUri()); } } /** * @return string * @throws ModuleException */ public function _getCurrentUri() { return Uri::retrieveUri($this->getRunningClient()->getHistory()->current()->getUri()); } public function seeInCurrentUrl($uri) { $this->assertContains($uri, $this->_getCurrentUri()); } public function dontSeeInCurrentUrl($uri) { $this->assertNotContains($uri, $this->_getCurrentUri()); } public function seeCurrentUrlEquals($uri) { $this->assertEquals(rtrim($uri, '/'), rtrim($this->_getCurrentUri(), '/')); } public function dontSeeCurrentUrlEquals($uri) { $this->assertNotEquals(rtrim($uri, '/'), rtrim($this->_getCurrentUri(), '/')); } public function seeCurrentUrlMatches($uri) { \PHPUnit_Framework_Assert::assertRegExp($uri, $this->_getCurrentUri()); } public function dontSeeCurrentUrlMatches($uri) { \PHPUnit_Framework_Assert::assertNotRegExp($uri, $this->_getCurrentUri()); } public function grabFromCurrentUrl($uri = null) { if (!$uri) { return $this->_getCurrentUri(); } $matches = []; $res = preg_match($uri, $this->_getCurrentUri(), $matches); if (!$res) { $this->fail("Couldn't match $uri in " . $this->_getCurrentUri()); } if (!isset($matches[1])) { $this->fail("Nothing to grab. A regex parameter required. Ex: '/user/(\\d+)'"); } return $matches[1]; } public function seeCheckboxIsChecked($checkbox) { $checkboxes = $this->getFieldsByLabelOrCss($checkbox); $this->assertDomContains($checkboxes->filter('input[checked=checked]'), 'checkbox'); } public function dontSeeCheckboxIsChecked($checkbox) { $checkboxes = $this->getFieldsByLabelOrCss($checkbox); $this->assertEquals(0, $checkboxes->filter('input[checked=checked]')->count()); } public function seeInField($field, $value) { $nodes = $this->getFieldsByLabelOrCss($field); $this->assert($this->proceedSeeInField($nodes, $value)); } public function dontSeeInField($field, $value) { $nodes = $this->getFieldsByLabelOrCss($field); $this->assertNot($this->proceedSeeInField($nodes, $value)); } public function seeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, false); } public function dontSeeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, true); } protected function proceedSeeInFormFields($formSelector, array $params, $assertNot) { $form = $this->match($formSelector)->first(); if ($form->count() === 0) { throw new ElementNotFound($formSelector, 'Form'); } foreach ($params as $name => $values) { $field = $form->filterXPath(sprintf('.//*[@name=%s]', Crawler::xpathLiteral($name))); if ($field->count() === 0) { throw new ElementNotFound( sprintf('//*[@name=%s]', Crawler::xpathLiteral($name)), 'Form' ); } if (!is_array($values)) { $values = [$values]; } foreach ($values as $value) { $ret = $this->proceedSeeInField($field, $value); if ($assertNot) { $this->assertNot($ret); } else { $this->assert($ret); } } } } protected function proceedSeeInField(Crawler $fields, $value) { $testValues = $this->proceedGetValueFromField($fields); if (!is_array($testValues)) { $testValues = [$testValues]; } if (is_bool($value) && $value === true && !empty($testValues)) { $value = reset($testValues); } elseif (empty($testValues)) { $testValues = ['']; } return [ 'Contains', $value, $testValues, sprintf( 'Failed asserting that `%s` is in %s\'s value: %s', $value, $fields->getNode(0)->nodeName, var_export($testValues, true) ) ]; } /** * Strips out one pair of trailing square brackets from a field's * name. * * @param string $name the field name * @return string the name after stripping trailing square brackets */ protected function getSubmissionFormFieldName($name) { if (substr($name, -2) === '[]') { return substr($name, 0, -2); } return $name; } /** * Replaces boolean values in $params with the corresponding field's * value for checkbox form fields. * * The function loops over all input checkbox fields, checking if a * corresponding key is set in $params. If it is, and the value is * boolean or an array containing booleans, the value(s) are * replaced in the array with the real value of the checkbox, and * the array is returned. * * @param Crawler $form the form to find checkbox elements * @param array $params the parameters to be submitted * @return array the $params array after replacing bool values */ protected function setCheckboxBoolValues(Crawler $form, array $params) { $checkboxes = $form->filter('input[type=checkbox]'); $chFoundByName = []; foreach ($checkboxes as $box) { $fieldName = $this->getSubmissionFormFieldName($box->getAttribute('name')); $pos = (!isset($chFoundByName[$fieldName])) ? 0 : $chFoundByName[$fieldName]; $skip = (!isset($params[$fieldName])) || (!is_array($params[$fieldName]) && !is_bool($params[$fieldName])) || ($pos >= count($params[$fieldName]) || (is_array($params[$fieldName]) && !is_bool($params[$fieldName][$pos]))); if ($skip) { continue; } $values = $params[$fieldName]; if ($values === true) { $params[$fieldName] = $box->getAttribute('value'); $chFoundByName[$fieldName] = $pos + 1; } elseif ($values[$pos] === true) { $params[$fieldName][$pos] = $box->getAttribute('value'); $chFoundByName[$fieldName] = $pos + 1; } elseif (is_array($values)) { array_splice($params[$fieldName], $pos, 1); } else { unset($params[$fieldName]); } } return $params; } /** * Submits the form currently selected in the passed Crawler, after * setting any values passed in $params and setting the value of the * passed button name. * * @param Crawler $frmCrawl the form to submit * @param array $params additional parameter values to set on the * form * @param string $button the name of a submit button in the form */ protected function proceedSubmitForm(Crawler $frmCrawl, array $params, $button = null) { $form = $this->getFormFor($frmCrawl); $defaults = $this->getFormValuesFor($form); $merged = array_merge($defaults, $params); $requestParams = $this->setCheckboxBoolValues($frmCrawl, $merged); if (!empty($button)) { $btnCrawl = $frmCrawl->filterXPath(sprintf( '//*[not(@disabled) and @type="submit" and @name=%s]', Crawler::xpathLiteral($button) )); if (count($btnCrawl)) { $requestParams[$button] = $btnCrawl->attr('value'); } } $url = $this->getFormUrl($frmCrawl); if (strcasecmp($form->getMethod(), 'GET') === 0) { $url = Uri::mergeUrls($url, '?' . http_build_query($requestParams)); } $url = preg_replace('/#.*/', '', $url); $this->debugSection('Uri', $url); $this->debugSection('Method', $form->getMethod()); $this->debugSection('Parameters', $requestParams); $requestParams= $this->getFormPhpValues($requestParams); $this->crawler = $this->clientRequest( $form->getMethod(), $url, $requestParams, $form->getPhpFiles() ); $this->forms = []; } public function submitForm($selector, array $params, $button = null) { $form = $this->match($selector)->first(); if (!count($form)) { throw new ElementNotFound($this->stringifySelector($selector), 'Form'); } $this->proceedSubmitForm($form, $params, $button); } /** * Returns an absolute URL for the passed URI with the current URL * as the base path. * * @param string $uri the absolute or relative URI * @return string the absolute URL * @throws \Codeception\Exception\TestRuntimeException if either the current * URL or the passed URI can't be parsed */ protected function getAbsoluteUrlFor($uri) { $currentUrl = $this->getRunningClient()->getHistory()->current()->getUri(); if (empty($uri) || $uri[0] === '#') { return $currentUrl; } return Uri::mergeUrls($currentUrl, $uri); } /** * Returns the form action's absolute URL. * * @param \Symfony\Component\DomCrawler\Crawler $form * @return string * @throws \Codeception\Exception\TestRuntimeException if either the current * URL or the URI of the form's action can't be parsed */ protected function getFormUrl(Crawler $form) { $action = $form->form()->getUri(); return $this->getAbsoluteUrlFor($action); } /** * Returns a crawler Form object for the form pointed to by the * passed Crawler. * * The returned form is an independent Crawler created to take care * of the following issues currently experienced by Crawler's form * object: * - input fields disabled at a higher level (e.g. by a surrounding * fieldset) still return values * - Codeception expects an empty value to match an unselected * select box. * * The function clones the crawler's node and creates a new crawler * because it destroys or adds to the DOM for the form to achieve * the desired functionality. Other functions simply querying the * DOM wouldn't expect them. * * @param Crawler $form the form * @return Form */ private function getFormFromCrawler(Crawler $form) { $fakeDom = new \DOMDocument(); $fakeDom->appendChild($fakeDom->importNode($form->getNode(0), true)); $node = $fakeDom->documentElement; $action = (string)$this->getFormUrl($form); $cloned = new Crawler($node, $action, $this->getBaseUrl()); $shouldDisable = $cloned->filter( 'input:disabled:not([disabled]),select option:disabled,select optgroup:disabled option:not([disabled])' ); foreach ($shouldDisable as $field) { $field->parentNode->removeChild($field); } return $cloned->form(); } /** * Returns the DomCrawler\Form object for the form pointed to by * $node or its closes form parent. * * @param \Symfony\Component\DomCrawler\Crawler $node * @return \Symfony\Component\DomCrawler\Form */ protected function getFormFor(Crawler $node) { if (strcasecmp($node->first()->getNode(0)->tagName, 'form') === 0) { $form = $node->first(); } else { $form = $node->parents()->filter('form')->first(); } if (!$form) { $this->fail('The selected node is not a form and does not have a form ancestor.'); } $identifier = $form->attr('id') ?: $form->attr('action'); if (!isset($this->forms[$identifier])) { $this->forms[$identifier] = $this->getFormFromCrawler($form); } return $this->forms[$identifier]; } /** * Returns an array of name => value pairs for the passed form. * * For form fields containing a name ending in [], an array is * created out of all field values with the given name. * * @param \Symfony\Component\DomCrawler\Form the form * @return array an array of name => value pairs */ protected function getFormValuesFor(Form $form) { $values = []; $fields = $form->all(); foreach ($fields as $field) { if ($field->isDisabled() || !$field->hasValue() || $field instanceof FileFormField) { continue; } $fieldName = $this->getSubmissionFormFieldName($field->getName()); if (substr($field->getName(), -2) === '[]') { if (!isset($values[$fieldName])) { $values[$fieldName] = []; } $values[$fieldName][] = $field->getValue(); } else { $values[$fieldName] = $field->getValue(); } } return $values; } public function fillField($field, $value) { $input = $this->getFieldByLabelOrCss($field); $form = $this->getFormFor($input); $name = $input->attr('name'); $dynamicField = $input->getNode(0)->tagName == 'textarea' ? new TextareaFormField($input->getNode(0)) : new InputFormField($input->getNode(0)); $formField = $this->matchFormField($name, $form, $dynamicField); $formField->setValue($value); $input->getNode(0)->setAttribute('value', htmlspecialchars($value)); if ($input->getNode(0)->tagName == 'textarea') { $input->getNode(0)->nodeValue = htmlspecialchars($value); } } /** * @param $field * * @return \Symfony\Component\DomCrawler\Crawler */ protected function getFieldsByLabelOrCss($field) { if (is_array($field)) { $input = $this->strictMatch($field); if (!count($input)) { throw new ElementNotFound($field); } return $input; } // by label $label = $this->strictMatch(['xpath' => sprintf('.//label[descendant-or-self::node()[text()[normalize-space()=%s]]]', Crawler::xpathLiteral($field))]); if (count($label)) { $label = $label->first(); if ($label->attr('for')) { $input = $this->strictMatch(['id' => $label->attr('for')]); } else { $input = $this->strictMatch(['xpath' => sprintf('.//label[descendant-or-self::node()[text()[normalize-space()=%s]]]//input', Crawler::xpathLiteral($field))]); } } // by name if (!isset($input)) { $input = $this->strictMatch(['name' => $field]); } // by CSS and XPath if (!count($input)) { $input = $this->match($field); } if (!count($input)) { throw new ElementNotFound($field, 'Form field by Label or CSS'); } return $input; } protected function getFieldByLabelOrCss($field) { $input = $this->getFieldsByLabelOrCss($field); return $input->first(); } public function selectOption($select, $option) { $field = $this->getFieldByLabelOrCss($select); $form = $this->getFormFor($field); $fieldName = $this->getSubmissionFormFieldName($field->attr('name')); if (is_array($option)) { if (!isset($option[0])) { // strict option locator $form[$fieldName]->select($this->matchOption($field, $option)); codecept_debug($option); return; } $options = []; foreach ($option as $opt) { $options[] = $this->matchOption($field, $opt); } $form[$fieldName]->select($options); return; } $dynamicField = new ChoiceFormField($field->getNode(0)); $formField = $this->matchFormField($fieldName, $form, $dynamicField); $selValue = $this->matchOption($field, $option); if (is_array($formField)) { foreach ($formField as $field) { $values = $field->availableOptionValues(); foreach ($values as $val) { if ($val === $option) { $field->select($selValue); return; } } } return; } $formField->select($this->matchOption($field, $option)); } protected function matchOption(Crawler $field, $option) { if (isset($option['value'])) { return $option['value']; } if (isset($option['text'])) { $option = $option['text']; } $options = $field->filterXPath(sprintf('//option[text()=normalize-space("%s")]|//input[@type="radio" and @value=normalize-space("%s")]', $option, $option)); if ($options->count()) { if ($options->getNode(0)->tagName === 'option') { $options->getNode(0)->setAttribute('selected', 'selected'); } else { $options->getNode(0)->setAttribute('checked', 'checked'); } if ($options->first()->attr('value') !== false) { return $options->first()->attr('value'); } return $options->first()->text(); } return $option; } public function checkOption($option) { $this->proceedCheckOption($option)->tick(); } public function uncheckOption($option) { $this->proceedCheckOption($option)->untick(); } /** * @param $option * @return ChoiceFormField */ protected function proceedCheckOption($option) { $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($option)); $name = $field->attr('name'); if ($field->getNode(0) === null) { throw new TestRuntimeException("Form field $name is not located"); } // If the name is an array than we compare objects to find right checkbox $formField = $this->matchFormField($name, $form, new ChoiceFormField($field->getNode(0))); $field->getNode(0)->setAttribute('checked', 'checked'); if (!$formField instanceof ChoiceFormField) { throw new TestRuntimeException("Form field $name is not a checkable"); } return $formField; } public function attachFile($field, $filename) { $form = $this->getFormFor($field = $this->getFieldByLabelOrCss($field)); $filePath = codecept_data_dir() . $filename; if (!file_exists($filePath)) { throw new \InvalidArgumentException("File does not exist: $filePath"); } if (!is_readable($filePath)) { throw new \InvalidArgumentException("File is not readable: $filePath"); } $name = $field->attr('name'); $formField = $this->matchFormField($name, $form, new FileFormField($field->getNode(0))); if (is_array($formField)) { $this->fail("Field $name is ignored on upload, field $name is treated as array."); } $formField->upload($filePath); } /** * If your page triggers an ajax request, you can perform it manually. * This action sends a GET ajax request with specified params. * * See ->sendAjaxPostRequest for examples. * * @param $uri * @param $params */ public function sendAjaxGetRequest($uri, $params = []) { $this->sendAjaxRequest('GET', $uri, $params); } /** * If your page triggers an ajax request, you can perform it manually. * This action sends a POST ajax request with specified params. * Additional params can be passed as array. * * Example: * * Imagine that by clicking checkbox you trigger ajax request which updates user settings. * We emulate that click by running this ajax request manually. * * ``` php * sendAjaxPostRequest('/updateSettings', array('notifications' => true)); // POST * $I->sendAjaxGetRequest('/updateSettings', array('notifications' => true)); // GET * * ``` * * @param $uri * @param $params */ public function sendAjaxPostRequest($uri, $params = []) { $this->sendAjaxRequest('POST', $uri, $params); } /** * If your page triggers an ajax request, you can perform it manually. * This action sends an ajax request with specified method and params. * * Example: * * You need to perform an ajax request specifying the HTTP method. * * ``` php * sendAjaxRequest('PUT', '/posts/7', array('title' => 'new title')); * * ``` * * @param $method * @param $uri * @param $params */ public function sendAjaxRequest($method, $uri, $params = []) { $this->clientRequest($method, $uri, $params, [], ['HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'], null, false); } /** * @param $url */ protected function debugResponse($url) { $this->debugSection('Page', $url); $this->debugSection('Response', $this->getResponseStatusCode()); $this->debugSection('Request Cookies', $this->getRunningClient()->getInternalRequest()->getCookies()); $this->debugSection('Response Headers', $this->getRunningClient()->getInternalResponse()->getHeaders()); } protected function getResponseStatusCode() { // depending on Symfony version $response = $this->getRunningClient()->getInternalResponse(); if (method_exists($response, 'getStatus')) { return $response->getStatus(); } if (method_exists($response, 'getStatusCode')) { return $response->getStatusCode(); } return "N/A"; } /** * @param $selector * * @return Crawler */ protected function match($selector) { if (is_array($selector)) { return $this->strictMatch($selector); } if (Locator::isCSS($selector)) { return $this->getCrawler()->filter($selector); } if (Locator::isXPath($selector)) { return $this->getCrawler()->filterXPath($selector); } throw new MalformedLocatorException($selector, 'XPath or CSS'); } /** * @param array $by * @throws TestRuntimeException * @return Crawler */ protected function strictMatch(array $by) { $type = key($by); $locator = $by[$type]; switch ($type) { case 'id': return $this->filterByCSS("#$locator"); case 'name': return $this->filterByXPath(sprintf('.//*[@name=%s]', Crawler::xpathLiteral($locator))); case 'css': return $this->filterByCSS($locator); case 'xpath': return $this->filterByXPath($locator); case 'link': return $this->filterByXPath(sprintf('.//a[.=%s or contains(./@title, %s)]', Crawler::xpathLiteral($locator), Crawler::xpathLiteral($locator))); case 'class': return $this->filterByCSS(".$locator"); default: throw new TestRuntimeException( "Locator type '$by' is not defined. Use either: xpath, css, id, link, class, name" ); } } protected function filterByAttributes(Crawler $nodes, array $attributes) { foreach ($attributes as $attr => $val) { $nodes = $nodes->reduce( function (Crawler $node) use ($attr, $val) { return $node->attr($attr) == $val; } ); } return $nodes; } public function grabTextFrom($cssOrXPathOrRegex) { if (@preg_match($cssOrXPathOrRegex, $this->client->getInternalResponse()->getContent(), $matches)) { return $matches[1]; } $nodes = $this->match($cssOrXPathOrRegex); if ($nodes->count()) { return $nodes->first()->text(); } throw new ElementNotFound($cssOrXPathOrRegex, 'Element that matches CSS or XPath or Regex'); } public function grabAttributeFrom($cssOrXpath, $attribute) { $nodes = $this->match($cssOrXpath); if (!$nodes->count()) { throw new ElementNotFound($cssOrXpath, 'Element that matches CSS or XPath'); } return $nodes->first()->attr($attribute); } public function grabMultiple($cssOrXpath, $attribute = null) { $result = []; $nodes = $this->match($cssOrXpath); foreach ($nodes as $node) { if ($attribute !== null) { $result[] = $node->getAttribute($attribute); } else { $result[] = $node->textContent; } } return $result; } /** * @param $field * * @return array|mixed|null|string */ public function grabValueFrom($field) { $nodes = $this->match($field); if (!$nodes->count()) { throw new ElementNotFound($field, 'Field'); } return $this->proceedGetValueFromField($nodes); } /** * @param Crawler $nodes * @return array|mixed|string */ protected function proceedGetValueFromField(Crawler $nodes) { $values = []; if ($nodes->filter('textarea')->count()) { return (new TextareaFormField($nodes->filter('textarea')->getNode(0)))->getValue(); } if ($nodes->filter('input')->count()) { $input = $nodes->filter('input'); if ($input->attr('type') == 'checkbox' or $input->attr('type') == 'radio') { $values = []; $input = $nodes->filter('input:checked'); foreach ($input as $checkbox) { $values[] = $checkbox->getAttribute('value'); } return $values; } return (new InputFormField($nodes->filter('input')->getNode(0)))->getValue(); } if ($nodes->filter('select')->count()) { $field = new ChoiceFormField($nodes->filter('select')->getNode(0)); $options = $nodes->filter('option[selected]'); foreach ($options as $option) { $values[] = $option->getAttribute('value'); } if (!$field->isMultiple()) { return reset($values); } return $values; } $this->fail("Element $nodes is not a form field or does not contain a form field"); } public function setCookie($name, $val, array $params = []) { $cookies = $this->client->getCookieJar(); $params = array_merge($this->defaultCookieParameters, $params); $expires = isset($params['expiry']) ? $params['expiry'] : null; // WebDriver compatibility $expires = isset($params['expires']) && !$expires ? $params['expires'] : null; $path = isset($params['path']) ? $params['path'] : null; $domain = isset($params['domain']) ? $params['domain'] : ''; $secure = isset($params['secure']) ? $params['secure'] : false; $httpOnly = isset($params['httpOnly']) ? $params['httpOnly'] : true; $encodedValue = isset($params['encodedValue']) ? $params['encodedValue'] : false; $cookies->set(new Cookie($name, $val, $expires, $path, $domain, $secure, $httpOnly, $encodedValue)); $this->debugCookieJar(); } public function grabCookie($cookie, array $params = []) { $params = array_merge($this->defaultCookieParameters, $params); $this->debugCookieJar(); $cookies = $this->getRunningClient()->getCookieJar()->get($cookie, $params['path'], $params['domain']); if (!$cookies) { return null; } return $cookies->getValue(); } /** * Grabs current page source code. * * @throws ModuleException if no page was opened. * * @return string Current page source code. */ public function grabPageSource() { return $this->_getResponseContent(); } public function seeCookie($cookie, array $params = []) { $params = array_merge($this->defaultCookieParameters, $params); $this->debugCookieJar(); $this->assertNotNull($this->client->getCookieJar()->get($cookie, $params['path'], $params['domain'])); } public function dontSeeCookie($cookie, array $params = []) { $params = array_merge($this->defaultCookieParameters, $params); $this->debugCookieJar(); $this->assertNull($this->client->getCookieJar()->get($cookie, $params['path'], $params['domain'])); } public function resetCookie($name, array $params = []) { $params = array_merge($this->defaultCookieParameters, $params); $this->client->getCookieJar()->expire($name, $params['path'], $params['domain']); $this->debugCookieJar(); } private function stringifySelector($selector) { if (is_array($selector)) { return trim(json_encode($selector), '{}'); } return $selector; } public function seeElement($selector, $attributes = []) { $nodes = $this->match($selector); $selector = $this->stringifySelector($selector); if (!empty($attributes)) { $nodes = $this->filterByAttributes($nodes, $attributes); $selector .= "' with attribute(s) '" . trim(json_encode($attributes), '{}'); } $this->assertDomContains($nodes, $selector); } public function dontSeeElement($selector, $attributes = []) { $nodes = $this->match($selector); $selector = $this->stringifySelector($selector); if (!empty($attributes)) { $nodes = $this->filterByAttributes($nodes, $attributes); $selector .= "' with attribute(s) '" . trim(json_encode($attributes), '{}'); } $this->assertDomNotContains($nodes, $selector); } public function seeNumberOfElements($selector, $expected) { $counted = count($this->match($selector)); if (is_array($expected)) { list($floor, $ceil) = $expected; $this->assertTrue( $floor <= $counted && $ceil >= $counted, 'Number of elements counted differs from expected range' ); } else { $this->assertEquals( $expected, $counted, 'Number of elements counted differs from expected number' ); } } public function seeOptionIsSelected($selector, $optionText) { $selected = $this->matchSelectedOption($selector); $this->assertDomContains($selected, 'selected option'); //If element is radio then we need to check value $value = $selected->getNode(0)->tagName == 'option' ? $selected->text() : $selected->getNode(0)->getAttribute('value'); $this->assertEquals($optionText, $value); } public function dontSeeOptionIsSelected($selector, $optionText) { $selected = $this->matchSelectedOption($selector); if (!$selected->count()) { $this->assertEquals(0, $selected->count()); return; } //If element is radio then we need to check value $value = $selected->getNode(0)->tagName == 'option' ? $selected->text() : $selected->getNode(0)->getAttribute('value'); $this->assertNotEquals($optionText, $value); } protected function matchSelectedOption($select) { $nodes = $this->getFieldsByLabelOrCss($select); $selectedOptions = $nodes->filter('option[selected],input:checked'); if ($selectedOptions->count() == 0) { $selectedOptions = $nodes->filter('option,input')->first(); } return $selectedOptions; } /** * Asserts that current page has 404 response status code. */ public function seePageNotFound() { $this->seeResponseCodeIs(404); } /** * Checks that response code is equal to value provided. * * ```php * seeResponseCodeIs(200); * * // recommended \Codeception\Util\HttpCode * $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); * ``` * * @param $code */ public function seeResponseCodeIs($code) { $failureMessage = sprintf( 'Expected HTTP Status Code: %s. Actual Status Code: %s', HttpCode::getDescription($code), HttpCode::getDescription($this->getResponseStatusCode()) ); $this->assertEquals($code, $this->getResponseStatusCode(), $failureMessage); } /** * Checks that response code is equal to value provided. * * ```php * dontSeeResponseCodeIs(200); * * // recommended \Codeception\Util\HttpCode * $I->dontSeeResponseCodeIs(\Codeception\Util\HttpCode::OK); * ``` * @param $code */ public function dontSeeResponseCodeIs($code) { $failureMessage = sprintf( 'Expected HTTP status code other than %s', HttpCode::getDescription($code) ); $this->assertNotEquals($code, $this->getResponseStatusCode(), $failureMessage); } public function seeInTitle($title) { $nodes = $this->getCrawler()->filter('title'); if (!$nodes->count()) { throw new ElementNotFound("", "Tag"); } $this->assertContains($title, $nodes->first()->text(), "page title contains $title"); } public function dontSeeInTitle($title) { $nodes = $this->getCrawler()->filter('title'); if (!$nodes->count()) { $this->assertTrue(true); return; } $this->assertNotContains($title, $nodes->first()->text(), "page title contains $title"); } protected function assertDomContains($nodes, $message, $text = '') { $constraint = new CrawlerConstraint($text, $this->_getCurrentUri()); $this->assertThat($nodes, $constraint, $message); } protected function assertDomNotContains($nodes, $message, $text = '') { $constraint = new CrawlerNotConstraint($text, $this->_getCurrentUri()); $this->assertThat($nodes, $constraint, $message); } protected function assertPageContains($needle, $message = '') { $constraint = new PageConstraint($needle, $this->_getCurrentUri()); $this->assertThat( $this->getNormalizedResponseContent(), $constraint, $message ); } protected function assertPageNotContains($needle, $message = '') { $constraint = new PageConstraint($needle, $this->_getCurrentUri()); $this->assertThatItsNot( $this->getNormalizedResponseContent(), $constraint, $message ); } protected function assertPageSourceContains($needle, $message = '') { $constraint = new PageConstraint($needle, $this->_getCurrentUri()); $this->assertThat( $this->_getResponseContent(), $constraint, $message ); } protected function assertPageSourceNotContains($needle, $message = '') { $constraint = new PageConstraint($needle, $this->_getCurrentUri()); $this->assertThatItsNot( $this->_getResponseContent(), $constraint, $message ); } /** * @param $name * @param $form * @param $dynamicField * @return FormField */ protected function matchFormField($name, $form, $dynamicField) { if (substr($name, -2) != '[]') { return $form[$name]; } $name = substr($name, 0, -2); /** @var $item \Symfony\Component\DomCrawler\Field\FormField */ foreach ($form[$name] as $item) { if ($item == $dynamicField) { return $item; } } throw new TestRuntimeException("None of form fields by {$name}[] were not matched"); } /** * @param $locator * @return Crawler */ protected function filterByCSS($locator) { if (!Locator::isCSS($locator)) { throw new MalformedLocatorException($locator, 'css'); } return $this->getCrawler()->filter($locator); } /** * @param $locator * @return Crawler */ protected function filterByXPath($locator) { if (!Locator::isXPath($locator)) { throw new MalformedLocatorException($locator, 'xpath'); } return $this->getCrawler()->filterXPath($locator); } /** * @param $requestParams * @return array */ protected function getFormPhpValues($requestParams) { foreach ($requestParams as $name => $value) { $qs = http_build_query([$name => $value], '', '&'); if (!empty($qs)) { parse_str($qs, $expandedValue); $varName = substr($name, 0, strlen(key($expandedValue))); $requestParams = array_replace_recursive($requestParams, [$varName => current($expandedValue)]); } } return $requestParams; } /** * @param $result * @return mixed */ protected function redirectIfNecessary($result, $maxRedirects, $redirectCount) { $locationHeader = $this->client->getInternalResponse()->getHeader('Location'); $statusCode = $this->getResponseStatusCode(); if ($locationHeader && $statusCode >= 300 && $statusCode < 400) { if ($redirectCount == $maxRedirects) { throw new \LogicException(sprintf( 'The maximum number (%d) of redirections was reached.', $maxRedirects )); } $this->debugSection('Redirecting to', $locationHeader); $result = $this->client->followRedirect(); $this->debugResponse($locationHeader); return $this->redirectIfNecessary($result, $maxRedirects, $redirectCount + 1); } $this->client->followRedirects(true); return $result; } /** * Clicks on a given link. * * @param Link $link A Link instance * @return Crawler * @deprecated No longer used by InnerBrowser, please use amOnPage instead */ protected function clientClick(Link $link) { if ($link instanceof Form) { return $this->proceedSubmitForm($link); } return $this->clientRequest($link->getMethod(), $link->getUri()); } /** * Switch to iframe or frame on the page. * * Example: * ``` html * <iframe name="another_frame" src="http://example.com"> * ``` * * ``` php * <?php * # switch to iframe * $I->switchToIframe("another_frame"); * ``` * * @param string $name */ public function switchToIframe($name) { $iframe = $this->match("iframe[name=$name]")->first(); if (!count($iframe)) { $iframe = $this->match("frame[name=$name]")->first(); } if (!count($iframe)) { throw new ElementNotFound("name=$name", 'Iframe'); } $uri = $iframe->getNode(0)->getAttribute('src'); $this->amOnPage($uri); } /** * Moves back in history. * * @param int $numberOfSteps (default value 1) */ public function moveBack($numberOfSteps = 1) { if (!is_int($numberOfSteps) || $numberOfSteps < 1) { throw new \InvalidArgumentException('numberOfSteps must be positive integer'); } try { $history = $this->getRunningClient()->getHistory(); for ($i = $numberOfSteps; $i > 0; $i--) { $request = $history->back(); } } catch (\LogicException $e) { throw new \InvalidArgumentException( sprintf( 'numberOfSteps is set to %d, but there are only %d previous steps in the history', $numberOfSteps, $numberOfSteps - $i ) ); } $this->_loadPage( $request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent() ); } protected function debugCookieJar() { $cookies = $this->client->getCookieJar()->all(); $cookieStrings = array_map('strval', $cookies); $this->debugSection('Cookie Jar', $cookieStrings); } protected function setCookiesFromOptions() { if (isset($this->config['cookies']) && is_array($this->config['cookies']) && !empty($this->config['cookies'])) { $domain = parse_url($this->config['url'], PHP_URL_HOST); $cookieJar = $this->client->getCookieJar(); foreach ($this->config['cookies'] as &$cookie) { if (!is_array($cookie) || !array_key_exists('Name', $cookie) || !array_key_exists('Value', $cookie)) { throw new \InvalidArgumentException('Cookies must have at least Name and Value attributes'); } if (!isset($cookie['Domain'])) { $cookie['Domain'] = $domain; } if (!isset($cookie['Expires'])) { $cookie['Expires'] = null; } if (!isset($cookie['Path'])) { $cookie['Path'] = '/'; } if (!isset($cookie['Secure'])) { $cookie['Secure'] = false; } if (!isset($cookie['HttpOnly'])) { $cookie['HttpOnly'] = false; } $cookieJar->set(new \Symfony\Component\BrowserKit\Cookie( $cookie['Name'], $cookie['Value'], $cookie['Expires'], $cookie['Path'], $cookie['Domain'], $cookie['Secure'], $cookie['HttpOnly'] )); } } } /** * @return string */ protected function getNormalizedResponseContent() { $content = $this->_getResponseContent(); $content = strip_tags($content); $content = html_entity_decode($content, ENT_QUOTES); $content = str_replace("\n", ' ', $content); $content = preg_replace('/\s{2,}/', ' ', $content); return $content; } } <?php namespace Codeception\Lib; use Codeception\Actor; use Codeception\Exception\TestRuntimeException; class Friend { protected $name; protected $actor; protected $data = []; protected $multiSessionModules = []; public function __construct($name, Actor $actor, $modules = []) { $this->name = $name; $this->actor = $actor; $this->multiSessionModules = array_filter($modules, function ($m) { return $m instanceof Interfaces\MultiSession; }); if (empty($this->multiSessionModules)) { throw new TestRuntimeException("No multisession modules used. Can't instantiate friend"); } } public function does($closure) { $currentUserData = []; foreach ($this->multiSessionModules as $module) { $name = $module->_getName(); $currentUserData[$name] = $module->_backupSession(); if (empty($this->data[$name])) { $module->_initializeSession(); $this->data[$name] = $module->_backupSession(); continue; } $module->_loadSession($this->data[$name]); }; $this->actor->comment(strtoupper("{$this->name} does ---")); $ret = $closure($this->actor); $this->actor->comment(strtoupper("--- {$this->name} finished")); foreach ($this->multiSessionModules as $module) { $name = $module->_getName(); $this->data[$name] = $module->_backupSession(); $module->_loadSession($currentUserData[$name]); }; return $ret; } public function isGoingTo($argumentation) { $this->actor->amGoingTo($argumentation); } public function expects($prediction) { $this->actor->expect($prediction); } public function expectsTo($prediction) { $this->actor->expectTo($prediction); } public function leave() { foreach ($this->multiSessionModules as $module) { if (isset($this->data[$module->_getName()])) { $module->_closeSession($this->data[$module->_getName()]); } } } } <?php namespace Codeception\Lib; use Codeception\Configuration; use Codeception\Test\Interfaces\Reported; use Codeception\Test\Descriptor; use Codeception\TestInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; /** * Loads information for groups from external sources (config, filesystem) */ class GroupManager { protected $configuredGroups; protected $testsInGroups = []; public function __construct(array $groups) { $this->configuredGroups = $groups; $this->loadGroupsByPattern(); $this->loadConfiguredGroupSettings(); } /** * proceeds group names with asterisk: * * ``` * "tests/_log/g_*" => [ * "tests/_log/group_1", * "tests/_log/group_2", * "tests/_log/group_3", * ] * ``` */ protected function loadGroupsByPattern() { foreach ($this->configuredGroups as $group => $pattern) { if (strpos($group, '*') === false) { continue; } $files = Finder::create()->files() ->name(basename($pattern)) ->sortByName() ->in(Configuration::projectDir().dirname($pattern)); $i = 1; foreach ($files as $file) { /** @var SplFileInfo $file * */ $this->configuredGroups[str_replace('*', $i, $group)] = dirname($pattern).DIRECTORY_SEPARATOR.$file->getRelativePathname(); $i++; } unset($this->configuredGroups[$group]); } } protected function loadConfiguredGroupSettings() { foreach ($this->configuredGroups as $group => $tests) { $this->testsInGroups[$group] = []; if (is_array($tests)) { foreach ($tests as $test) { $file = str_replace(['/', '\\'], [DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $test); $this->testsInGroups[$group][] = Configuration::projectDir() . $file; } } elseif (is_file(Configuration::projectDir() . $tests)) { $handle = @fopen(Configuration::projectDir() . $tests, "r"); if ($handle) { while (($test = fgets($handle, 4096)) !== false) { // if the current line is blank then we need to move to the next line // otherwise the current codeception directory becomes part of the group // which causes every single test to run if (trim($test) === '') { continue; } $file = trim(Configuration::projectDir() . $test); $file = str_replace(['/', '\\'], [DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $file); $this->testsInGroups[$group][] = $file; } fclose($handle); } } } } public function groupsForTest(\PHPUnit_Framework_Test $test) { $groups = []; $filename = Descriptor::getTestFileName($test); if ($test instanceof TestInterface) { $groups = $test->getMetadata()->getGroups(); } if ($test instanceof Reported) { $info = $test->getReportFields(); if (isset($info['class'])) { $groups = array_merge($groups, \PHPUnit_Util_Test::getGroups($info['class'], $info['name'])); } $filename = str_replace(['\\\\', '//'], ['\\', '/'], $info['file']); } if ($test instanceof \PHPUnit_Framework_TestCase) { $groups = array_merge($groups, \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName(false))); } if ($test instanceof \PHPUnit_Framework_TestSuite_DataProvider) { $firstTest = $test->testAt(0); if ($firstTest != false && $firstTest instanceof TestInterface) { $groups = array_merge($groups, $firstTest->getMetadata()->getGroups()); $filename = Descriptor::getTestFileName($firstTest); } } foreach ($this->testsInGroups as $group => $tests) { foreach ($tests as $testPattern) { if ($filename == $testPattern) { $groups[] = $group; } if (strpos($filename . ':' . $test->getName(false), $testPattern) === 0) { $groups[] = $group; } if ($test instanceof \PHPUnit_Framework_TestSuite_DataProvider) { $firstTest = $test->testAt(0); if ($firstTest != false && $firstTest instanceof TestInterface) { if (strpos($filename . ':' . $firstTest->getName(false), $testPattern) === 0) { $groups[] = $group; } } } } } return array_unique($groups); } } <?php namespace Codeception\Lib\Connector; use Codeception\Util\Uri; use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Cookie\CookieJar; use GuzzleHttp\Cookie\SetCookie; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Handler\CurlHandler; use GuzzleHttp\Handler\StreamHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Request as Psr7Request; use GuzzleHttp\Psr7\Response as Psr7Response; use GuzzleHttp\Psr7\Uri as Psr7Uri; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\Request as BrowserKitRequest; use Symfony\Component\BrowserKit\Request; use Symfony\Component\BrowserKit\Response as BrowserKitResponse; class Guzzle6 extends Client { protected $requestOptions = [ 'allow_redirects' => false, 'headers' => [], ]; protected $refreshMaxInterval = 0; /** @var \GuzzleHttp\Client */ protected $client; /** * Sets the maximum allowable timeout interval for a meta tag refresh to * automatically redirect a request. * * A meta tag detected with an interval equal to or greater than $seconds * would not result in a redirect. A meta tag without a specified interval * or one with a value less than $seconds would result in the client * automatically redirecting to the specified URL * * @param int $seconds Number of seconds */ public function setRefreshMaxInterval($seconds) { $this->refreshMaxInterval = $seconds; } public function setClient(GuzzleClient &$client) { $this->client = $client; } /** * Sets the request header to the passed value. The header will be * sent along with the next request. * * Passing an empty value clears the header, which is the equivelant * of calling deleteHeader. * * @param string $name the name of the header * @param string $value the value of the header */ public function setHeader($name, $value) { if (strval($value) === '') { $this->deleteHeader($name); } else { $this->requestOptions['headers'][$name] = $value; } } /** * Deletes the header with the passed name from the list of headers * that will be sent with the request. * * @param string $name the name of the header to delete. */ public function deleteHeader($name) { unset($this->requestOptions['headers'][$name]); } /** * @param string $username * @param string $password * @param string $type Default: 'basic' */ public function setAuth($username, $password, $type = 'basic') { if (!$username) { unset($this->requestOptions['auth']); return; } $this->requestOptions['auth'] = [$username, $password, $type]; } /** * Taken from Mink\BrowserKitDriver * * @param Response $response * * @return \Symfony\Component\BrowserKit\Response */ protected function createResponse(Psr7Response $response) { $body = (string) $response->getBody(); $headers = $response->getHeaders(); $contentType = null; if (isset($headers['Content-Type'])) { $contentType = reset($headers['Content-Type']); } if (!$contentType) { $contentType = 'text/html'; } if (strpos($contentType, 'charset=') === false) { if (preg_match('/\<meta[^\>]+charset *= *["\']?([a-zA-Z\-0-9]+)/i', $body, $matches)) { $contentType .= ';charset=' . $matches[1]; } $headers['Content-Type'] = [$contentType]; } $status = $response->getStatusCode(); if ($status < 300 || $status >= 400) { $matches = []; $matchesMeta = preg_match( '/\<meta[^\>]+http-equiv="refresh" content="\s*(\d*)\s*;\s*url=(.*?)"/i', $body, $matches ); if (!$matchesMeta && isset($headers['Refresh'])) { // match by header preg_match( '/^\s*(\d*)\s*;\s*url=(.*)/i', (string) reset($headers['Refresh']), $matches ); } if ((!empty($matches)) && (empty($matches[1]) || $matches[1] < $this->refreshMaxInterval)) { $uri = new Psr7Uri($this->getAbsoluteUri($matches[2])); $currentUri = new Psr7Uri($this->getHistory()->current()->getUri()); if ($uri->withFragment('') != $currentUri->withFragment('')) { $status = 302; $headers['Location'] = $matchesMeta ? htmlspecialchars_decode($uri) : (string)$uri; } } } return new BrowserKitResponse($body, $status, $headers); } public function getAbsoluteUri($uri) { $baseUri = $this->client->getConfig('base_uri'); if (strpos($uri, '://') === false && strpos($uri, '//') !== 0) { if (strpos($uri, '/') === 0) { $baseUriPath = $baseUri->getPath(); if (!empty($baseUriPath) && strpos($uri, $baseUriPath) === 0) { $uri = substr($uri, strlen($baseUriPath)); } return Uri::appendPath((string)$baseUri, $uri); } // relative url if (!$this->getHistory()->isEmpty()) { return Uri::mergeUrls((string)$this->getHistory()->current()->getUri(), $uri); } } return Uri::mergeUrls($baseUri, $uri); } protected function doRequest($request) { /** @var $request BrowserKitRequest **/ $guzzleRequest = new Psr7Request( $request->getMethod(), $request->getUri(), $this->extractHeaders($request), $request->getContent() ); $options = $this->requestOptions; $options['cookies'] = $this->extractCookies($guzzleRequest->getUri()->getHost()); $multipartData = $this->extractMultipartFormData($request); if (!empty($multipartData)) { $options['multipart'] = $multipartData; } $formData = $this->extractFormData($request); if (empty($multipartData) and $formData) { $options['form_params'] = $formData; } try { $response = $this->client->send($guzzleRequest, $options); } catch (RequestException $e) { if (!$e->hasResponse()) { throw $e; } $response = $e->getResponse(); } return $this->createResponse($response); } protected function extractHeaders(BrowserKitRequest $request) { $headers = []; $server = $request->getServer(); $contentHeaders = ['Content-Length' => true, 'Content-Md5' => true, 'Content-Type' => true]; foreach ($server as $header => $val) { $header = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $header))))); if (strpos($header, 'Http-') === 0) { $headers[substr($header, 5)] = $val; } elseif (isset($contentHeaders[$header])) { $headers[$header] = $val; } } return $headers; } protected function extractFormData(BrowserKitRequest $request) { if (!in_array(strtoupper($request->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) { return null; } // guessing if it is a form data $headers = $request->getServer(); if (isset($headers['HTTP_CONTENT_TYPE'])) { // not a form if ($headers['HTTP_CONTENT_TYPE'] !== 'application/x-www-form-urlencoded') { return null; } } if ($request->getContent() !== null) { return null; } return $request->getParameters(); } protected function extractMultipartFormData(Request $request) { if (!in_array(strtoupper($request->getMethod()), ['POST', 'PUT', 'PATCH'])) { return []; } $parts = $this->mapFiles($request->getFiles()); if (empty($parts)) { return []; } foreach ($request->getParameters() as $k => $v) { $parts = $this->formatMultipart($parts, $k, $v); } return $parts; } protected function formatMultipart($parts, $key, $value) { if (is_array($value)) { foreach ($value as $subKey => $subValue) { $parts = array_merge($this->formatMultipart([], $key."[$subKey]", $subValue), $parts); } return $parts; } $parts[] = ['name' => $key, 'contents' => (string) $value]; return $parts; } protected function mapFiles($requestFiles, $arrayName = '') { $files = []; foreach ($requestFiles as $name => $info) { if (!empty($arrayName)) { $name = $arrayName . '[' . $name . ']'; } if (is_array($info)) { if (isset($info['tmp_name'])) { if ($info['tmp_name']) { $handle = fopen($info['tmp_name'], 'r'); $filename = isset($info['name']) ? $info['name'] : null; $files[] = [ 'name' => $name, 'contents' => $handle, 'filename' => $filename ]; } } else { $files = array_merge($files, $this->mapFiles($info, $name)); } } else { $files[] = [ 'name' => $name, 'contents' => fopen($info, 'r') ]; } } return $files; } protected function extractCookies($host) { $jar = []; $cookies = $this->getCookieJar()->all(); foreach ($cookies as $cookie) { /** @var $cookie Cookie **/ $setCookie = SetCookie::fromString((string)$cookie); if (!$setCookie->getDomain()) { $setCookie->setDomain($host); } $jar[] = $setCookie; } return new CookieJar(false, $jar); } public static function createHandler($handler) { if ($handler === 'curl') { return HandlerStack::create(new CurlHandler()); } if ($handler === 'stream') { return HandlerStack::create(new StreamHandler()); } if (class_exists($handler)) { return HandlerStack::create(new $handler); } if (is_callable($handler)) { return HandlerStack::create($handler); } return HandlerStack::create(); } } <?php namespace Codeception\Lib\Connector\Shared; /** * Converts BrowserKit\Request's request parameters and files into PHP-compatible structure * * @see https://bugs.php.net/bug.php?id=25589 * @see https://bugs.php.net/bug.php?id=25589 * * @package Codeception\Lib\Connector */ trait PhpSuperGlobalsConverter { /** * Rearrange files array to be compatible with PHP $_FILES superglobal structure * @see https://bugs.php.net/bug.php?id=25589 * * @param array $requestFiles * * @return array */ protected function remapFiles(array $requestFiles) { $files = $this->rearrangeFiles($requestFiles); return $this->replaceSpaces($files); } /** * Escape high-level variable name with dots, underscores and other "special" chars * to be compatible with PHP "bug" * @see https://bugs.php.net/bug.php?id=40000 * * @param array $parameters * * @return array */ protected function remapRequestParameters(array $parameters) { return $this->replaceSpaces($parameters); } private function rearrangeFiles($requestFiles) { $files = []; foreach ($requestFiles as $name => $info) { if (!is_array($info)) { continue; } /** * If we have a form with fields like * ``` * <input type="file" name="foo" /> * <input type="file" name="foo[bar]" /> * ``` * then only array variable will be used while simple variable will be ignored in php $_FILES * (eg $_FILES = [ * foo => [ * tmp_name => [ * 'bar' => 'asdf' * ], * //... * ] * ] * ) * (notice there is no entry for file "foo", only for file "foo[bar]" * this will check if current element contains inner arrays within it's keys * so we can ignore element itself and only process inner files */ $hasInnerArrays = count(array_filter($info, 'is_array')); if ($hasInnerArrays || !isset($info['tmp_name'])) { $inner = $this->remapFiles($info); foreach ($inner as $innerName => $innerInfo) { /** * Convert from ['a' => ['tmp_name' => '/tmp/test.txt'] ] * to ['tmp_name' => ['a' => '/tmp/test.txt'] ] */ $innerInfo = array_map( function ($v) use ($innerName) { return [$innerName => $v]; }, $innerInfo ); if (empty($files[$name])) { $files[$name] = []; } $files[$name] = array_replace_recursive($files[$name], $innerInfo); } } else { $files[$name] = $info; } } return $files; } /** * Replace spaces and dots and other chars in high-level query parameters for * compatibility with PHP bug (or not a bug) * @see https://bugs.php.net/bug.php?id=40000 * * @param array $parameters Array of request parameters to be converted * * @return array */ private function replaceSpaces($parameters) { $qs = http_build_query($parameters, '', '&'); parse_str($qs, $output); return $output; } } <?php namespace Codeception\Lib\Connector\Shared; /** * Common functions for Laravel family * * @package Codeception\Lib\Connector\Shared */ trait LaravelCommon { /** * @var array */ private $bindings = []; /** * @var array */ private $contextualBindings = []; /** * @var array */ private $instances = []; /** * @var array */ private $applicationHandlers = []; /** * Apply the registered application handlers. */ private function applyApplicationHandlers() { foreach ($this->applicationHandlers as $handler) { call_user_func($handler, $this->app); } } /** * Apply the registered Laravel service container bindings. */ private function applyBindings() { foreach ($this->bindings as $abstract => $binding) { list($concrete, $shared) = $binding; $this->app->bind($abstract, $concrete, $shared); } } /** * Apply the registered Laravel service container contextual bindings. */ private function applyContextualBindings() { foreach ($this->contextualBindings as $concrete => $bindings) { foreach ($bindings as $abstract => $implementation) { $this->app->addContextualBinding($concrete, $abstract, $implementation); } } } /** * Apply the registered Laravel service container instance bindings. */ private function applyInstances() { foreach ($this->instances as $abstract => $instance) { $this->app->instance($abstract, $instance); } } //====================================================================== // Public methods called by module //====================================================================== /** * Register a Laravel service container binding that should be applied * after initializing the Laravel Application object. * * @param $abstract * @param $concrete * @param bool $shared */ public function haveBinding($abstract, $concrete, $shared = false) { $this->bindings[$abstract] = [$concrete, $shared]; } /** * Register a Laravel service container contextual binding that should be applied * after initializing the Laravel Application object. * * @param $concrete * @param $abstract * @param $implementation */ public function haveContextualBinding($concrete, $abstract, $implementation) { if (! isset($this->contextualBindings[$concrete])) { $this->contextualBindings[$concrete] = []; } $this->contextualBindings[$concrete][$abstract] = $implementation; } /** * Register a Laravel service container instance binding that should be applied * after initializing the Laravel Application object. * * @param $abstract * @param $instance */ public function haveInstance($abstract, $instance) { $this->instances[$abstract] = $instance; } /** * Register a handler than can be used to modify the Laravel application object after it is initialized. * The Laravel application object will be passed as an argument to the handler. * * @param $handler */ public function haveApplicationHandler($handler) { $this->applicationHandlers[] = $handler; } /** * Clear the registered application handlers. */ public function clearApplicationHandlers() { $this->applicationHandlers = []; } } <?php namespace Codeception\Lib\Connector; class Symfony extends \Symfony\Component\HttpKernel\Client { /** * @var boolean */ private $rebootable = true; /** * @var boolean */ private $hasPerformedRequest = false; /** * @var \Symfony\Component\DependencyInjection\ContainerInterface */ private $container = null; /** * @var array */ public $persistentServices = []; /** * Constructor. * * @param \Symfony\Component\HttpKernel\Kernel $kernel A booted HttpKernel instance * @param array $services An injected services * @param boolean $rebootable */ public function __construct(\Symfony\Component\HttpKernel\Kernel $kernel, array $services = [], $rebootable = true) { parent::__construct($kernel); $this->followRedirects(true); $this->rebootable = (boolean)$rebootable; $this->persistentServices = $services; $this->rebootKernel(); } /** * @param \Symfony\Component\HttpFoundation\Request $request */ protected function doRequest($request) { if ($this->rebootable) { if ($this->hasPerformedRequest) { $this->rebootKernel(); } else { $this->hasPerformedRequest = true; } } return parent::doRequest($request); } /** * Reboot kernel * * Services from the list of persistent services * are updated from service container before kernel shutdown * and injected into newly initialized container after kernel boot. */ public function rebootKernel() { if ($this->container) { foreach ($this->persistentServices as $serviceName => $service) { if ($this->container->has($serviceName)) { $this->persistentServices[$serviceName] = $this->container->get($serviceName); } } } $this->kernel->shutdown(); $this->kernel->boot(); $this->container = $this->kernel->getContainer(); if ($this->container->has('profiler')) { $this->container->get('profiler')->enable(); } foreach ($this->persistentServices as $serviceName => $service) { $this->container->set($serviceName, $service); } } } <?php namespace Codeception\Lib\Connector; use Codeception\Exception\ModuleException; use Codeception\Lib\Connector\ZF2\PersistentServiceManager; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Request; use Symfony\Component\BrowserKit\Response; use Zend\Http\Request as HttpRequest; use Zend\Http\Headers as HttpHeaders; use Zend\Mvc\Application; use Zend\Stdlib\Parameters; use Zend\Uri\Http as HttpUri; use Symfony\Component\BrowserKit\Request as BrowserKitRequest; class ZF2 extends Client { /** * @var \Zend\Mvc\ApplicationInterface */ protected $application; /** * @var array */ protected $applicationConfig; /** * @var \Zend\Http\PhpEnvironment\Request */ protected $zendRequest; /** * @var PersistentServiceManager */ private $persistentServiceManager; /** * @param array $applicationConfig */ public function setApplicationConfig($applicationConfig) { $this->applicationConfig = $applicationConfig; $this->createApplication(); } /** * @param Request $request * * @return Response * @throws \Exception */ public function doRequest($request) { $this->createApplication(); $zendRequest = $this->application->getRequest(); $uri = new HttpUri($request->getUri()); $queryString = $uri->getQuery(); $method = strtoupper($request->getMethod()); $zendRequest->setCookies(new Parameters($request->getCookies())); $query = []; $post = []; $content = $request->getContent(); if ($queryString) { parse_str($queryString, $query); } if ($method !== HttpRequest::METHOD_GET) { $post = $request->getParameters(); } $zendRequest->setQuery(new Parameters($query)); $zendRequest->setPost(new Parameters($post)); $zendRequest->setFiles(new Parameters($request->getFiles())); $zendRequest->setContent($content); $zendRequest->setMethod($method); $zendRequest->setUri($uri); $requestUri = $uri->getPath(); if (!empty($queryString)) { $requestUri .= '?' . $queryString; } $zendRequest->setRequestUri($requestUri); $zendRequest->setHeaders($this->extractHeaders($request)); $this->application->run(); // get the response *after* the application has run, because other ZF // libraries like API Agility may *replace* the application's response // $zendResponse = $this->application->getResponse(); $this->zendRequest = $zendRequest; $exception = $this->application->getMvcEvent()->getParam('exception'); if ($exception instanceof \Exception) { throw $exception; } $response = new Response( $zendResponse->getBody(), $zendResponse->getStatusCode(), $zendResponse->getHeaders()->toArray() ); return $response; } /** * @return \Zend\Http\PhpEnvironment\Request */ public function getZendRequest() { return $this->zendRequest; } private function extractHeaders(BrowserKitRequest $request) { $headers = []; $server = $request->getServer(); $contentHeaders = array('Content-Length' => true, 'Content-Md5' => true, 'Content-Type' => true); foreach ($server as $header => $val) { $header = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $header))))); if (strpos($header, 'Http-') === 0) { $headers[substr($header, 5)] = $val; } elseif (isset($contentHeaders[$header])) { $headers[$header] = $val; } } $zendHeaders = new HttpHeaders(); $zendHeaders->addHeaders($headers); return $zendHeaders; } public function grabServiceFromContainer($service) { $serviceManager = $this->application->getServiceManager(); if (!$serviceManager->has($service)) { throw new \PHPUnit_Framework_AssertionFailedError("Service $service is not available in container"); } if ($service === 'Doctrine\ORM\EntityManager' && !isset($this->persistentServiceManager)) { if (!method_exists($serviceManager, 'addPeeringServiceManager')) { throw new ModuleException('Codeception\Module\ZF2', 'integration with Doctrine2 module is not compatible with ZF3'); } $this->persistentServiceManager = new PersistentServiceManager($serviceManager); } return $serviceManager->get($service); } public function addServiceToContainer($name, $service) { if (!isset($this->persistentServiceManager)) { $serviceManager = $this->application->getServiceManager(); if (!method_exists($serviceManager, 'addPeeringServiceManager')) { throw new ModuleException('Codeception\Module\ZF2', 'addServiceToContainer method is not compatible with ZF3'); } $this->persistentServiceManager = new PersistentServiceManager($serviceManager); $serviceManager->addPeeringServiceManager($this->persistentServiceManager); $serviceManager->setRetrieveFromPeeringManagerFirst(true); } $this->persistentServiceManager->setAllowOverride(true); $this->persistentServiceManager->setService($name, $service); $this->persistentServiceManager->setAllowOverride(false); } private function createApplication() { $this->application = Application::init($this->applicationConfig); $serviceManager = $this->application->getServiceManager(); if (isset($this->persistentServiceManager)) { $serviceManager->addPeeringServiceManager($this->persistentServiceManager); $serviceManager->setRetrieveFromPeeringManagerFirst(true); } $sendResponseListener = $serviceManager->get('SendResponseListener'); $events = $this->application->getEventManager(); if (class_exists('Zend\EventManager\StaticEventManager')) { $events->detach($sendResponseListener); //ZF2 } else { $events->detach([$sendResponseListener, 'sendResponse']); //ZF3 } } } <?php namespace Codeception\Lib\Connector\Lumen; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; /** * Dummy kernel to satisfy the parent constructor of the LumenConnector class. */ class DummyKernel implements HttpKernelInterface { public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) { // } } <?php namespace Codeception\Lib\Connector\Yii2; use yii\mail\BaseMailer; class TestMailer extends BaseMailer { public $messageClass = 'yii\swiftmailer\Message'; private $sentMessages = []; protected function sendMessage($message) { $this->sentMessages[] = $message; return true; } protected function saveMessage($message) { return $this->sendMessage($message); } public function getSentMessages() { return $this->sentMessages; } public function reset() { $this->sentMessages = []; } } <?php namespace Codeception\Lib\Connector\Yii2; use Codeception\Util\Debug; class Logger extends \yii\log\Logger { public function init() { // overridden to prevent register_shutdown_function } public function log($message, $level, $category = 'application') { if (!in_array($level, [ \yii\log\Logger::LEVEL_INFO, \yii\log\Logger::LEVEL_WARNING, \yii\log\Logger::LEVEL_ERROR, ])) { return; } if (strpos($category, 'yii\db\Command')===0) { return; // don't log queries } // https://github.com/Codeception/Codeception/issues/3696 if ($message instanceof \yii\base\Exception) { $message = $message->__toString(); } Debug::debug("[$category] " . \yii\helpers\VarDumper::export($message)); } } <?php namespace Codeception\Lib\Connector\Yii2; use yii\test\FixtureTrait; use yii\test\InitDbFixture; class FixturesStore { use FixtureTrait; protected $data; /** * Expects fixtures config * * FixturesStore constructor. * @param $data */ public function __construct($data) { $this->data = $data; } public function fixtures() { return $this->data; } public function globalFixtures() { return [ InitDbFixture::className() ]; } } <?php namespace Codeception\Lib\Connector\Phalcon; use Phalcon\Session\AdapterInterface; class MemorySession implements AdapterInterface { /** * @var string */ protected $sessionId; /** * @var string */ protected $name; /** * @var bool */ protected $started = false; /** * @var array */ protected $memory = []; /** * @var array */ protected $options = []; public function __construct(array $options = null) { $this->sessionId = $this->generateId(); if (is_array($options)) { $this->setOptions($options); } } /** * @inheritdoc */ public function start() { if ($this->status() !== PHP_SESSION_ACTIVE) { $this->memory = []; $this->started = true; return true; } return false; } /** * @inheritdoc * * @param array $options */ public function setOptions(array $options) { if (isset($options['uniqueId'])) { $this->sessionId = $options['uniqueId']; } $this->options = $options; } /** * @inheritdoc * * @return array */ public function getOptions() { return $this->options; } /** * @inheritdoc * * @param string $index * @param mixed $defaultValue * @param bool $remove * @return mixed */ public function get($index, $defaultValue = null, $remove = false) { $key = $this->prepareIndex($index); if (!isset($this->memory[$key])) { return $defaultValue; } $return = $this->memory[$key]; if ($remove) { unset($this->memory[$key]); } return $return; } /** * @inheritdoc * * @param string $index * @param mixed $value */ public function set($index, $value) { $this->memory[$this->prepareIndex($index)] = $value; } /** * @inheritdoc * * @param string $index * @return bool */ public function has($index) { return isset($this->memory[$this->prepareIndex($index)]); } /** * @inheritdoc * * @param string $index */ public function remove($index) { unset($this->memory[$this->prepareIndex($index)]); } /** * @inheritdoc * * @return string */ public function getId() { return $this->sessionId; } /** * @inheritdoc * * @return bool */ public function isStarted() { return $this->started; } /** * Returns the status of the current session * * ``` php * <?php * if ($session->status() !== PHP_SESSION_ACTIVE) { * $session->start(); * } * ?> * ``` * * @return int */ public function status() { if ($this->isStarted()) { return PHP_SESSION_ACTIVE; } return PHP_SESSION_NONE; } /** * @inheritdoc * * @param bool $removeData * @return bool */ public function destroy($removeData = false) { if ($removeData) { if (!empty($this->sessionId)) { foreach ($this->memory as $key => $value) { if (0 === strpos($key, $this->sessionId . '#')) { unset($this->memory[$key]); } } } else { $this->memory = []; } } $this->started = false; return true; } /** * @inheritdoc * * @param bool $deleteOldSession * @return \Phalcon\Session\AdapterInterface */ public function regenerateId($deleteOldSession = true) { $this->sessionId = $this->generateId(); return $this; } /** * @inheritdoc * * @param string $name */ public function setName($name) { $this->name = $name; } /** * @inheritdoc * * @return string */ public function getName() { return $this->name; } /** * Dump all session * * @return array */ public function toArray() { return (array) $this->memory; } /** * Alias: Gets a session variable from an application context * * @param string $index * @return mixed */ public function __get($index) { return $this->get($index); } /** * Alias: Sets a session variable in an application context * * @param string $index * @param mixed $value */ public function __set($index, $value) { $this->set($index, $value); } /** * Alias: Check whether a session variable is set in an application context * * @param string $index * @return bool */ public function __isset($index) { return $this->has($index); } /** * Alias: Removes a session variable from an application context * * @param string $index */ public function __unset($index) { $this->remove($index); } private function prepareIndex($index) { if ($this->sessionId) { $key = $this->sessionId . '#' . $index; } else { $key = $index; } return $key; } /** * @return string */ private function generateId() { return md5(time()); } } <?php namespace Codeception\Lib\Connector\ZendExpressive; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response\EmitterInterface; class ResponseCollector implements EmitterInterface { /** * @var ResponseInterface */ private $response; public function emit(ResponseInterface $response) { $this->response = $response; } public function getResponse() { if ($this->response === null) { throw new \LogicException('Response wasn\'t emitted yet'); } return $this->response; } public function clearResponse() { $this->response = null; } } <?php namespace Codeception\Lib\Connector; use Closure; use Phalcon\Di; use Phalcon\Http; use RuntimeException; use ReflectionProperty; use Codeception\Util\Stub; use Phalcon\Mvc\Application; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\Client; use Phalcon\Mvc\Micro as MicroApplication; use Symfony\Component\BrowserKit\Response; use Codeception\Lib\Connector\Shared\PhpSuperGlobalsConverter; class Phalcon extends Client { use PhpSuperGlobalsConverter; /** * Phalcon Application * @var mixed */ private $application; /** * Set Phalcon Application by \Phalcon\DI\Injectable, Closure or bootstrap file path * * @param mixed $application */ public function setApplication($application) { $this->application = $application; } /** * Get Phalcon Application * * @return Application|MicroApplication */ public function getApplication() { $application = $this->application; if ($application instanceof Closure) { return $application(); } elseif (is_string($application)) { /** @noinspection PhpIncludeInspection */ return require $application; } else { return $application; } } /** * Makes a request. * * @param \Symfony\Component\BrowserKit\Request $request * * @return \Symfony\Component\BrowserKit\Response * @throws \RuntimeException */ public function doRequest($request) { $application = $this->getApplication(); if (!$application instanceof Application && !$application instanceof MicroApplication) { throw new RuntimeException('Unsupported application class.'); } $di = $application->getDI(); /** @var Http\Request $phRequest */ if ($di->has('request')) { $phRequest = $di->get('request'); } if (!$phRequest instanceof Http\RequestInterface) { $phRequest = new Http\Request(); } $uri = $request->getUri() ?: $phRequest->getURI(); $pathString = parse_url($uri, PHP_URL_PATH); $queryString = parse_url($uri, PHP_URL_QUERY); $_SERVER = $request->getServer(); $_SERVER['REQUEST_METHOD'] = strtoupper($request->getMethod()); $_SERVER['REQUEST_URI'] = null === $queryString ? $pathString : $pathString . '?' . $queryString; $_COOKIE = $request->getCookies(); $_FILES = $this->remapFiles($request->getFiles()); $_REQUEST = $this->remapRequestParameters($request->getParameters()); $_POST = []; $_GET = []; if ($_SERVER['REQUEST_METHOD'] == 'GET') { $_GET = $_REQUEST; } else { $_POST = $_REQUEST; } parse_str($queryString, $output); foreach ($output as $k => $v) { $_GET[$k] = $v; } $_GET['_url'] = $pathString; $_SERVER['QUERY_STRING'] = http_build_query($_GET); Di::reset(); Di::setDefault($di); $di['request'] = Stub::construct($phRequest, [], ['getRawBody' => $request->getContent()]); $response = $application->handle(); if (!$response instanceof Http\ResponseInterface) { $response = $application->response; } $headers = $response->getHeaders(); $status = (int) $headers->get('Status'); $headersProperty = new ReflectionProperty($headers, '_headers'); $headersProperty->setAccessible(true); $headers = $headersProperty->getValue($headers); if (!is_array($headers)) { $headers = []; } $cookiesProperty = new ReflectionProperty($di['cookies'], '_cookies'); $cookiesProperty->setAccessible(true); $cookies = $cookiesProperty->getValue($di['cookies']); if (is_array($cookies)) { $restoredProperty = new ReflectionProperty('\Phalcon\Http\Cookie', '_restored'); $restoredProperty->setAccessible(true); $valueProperty = new ReflectionProperty('\Phalcon\Http\Cookie', '_value'); $valueProperty->setAccessible(true); foreach ($cookies as $name => $cookie) { if (!$restoredProperty->getValue($cookie)) { $clientCookie = new Cookie( $name, $valueProperty->getValue($cookie), $cookie->getExpiration(), $cookie->getPath(), $cookie->getDomain(), $cookie->getSecure(), $cookie->getHttpOnly() ); $headers['Set-Cookie'][] = (string)$clientCookie; } } } return new Response( $response->getContent(), $status ? $status : 200, $headers ); } } <?php namespace Codeception\Lib\Connector; use Codeception\Util\Uri; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Message\Response; use GuzzleHttp\Post\PostFile; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Response as BrowserKitResponse; use GuzzleHttp\Url; use Symfony\Component\BrowserKit\Request as BrowserKitRequest; class Guzzle extends Client { protected $baseUri; protected $requestOptions = [ 'allow_redirects' => false, 'headers' => [], ]; protected $refreshMaxInterval = 0; /** @var \GuzzleHttp\Client */ protected $client; public function setBaseUri($uri) { $this->baseUri = $uri; } /** * Sets the maximum allowable timeout interval for a meta tag refresh to * automatically redirect a request. * * A meta tag detected with an interval equal to or greater than $seconds * would not result in a redirect. A meta tag without a specified interval * or one with a value less than $seconds would result in the client * automatically redirecting to the specified URL * * @param int $seconds Number of seconds */ public function setRefreshMaxInterval($seconds) { $this->refreshMaxInterval = $seconds; } public function setClient(\GuzzleHttp\Client $client) { $this->client = $client; } /** * Sets the request header to the passed value. The header will be * sent along with the next request. * * Passing an empty value clears the header, which is the equivelant * of calling deleteHeader. * * @param string $name the name of the header * @param string $value the value of the header */ public function setHeader($name, $value) { if (strval($value) === '') { $this->deleteHeader($name); } else { $this->requestOptions['headers'][$name] = $value; } } /** * Deletes the header with the passed name from the list of headers * that will be sent with the request. * * @param string $name the name of the header to delete. */ public function deleteHeader($name) { unset($this->requestOptions['headers'][$name]); } /** * @param string $username * @param string $password * @param string $type Default: 'basic' */ public function setAuth($username, $password, $type = 'basic') { if (!$username) { unset($this->requestOptions['auth']); return; } $this->requestOptions['auth'] = [$username, $password, $type]; } /** * Taken from Mink\BrowserKitDriver * * @param Response $response * * @return \Symfony\Component\BrowserKit\Response */ protected function createResponse(Response $response) { $contentType = $response->getHeader('Content-Type'); if (!$contentType) { $contentType = 'text/html'; } if (strpos($contentType, 'charset=') === false) { $body = $response->getBody(true); if (preg_match('/\<meta[^\>]+charset *= *["\']?([a-zA-Z\-0-9]+)/i', $body, $matches)) { $contentType .= ';charset=' . $matches[1]; } $response->setHeader('Content-Type', $contentType); } $headers = $response->getHeaders(); $status = $response->getStatusCode(); if ($status < 300 || $status >= 400) { $matches = []; $matchesMeta = preg_match( '/\<meta[^\>]+http-equiv="refresh" content="\s*(\d*)\s*;\s*url=(.*?)"/i', $response->getBody(true), $matches ); if (!$matchesMeta) { // match by header preg_match( '/^\s*(\d*)\s*;\s*url=(.*)/i', (string)$response->getHeader('Refresh'), $matches ); } if ((!empty($matches)) && (empty($matches[1]) || $matches[1] < $this->refreshMaxInterval)) { $uri = $this->getAbsoluteUri($matches[2]); $partsUri = parse_url($uri); $partsCur = parse_url($this->getHistory()->current()->getUri()); foreach ($partsCur as $key => $part) { if ($key === 'fragment') { continue; } if (!isset($partsUri[$key]) || $partsUri[$key] !== $part) { $status = 302; $headers['Location'] = $matchesMeta ? htmlspecialchars_decode($uri) : $uri; break; } } } } return new BrowserKitResponse($response->getBody(), $status, $headers); } public function getAbsoluteUri($uri) { $baseUri = $this->baseUri; if (strpos($uri, '://') === false && strpos($uri, '//') !== 0) { if (strpos($uri, '/') === 0) { $baseUriPath = parse_url($baseUri, PHP_URL_PATH); if (!empty($baseUriPath) && strpos($uri, $baseUriPath) === 0) { $uri = substr($uri, strlen($baseUriPath)); } return Uri::appendPath((string)$baseUri, $uri); } // relative url if (!$this->getHistory()->isEmpty()) { return Uri::mergeUrls((string)$this->getHistory()->current()->getUri(), $uri); } } return Uri::mergeUrls($baseUri, $uri); } protected function doRequest($request) { /** @var $request BrowserKitRequest **/ $requestOptions = [ 'body' => $this->extractBody($request), 'cookies' => $this->extractCookies($request), 'headers' => $this->extractHeaders($request) ]; $requestOptions = array_replace_recursive($requestOptions, $this->requestOptions); $guzzleRequest = $this->client->createRequest( $request->getMethod(), $request->getUri(), $requestOptions ); foreach ($this->extractFiles($request) as $postFile) { $guzzleRequest->getBody()->addFile($postFile); } // Let BrowserKit handle redirects try { $response = $this->client->send($guzzleRequest); } catch (RequestException $e) { if ($e->hasResponse()) { $response = $e->getResponse(); } else { throw $e; } } return $this->createResponse($response); } protected function extractHeaders(BrowserKitRequest $request) { $headers = []; $server = $request->getServer(); $contentHeaders = ['Content-Length' => true, 'Content-Md5' => true, 'Content-Type' => true]; foreach ($server as $header => $val) { $header = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $header))))); if (strpos($header, 'Http-') === 0) { $headers[substr($header, 5)] = $val; } elseif (isset($contentHeaders[$header])) { $headers[$header] = $val; } } return $headers; } protected function extractBody(BrowserKitRequest $request) { if (in_array(strtoupper($request->getMethod()), ['GET', 'HEAD'])) { return null; } if ($request->getContent() !== null) { return $request->getContent(); } else { return $request->getParameters(); } } protected function extractFiles(BrowserKitRequest $request) { if (!in_array(strtoupper($request->getMethod()), ['POST', 'PUT'])) { return []; } return $this->mapFiles($request->getFiles()); } protected function mapFiles($requestFiles, $arrayName = '') { $files = []; foreach ($requestFiles as $name => $info) { if (!empty($arrayName)) { $name = $arrayName.'['.$name.']'; } if (is_array($info)) { if (isset($info['tmp_name'])) { if ($info['tmp_name']) { $handle = fopen($info['tmp_name'], 'r'); $filename = isset($info['name']) ? $info['name'] : null; $files[] = new PostFile($name, $handle, $filename); } } else { $files = array_merge($files, $this->mapFiles($info, $name)); } } else { $files[] = new PostFile($name, fopen($info, 'r')); } } return $files; } protected function extractCookies(BrowserKitRequest $request) { return $this->getCookieJar()->allRawValues($request->getUri()); } } <?php namespace Codeception\Lib\Connector; use Codeception\Lib\Connector\Yii2\Logger; use Codeception\Lib\Connector\Yii2\TestMailer; use Codeception\Util\Debug; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\Response; use Yii; use yii\base\ExitException; use yii\web\HttpException; use yii\web\Response as YiiResponse; class Yii2 extends Client { use Shared\PhpSuperGlobalsConverter; /** * @var string application config file */ public $configFile; public $defaultServerVars = []; /** * @var array */ public $headers; public $statusCode; /** * @var \yii\web\Application */ private $app; /** * @var \yii\db\Connection */ public static $db; // remember the db instance /** * @var TestMailer */ public static $mailer; /** * @return \yii\web\Application */ public function getApplication() { if (!isset($this->app)) { $this->startApp(); } return $this->app; } public function resetApplication() { $this->app = null; } public function startApp() { $config = require($this->configFile); if (!isset($config['class'])) { $config['class'] = 'yii\web\Application'; } /** @var \yii\web\Application $app */ $this->app = Yii::createObject($config); $this->persistDb(); $this->mockMailer($config); \Yii::setLogger(new Logger()); } public function resetPersistentVars() { static::$db = null; static::$mailer = null; \yii\web\UploadedFile::reset(); } /** * * @param \Symfony\Component\BrowserKit\Request $request * * @return \Symfony\Component\BrowserKit\Response */ public function doRequest($request) { $_COOKIE = $request->getCookies(); $_SERVER = $request->getServer(); $this->restoreServerVars(); $_FILES = $this->remapFiles($request->getFiles()); $_REQUEST = $this->remapRequestParameters($request->getParameters()); $_POST = $_GET = []; if (strtoupper($request->getMethod()) === 'GET') { $_GET = $_REQUEST; } else { $_POST = $_REQUEST; } $uri = $request->getUri(); $pathString = parse_url($uri, PHP_URL_PATH); $queryString = parse_url($uri, PHP_URL_QUERY); $_SERVER['REQUEST_URI'] = $queryString === null ? $pathString : $pathString . '?' . $queryString; $_SERVER['REQUEST_METHOD'] = strtoupper($request->getMethod()); parse_str($queryString, $params); foreach ($params as $k => $v) { $_GET[$k] = $v; } $app = $this->getApplication(); $app->getResponse()->on(YiiResponse::EVENT_AFTER_PREPARE, [$this, 'processResponse']); // disabling logging. Logs are slowing test execution down foreach ($app->log->targets as $target) { $target->enabled = false; } $this->headers = array(); $this->statusCode = null; ob_start(); $yiiRequest = $app->getRequest(); if ($request->getContent() !== null) { $yiiRequest->setRawBody($request->getContent()); $yiiRequest->setBodyParams(null); } else { $yiiRequest->setRawBody(null); $yiiRequest->setBodyParams($_POST); } $yiiRequest->setQueryParams($_GET); try { $app->trigger($app::EVENT_BEFORE_REQUEST); $app->handleRequest($yiiRequest)->send(); $app->trigger($app::EVENT_AFTER_REQUEST); } catch (\Exception $e) { if ($e instanceof HttpException) { // Don't discard output and pass exception handling to Yii to be able // to expect error response codes in tests. $app->errorHandler->discardExistingOutput = false; $app->errorHandler->handleException($e); } elseif (!$e instanceof ExitException) { // for exceptions not related to Http, we pass them to Codeception $this->resetApplication(); throw $e; } } $content = ob_get_clean(); // catch "location" header and display it in debug, otherwise it would be handled // by symfony browser-kit and not displayed. if (isset($this->headers['location'])) { Debug::debug("[Headers] " . json_encode($this->headers)); } $this->resetApplication(); return new Response($content, $this->statusCode, $this->headers); } protected function revertErrorHandler() { $handler = new ErrorHandler(); set_error_handler(array($handler, 'errorHandler')); } public function restoreServerVars() { $this->server = $this->defaultServerVars; foreach ($this->server as $key => $value) { $_SERVER[$key] = $value; } } public function processResponse($event) { /** @var \yii\web\Response $response */ $response = $event->sender; $request = Yii::$app->getRequest(); $this->headers = $response->getHeaders()->toArray(); $response->getHeaders()->removeAll(); $this->statusCode = $response->getStatusCode(); $cookies = $response->getCookies(); if ($request->enableCookieValidation) { $validationKey = $request->cookieValidationKey; } foreach ($cookies as $cookie) { /** @var \yii\web\Cookie $cookie */ $value = $cookie->value; if ($cookie->expire != 1 && isset($validationKey)) { $data = version_compare(Yii::getVersion(), '2.0.2', '>') ? [$cookie->name, $cookie->value] : $cookie->value; $value = Yii::$app->security->hashData(serialize($data), $validationKey); } $c = new Cookie( $cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly ); $this->getCookieJar()->set($c); } $cookies->removeAll(); } /** * Replace mailer with in memory mailer * @param $config * @param $app */ protected function mockMailer($config) { if (static::$mailer) { $this->app->set('mailer', static::$mailer); return; } // options that make sense for mailer mock $allowedOptions = [ 'htmlLayout', 'textLayout', 'messageConfig', 'messageClass', 'useFileTransport', 'fileTransportPath', 'fileTransportCallback', 'view', 'viewPath', ]; $mailerConfig = [ 'class' => 'Codeception\Lib\Connector\Yii2\TestMailer', ]; if (isset($config['components']['mailer']) && is_array($config['components']['mailer'])) { foreach ($config['components']['mailer'] as $name => $value) { if (in_array($name, $allowedOptions, true)) { $mailerConfig[$name] = $value; } } } $this->app->set('mailer', $mailerConfig); static::$mailer = $this->app->get('mailer'); } /** * @param $app */ protected function persistDb() { // always use the same DB connection if (static::$db) { $this->app->set('db', static::$db); } elseif ($this->app->has('db')) { static::$db = $this->app->get('db'); } } } <?php namespace Codeception\Lib\Connector\ZF2; use \Zend\ServiceManager\ServiceLocatorInterface; use \Zend\ServiceManager\ServiceManager; class PersistentServiceManager extends ServiceManager implements ServiceLocatorInterface { /** * @var ServiceLocatorInterface Used to retrieve Doctrine services */ private $serviceManager; public function __construct(ServiceLocatorInterface $serviceManager) { $this->serviceManager = $serviceManager; } public function get($name) { if (parent::has($name)) { return parent::get($name); } return $this->serviceManager->get($name); } public function has($name) { if (parent::has($name)) { return true; } if (preg_match('/doctrine/i', $name)) { return $this->serviceManager->has($name); } return false; } public function setService($name, $service) { parent::setService($name, $service); } } <?php namespace Codeception\Lib\Connector\Laravel5; use Exception; use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract; /** * Class ExceptionHandlerDecorator * * @package Codeception\Lib\Connector\Laravel5 */ class ExceptionHandlerDecorator implements ExceptionHandlerContract { /** * @var ExceptionHandlerContract */ private $laravelExceptionHandler; /** * @var boolean */ private $exceptionHandlingDisabled = true; /** * ExceptionHandlerDecorator constructor. * * @param object $laravelExceptionHandler */ public function __construct($laravelExceptionHandler) { $this->laravelExceptionHandler = $laravelExceptionHandler; } /** * @param boolean $exceptionHandlingDisabled */ public function exceptionHandlingDisabled($exceptionHandlingDisabled) { $this->exceptionHandlingDisabled = $exceptionHandlingDisabled; } /** * Report or log an exception. * * @param \Exception $e * @return void */ public function report(Exception $e) { $this->laravelExceptionHandler->report($e); } /** * @param $request * @param Exception $e * @return \Symfony\Component\HttpFoundation\Response * @throws Exception */ public function render($request, Exception $e) { $response = $this->laravelExceptionHandler->render($request, $e); if ($this->exceptionHandlingDisabled && strpos($response->getContent(), '<div id="sf-resetcontent" class="sf-reset">') !== false ) { // If content was generated by the \Symfony\Component\Debug\ExceptionHandler class // the Laravel application could not handle the exception, // so re-throw this exception if the Codeception user disabled Laravel's exception handling. throw $e; } return $response; } /** * Render an exception to the console. * * @param \Symfony\Component\Console\Output\OutputInterface $output * @param \Exception $e * @return void */ public function renderForConsole($output, Exception $e) { $this->laravelExceptionHandler->renderForConsole($output, $e); } /** * @param string $method * @param array $args * @return mixed */ public function __call($method, $args) { return call_user_func_array(array($this->laravelExceptionHandler, $method), $args); } } <?php namespace Codeception\Lib\Connector; use Codeception\Lib\Connector\Laravel5\ExceptionHandlerDecorator; use Codeception\Lib\Connector\Shared\LaravelCommon; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Application; use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Client; class Laravel5 extends Client { use LaravelCommon; /** * @var Application */ private $app; /** * @var \Codeception\Module\Laravel5 */ private $module; /** * @var bool */ private $firstRequest = true; /** * @var array */ private $triggeredEvents = []; /** * @var bool */ private $exceptionHandlingDisabled; /** * @var bool */ private $middlewareDisabled; /** * @var bool */ private $eventsDisabled; /** * @var bool */ private $modelEventsDisabled; /** * @var object */ private $oldDb; /** * Constructor. * * @param \Codeception\Module\Laravel5 $module */ public function __construct($module) { $this->module = $module; $this->exceptionHandlingDisabled = $this->module->config['disable_exception_handling']; $this->middlewareDisabled = $this->module->config['disable_middleware']; $this->eventsDisabled = $this->module->config['disable_events']; $this->modelEventsDisabled = $this->module->config['disable_model_events']; $this->initialize(); $components = parse_url($this->app['config']->get('app.url', 'http://localhost')); if (array_key_exists('url', $this->module->config)) { $components = parse_url($this->module->config['url']); } $host = isset($components['host']) ? $components['host'] : 'localhost'; parent::__construct($this->app, ['HTTP_HOST' => $host]); // Parent constructor defaults to not following redirects $this->followRedirects(true); } /** * Execute a request. * * @param SymfonyRequest $request * @return Response */ protected function doRequest($request) { if (!$this->firstRequest) { $this->initialize($request); } $this->firstRequest = false; $this->applyBindings(); $this->applyContextualBindings(); $this->applyInstances(); $this->applyApplicationHandlers(); $request = Request::createFromBase($request); $response = $this->kernel->handle($request); $this->app->make('Illuminate\Contracts\Http\Kernel')->terminate($request, $response); return $response; } /** * Make sure files are \Illuminate\Http\UploadedFile instances with the private $test property set to true. * Fixes issue https://github.com/Codeception/Codeception/pull/3417. * * @param array $files * @return array */ protected function filterFiles(array $files) { $files = parent::filterFiles($files); if (! class_exists('Illuminate\Http\UploadedFile')) { // The \Illuminate\Http\UploadedFile class was introduced in Laravel 5.2.15, // so don't change the $files array if it does not exist. return $files; } return $this->convertToTestFiles($files); } /** * @param array $files * @return array */ private function convertToTestFiles(array $files) { $filtered = []; foreach ($files as $key => $value) { if (is_array($value)) { $filtered[$key] = $this->convertToTestFiles($value); } else { $filtered[$key] = UploadedFile::createFromBase($value, true); } } return $filtered; } /** * Initialize the Laravel framework. * * @param SymfonyRequest $request */ private function initialize($request = null) { // Store a reference to the database object // so the database connection can be reused during tests $this->oldDb = null; if (isset($this->app['db']) && $this->app['db']->connection()) { $this->oldDb = $this->app['db']; } $this->app = $this->kernel = $this->loadApplication(); // Set the request instance for the application, if (is_null($request)) { $appConfig = require $this->module->config['project_dir'] . 'config/app.php'; $request = SymfonyRequest::create($appConfig['url']); } $this->app->instance('request', Request::createFromBase($request)); // Reset the old database after all the service providers are registered. if ($this->oldDb) { $this->app['events']->listen('bootstrapped: Illuminate\Foundation\Bootstrap\RegisterProviders', function () { $this->app->singleton('db', function () { return $this->oldDb; }); }); } $this->app->make('Illuminate\Contracts\Http\Kernel')->bootstrap(); // Record all triggered events by adding a wildcard event listener // Since Laravel 5.4 wildcard event handlers receive the event name as the first argument, // but for earlier Laravel versions the firing() method of the event dispatcher should be used // to determine the event name. if (method_exists($this->app['events'], 'firing')) { $listener = function () { $this->triggeredEvents[] = $this->normalizeEvent($this->app['events']->firing()); }; } else { $listener = function ($event) { $this->triggeredEvents[] = $this->normalizeEvent($event); }; } $this->app['events']->listen('*', $listener); // Replace the Laravel exception handler with our decorated exception handler, // so exceptions can be intercepted for the disable_exception_handling functionality. $decorator = new ExceptionHandlerDecorator($this->app['Illuminate\Contracts\Debug\ExceptionHandler']); $decorator->exceptionHandlingDisabled($this->exceptionHandlingDisabled); $this->app->instance('Illuminate\Contracts\Debug\ExceptionHandler', $decorator); if ($this->module->config['disable_middleware'] || $this->middlewareDisabled) { $this->app->instance('middleware.disable', true); } if ($this->module->config['disable_events'] || $this->eventsDisabled) { $this->mockEventDispatcher(); } if ($this->module->config['disable_model_events'] || $this->modelEventsDisabled) { Model::unsetEventDispatcher(); } $this->module->setApplication($this->app); } /** * Boot the Laravel application object. * @return Application * @throws ModuleConfig */ private function loadApplication() { $app = require $this->module->config['bootstrap_file']; $app->loadEnvironmentFrom($this->module->config['environment_file']); $app->instance('request', new Request()); return $app; } /** * Replace the Laravel event dispatcher with a mock. */ private function mockEventDispatcher() { $mockGenerator = new \PHPUnit_Framework_MockObject_Generator; $mock = $mockGenerator->getMock('Illuminate\Contracts\Events\Dispatcher'); // Even if events are disabled we still want to record the triggered events. // But by mocking the event dispatcher the wildcard listener registered in the initialize method is removed. // So to record the triggered events we have to catch the calls to the fire method of the event dispatcher mock. $callback = function ($event) { $this->triggeredEvents[] = $this->normalizeEvent($event); return []; }; // In Laravel 5.4 the Illuminate\Contracts\Events\Dispatcher interface was changed, // the 'fire' method was renamed to 'dispatch'. This code determines the correct method to mock. $method = method_exists($this->app['events'], 'dispatch') ? 'dispatch' : 'fire'; $mock->expects(new \PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) ->method($method) ->will(new \PHPUnit_Framework_MockObject_Stub_ReturnCallback($callback)); $this->app->instance('events', $mock); } /** * Normalize events to class names. * * @param $event * @return string */ private function normalizeEvent($event) { if (is_object($event)) { $event = get_class($event); } if (preg_match('/^bootstrapp(ing|ed): /', $event)) { return $event; } // Events can be formatted as 'event.name: parameters' $segments = explode(':', $event); return $segments[0]; } //====================================================================== // Public methods called by module //====================================================================== /** * Did an event trigger? * * @param $event * @return bool */ public function eventTriggered($event) { $event = $this->normalizeEvent($event); foreach ($this->triggeredEvents as $triggeredEvent) { if ($event == $triggeredEvent || is_subclass_of($event, $triggeredEvent)) { return true; } } return false; } /** * Disable Laravel exception handling. */ public function disableExceptionHandling() { $this->exceptionHandlingDisabled = true; $this->app['Illuminate\Contracts\Debug\ExceptionHandler']->exceptionHandlingDisabled(true); } /** * Enable Laravel exception handling. */ public function enableExceptionHandling() { $this->exceptionHandlingDisabled = false; $this->app['Illuminate\Contracts\Debug\ExceptionHandler']->exceptionHandlingDisabled(false); } /** * Disable events. */ public function disableEvents() { $this->eventsDisabled = true; $this->mockEventDispatcher(); } /** * Disable model events. */ public function disableModelEvents() { $this->modelEventsDisabled = true; Model::unsetEventDispatcher(); } /* * Disable middleware. */ public function disableMiddleware() { $this->middlewareDisabled = true; $this->app->instance('middleware.disable', true); } } <?php namespace Codeception\Lib\Connector; use Codeception\Util\Stub; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Response; class Yii1 extends Client { use Shared\PhpSuperGlobalsConverter; /** * http://localhost/path/to/your/app/index.php * @var string url of the entry Yii script */ public $url; /** * Current application settings {@see Codeception\Module\Yii1::$appSettings} * @var array */ public $appSettings; /** * Full path to your application * @var string */ public $appPath; /** * Current request headers * @var array */ private $headers; /** * * @param \Symfony\Component\BrowserKit\Request $request * * @return \Symfony\Component\BrowserKit\Response */ public function doRequest($request) { $this->headers = array(); $_COOKIE = array_merge($_COOKIE, $request->getCookies()); $_SERVER = array_merge($_SERVER, $request->getServer()); $_FILES = $this->remapFiles($request->getFiles()); $_REQUEST = $this->remapRequestParameters($request->getParameters()); $_POST = $_GET = array(); if (strtoupper($request->getMethod()) == 'GET') { $_GET = $_REQUEST; } else { $_POST = $_REQUEST; } // Parse url parts $uriPath = ltrim(parse_url($request->getUri(), PHP_URL_PATH), '/'); $uriQuery = ltrim(parse_url($request->getUri(), PHP_URL_QUERY), '?'); $scriptName = trim(parse_url($this->url, PHP_URL_PATH), '/'); if (!empty($uriQuery)) { $uriPath .= "?{$uriQuery}"; parse_str($uriQuery, $params); foreach ($params as $k => $v) { $_GET[$k] = $v; } } // Add script name to request if none if ($scriptName and strpos($uriPath, $scriptName) === false) { $uriPath = "/{$scriptName}/{$uriPath}"; } // Add forward slash if not exists if (strpos($uriPath, '/') !== 0) { $uriPath = "/{$uriPath}"; } $_SERVER['REQUEST_METHOD'] = strtoupper($request->getMethod()); $_SERVER['REQUEST_URI'] = $uriPath; /** * Hack to be sure that CHttpRequest will resolve route correctly */ $_SERVER['SCRIPT_NAME'] = "/{$scriptName}"; $_SERVER['SCRIPT_FILENAME'] = $this->appPath; ob_start(); \Yii::setApplication(null); \Yii::createApplication($this->appSettings['class'], $this->appSettings['config']); $app = \Yii::app(); // disabling logging. Logs slow down test execution if ($app->hasComponent('log')) { foreach ($app->getComponent('log')->routes as $route) { $route->enabled = false; } } if ($app->hasComponent('session')) { // disable regenerate id in session $app->setComponent('session', Stub::make('CHttpSession', ['regenerateID' => false])); } $app->onEndRequest->add([$this, 'setHeaders']); $app->run(); if ($app->hasComponent('db')) { // close connection $app->getDb()->setActive(false); // cleanup metadata cache $property = new \ReflectionProperty('CActiveRecord', '_md'); $property->setAccessible(true); $property->setValue([]); } $content = ob_get_clean(); $headers = $this->getHeaders(); $statusCode = 200; foreach ($headers as $header => $val) { if ($header == 'Location') { $statusCode = 302; } } $response = new Response($content, $statusCode, $this->getHeaders()); return $response; } /** * Set current client headers when terminating yii application (onEndRequest) */ public function setHeaders() { $this->headers = \Yii::app()->request->getAllHeaders(); } /** * Returns current client headers * @return array headers */ public function getHeaders() { return $this->headers; } } <?php namespace Codeception\Lib\Connector; use Codeception\Lib\Connector\Lumen\DummyKernel; use Codeception\Lib\Connector\Shared\LaravelCommon; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use Illuminate\Support\Facades\Facade; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Client; class Lumen extends Client { use LaravelCommon; /** * @var \Laravel\Lumen\Application */ private $app; /** * @var \Codeception\Module\Lumen */ private $module; /** * @var bool */ private $firstRequest = true; /** * @var object */ private $oldDb; /** * Constructor. * * @param \Codeception\Module\Lumen $module */ public function __construct($module) { $this->module = $module; $components = parse_url($this->module->config['url']); $server = ['HTTP_HOST' => $components['host']]; // Pass a DummyKernel to satisfy the arguments of the parent constructor. // The actual kernel object is set in the initialize() method. parent::__construct(new DummyKernel(), $server); // Parent constructor defaults to not following redirects $this->followRedirects(true); $this->initialize(); } /** * Execute a request. * * @param SymfonyRequest $request * @return Response */ protected function doRequest($request) { if (!$this->firstRequest) { $this->initialize($request); } $this->firstRequest = false; $this->applyBindings(); $this->applyContextualBindings(); $this->applyInstances(); $this->applyApplicationHandlers(); $request = Request::createFromBase($request); $response = $this->kernel->handle($request); $method = new \ReflectionMethod(get_class($this->app), 'callTerminableMiddleware'); $method->setAccessible(true); $method->invoke($this->app, $response); return $response; } /** * Initialize the Lumen framework. * * @param SymfonyRequest|null $request */ private function initialize($request = null) { // Store a reference to the database object // so the database connection can be reused during tests $this->oldDb = null; if (isset($this->app['db']) && $this->app['db']->connection()) { $this->oldDb = $this->app['db']; } if (class_exists(Facade::class)) { // If the container has been instantiated ever, // we need to clear its static fields before create new container. Facade::clearResolvedInstances(); } $this->app = $this->kernel = require $this->module->config['bootstrap_file']; // Lumen registers necessary bindings on demand when calling $app->make(), // so here we force the request binding before registering our own request object, // otherwise Lumen will overwrite our request object. $this->app->make('request'); $request = $request ?: SymfonyRequest::create($this->module->config['url']); $this->app->instance('Illuminate\Http\Request', Request::createFromBase($request)); // Reset the old database if there is one if ($this->oldDb) { $this->app->singleton('db', function () { return $this->oldDb; }); Model::setConnectionResolver($this->oldDb); } $this->module->setApplication($this->app); } } <?php namespace Codeception\Lib\Connector; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Response; use Symfony\Component\BrowserKit\Request as BrowserKitRequest; class ZF1 extends Client { use Shared\PhpSuperGlobalsConverter; /** * @var \Zend_Controller_Front */ protected $front; /** * @var \Zend_Application */ protected $bootstrap; /** * @var \Zend_Controller_Request_HttpTestCase */ protected $zendRequest; public function setBootstrap($bootstrap) { $this->bootstrap = $bootstrap; $this->front = $this->bootstrap ->getBootstrap() ->getResource('frontcontroller'); $this->front ->throwExceptions(true) ->returnResponse(false); } public function doRequest($request) { // redirector should not exit $redirector = \Zend_Controller_Action_HelperBroker::getStaticHelper('redirector'); $redirector->setExit(false); // json helper should not exit $json = \Zend_Controller_Action_HelperBroker::getStaticHelper('json'); $json->suppressExit = true; $zendRequest = new \Zend_Controller_Request_HttpTestCase(); $zendRequest->setMethod($request->getMethod()); $zendRequest->setCookies($request->getCookies()); $zendRequest->setParams($request->getParameters()); // Sf2's BrowserKit does not distinguish between GET, POST, PUT etc., // so we set all parameters in ZF's request here to not break apps // relying on $request->getPost() $zendRequest->setPost($request->getParameters()); $zendRequest->setRawBody($request->getContent()); $uri = $request->getUri(); $queryString = parse_url($uri, PHP_URL_QUERY); $requestUri = parse_url($uri, PHP_URL_PATH); if (!empty($queryString)) { $requestUri .= '?' . $queryString; } $zendRequest->setRequestUri($requestUri); $zendRequest->setHeaders($this->extractHeaders($request)); $_FILES = $this->remapFiles($request->getFiles()); $_SERVER = array_merge($_SERVER, $request->getServer()); $zendResponse = new \Zend_Controller_Response_HttpTestCase; $this->front->setRequest($zendRequest)->setResponse($zendResponse); ob_start(); try { $this->bootstrap->run(); $_GET = $_POST = []; } catch (\Exception $e) { ob_end_clean(); $_GET = $_POST = []; throw $e; } ob_end_clean(); $this->zendRequest = $zendRequest; $response = new Response( $zendResponse->getBody(), $zendResponse->getHttpResponseCode(), $this->formatResponseHeaders($zendResponse) ); return $response; } /** * Format up the ZF1 response headers into Symfony\Component\BrowserKit\Response headers format. * * @param \Zend_Controller_Response_Abstract $response The ZF1 Response Object. * @return array the clean key/value headers */ private function formatResponseHeaders(\Zend_Controller_Response_Abstract $response) { $headers = array(); foreach ($response->getHeaders() as $header) { $name = $header['name']; if (array_key_exists($name, $headers)) { if ($header['replace']) { $headers[$name] = $header['value']; } } else { $headers[$name] = $header['value']; } } return $headers; } /** * @return \Zend_Controller_Request_HttpTestCase */ public function getZendRequest() { return $this->zendRequest; } private function extractHeaders(BrowserKitRequest $request) { $headers = []; $server = $request->getServer(); $contentHeaders = array('Content-Length' => true, 'Content-Md5' => true, 'Content-Type' => true); foreach ($server as $header => $val) { $header = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $header))))); if (strpos($header, 'Http-') === 0) { $headers[substr($header, 5)] = $val; } elseif (isset($contentHeaders[$header])) { $headers[$header] = $val; } } return $headers; } } <?php namespace Codeception\Lib\Connector; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Response; class Universal extends Client { use Shared\PhpSuperGlobalsConverter; protected $mockedResponse; protected $index; public function setIndex($index) { $this->index = $index; } public function mockResponse($response) { $this->mockedResponse = $response; } public function doRequest($request) { if ($this->mockedResponse) { $response = $this->mockedResponse; $this->mockedResponse = null; return $response; } $_COOKIE = $request->getCookies(); $_SERVER = $request->getServer(); $_FILES = $this->remapFiles($request->getFiles()); $uri = str_replace('http://localhost', '', $request->getUri()); $_REQUEST = $this->remapRequestParameters($request->getParameters()); if (strtoupper($request->getMethod()) == 'GET') { $_GET = $_REQUEST; } else { $_POST = $_REQUEST; } $_SERVER['REQUEST_METHOD'] = strtoupper($request->getMethod()); $_SERVER['REQUEST_URI'] = $uri; ob_start(); include $this->index; $content = ob_get_contents(); ob_end_clean(); $headers = []; $php_headers = headers_list(); foreach ($php_headers as $value) { // Get the header name $parts = explode(':', $value); if (count($parts) > 1) { $name = trim(array_shift($parts)); // Build the header hash map $headers[$name] = trim(implode(':', $parts)); } } $headers['Content-type'] = isset($headers['Content-type']) ? $headers['Content-type'] : "text/html; charset=UTF-8"; $response = new Response($content, 200, $headers); return $response; } } <?php namespace Codeception\Lib\Connector; use Codeception\Lib\Connector\ZendExpressive\ResponseCollector; use Symfony\Component\BrowserKit\Client; use Symfony\Component\BrowserKit\Request; use Symfony\Component\BrowserKit\Response; use Symfony\Component\BrowserKit\Request as BrowserKitRequest; use Zend\Diactoros\ServerRequest; use Zend\Expressive\Application; use Zend\Diactoros\UploadedFile; class ZendExpressive extends Client { /** * @var Application */ protected $application; /** * @var ResponseCollector */ protected $responseCollector; /** * @param Application */ public function setApplication(Application $application) { $this->application = $application; } /** * @param ResponseCollector $responseCollector */ public function setResponseCollector(ResponseCollector $responseCollector) { $this->responseCollector = $responseCollector; } /** * @param Request $request * * @return Response * @throws \Exception */ public function doRequest($request) { $inputStream = fopen('php://memory', 'r+'); $content = $request->getContent(); if ($content !== null) { fwrite($inputStream, $content); rewind($inputStream); } $queryParams = []; $postParams = []; $queryString = parse_url($request->getUri(), PHP_URL_QUERY); if ($queryString != '') { parse_str($queryString, $queryParams); } if ($request->getMethod() !== 'GET') { $postParams = $request->getParameters(); } $serverParams = $request->getServer(); if (!isset($serverParams['SCRIPT_NAME'])) { //required by WhoopsErrorHandler $serverParams['SCRIPT_NAME'] = 'Codeception'; } $zendRequest = new ServerRequest( $serverParams, $this->convertFiles($request->getFiles()), $request->getUri(), $request->getMethod(), $inputStream, $this->extractHeaders($request) ); $zendRequest = $zendRequest->withCookieParams($request->getCookies()) ->withQueryParams($queryParams) ->withParsedBody($postParams); $cwd = getcwd(); chdir(codecept_root_dir()); $this->application->run($zendRequest); chdir($cwd); $this->request = $zendRequest; $response = $this->responseCollector->getResponse(); $this->responseCollector->clearResponse(); return new Response( $response->getBody(), $response->getStatusCode(), $response->getHeaders() ); } private function convertFiles(array $files) { $fileObjects = []; foreach ($files as $fieldName => $file) { if ($file instanceof UploadedFile) { $fileObjects[$fieldName] = $file; } elseif (!isset($file['tmp_name']) && !isset($file['name'])) { $fileObjects[$fieldName] = $this->convertFiles($file); } else { $fileObjects[$fieldName] = new UploadedFile( $file['tmp_name'], $file['size'], $file['error'], $file['name'], $file['type'] ); } } return $fileObjects; } private function extractHeaders(BrowserKitRequest $request) { $headers = []; $server = $request->getServer(); $contentHeaders = array('Content-Length' => true, 'Content-Md5' => true, 'Content-Type' => true); foreach ($server as $header => $val) { $header = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace('_', '-', $header))))); if (strpos($header, 'Http-') === 0) { $headers[substr($header, 5)] = $val; } elseif (isset($contentHeaders[$header])) { $headers[$header] = $val; } } return $headers; } } <?php namespace Codeception\Lib\Driver; class Oci extends Db { public function cleanup() { $this->dbh->exec( "BEGIN FOR i IN (SELECT trigger_name FROM user_triggers) LOOP EXECUTE IMMEDIATE('DROP TRIGGER ' || user || '.\"' || i.trigger_name || '\"'); END LOOP; END;" ); $this->dbh->exec( "BEGIN FOR i IN (SELECT table_name FROM user_tables) LOOP EXECUTE IMMEDIATE('DROP TABLE ' || user || '.\"' || i.table_name || '\" CASCADE CONSTRAINTS'); END LOOP; END;" ); $this->dbh->exec( "BEGIN FOR i IN (SELECT sequence_name FROM user_sequences) LOOP EXECUTE IMMEDIATE('DROP SEQUENCE ' || user || '.\"' || i.sequence_name || '\"'); END LOOP; END;" ); $this->dbh->exec( "BEGIN FOR i IN (SELECT view_name FROM user_views) LOOP EXECUTE IMMEDIATE('DROP VIEW ' || user || '.\"' || i.view_name || '\"'); END LOOP; END;" ); } /** * SQL commands should ends with `//` in the dump file * IF you want to load triggers too. * IF you do not want to load triggers you can use the `;` characters * but in this case you need to change the $delimiter from `//` to `;` * * @param $sql */ public function load($sql) { $query = ''; $delimiter = '//'; $delimiterLength = 2; foreach ($sql as $sqlLine) { if (preg_match('/DELIMITER ([\;\$\|\\\\]+)/i', $sqlLine, $match)) { $delimiter = $match[1]; $delimiterLength = strlen($delimiter); continue; } $parsed = $this->sqlLine($sqlLine); if ($parsed) { continue; } $query .= "\n" . rtrim($sqlLine); if (substr($query, -1 * $delimiterLength, $delimiterLength) == $delimiter) { $this->sqlQuery(substr($query, 0, -1 * $delimiterLength)); $query = ""; } } if ($query !== '') { $this->sqlQuery($query); } } /** * @param string $tableName * * @return array[string] */ public function getPrimaryKey($tableName) { if (!isset($this->primaryKeys[$tableName])) { $primaryKey = []; $query = "SELECT cols.column_name FROM all_constraints cons, all_cons_columns cols WHERE cols.table_name = ? AND cons.constraint_type = 'P' AND cons.constraint_name = cols.constraint_name AND cons.owner = cols.owner ORDER BY cols.table_name, cols.position"; $stmt = $this->executeQuery($query, [$tableName]); $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ($columns as $column) { $primaryKey []= $column['COLUMN_NAME']; } $this->primaryKeys[$tableName] = $primaryKey; } return $this->primaryKeys[$tableName]; } } <?php namespace Codeception\Lib\Driver; use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleException; use MongoDB\Database; class MongoDb { const DEFAULT_PORT = 27017; private $legacy; private $dbh; private $dsn; private $dbName; private $host; private $user; private $password; private $client; private $quiet = ''; public static function connect($dsn, $user, $password) { throw new \Exception(__CLASS__ . '::connect() - hm, it looked like this method had become obsolete...'); } /** * Connect to the Mongo server using the MongoDB extension. */ protected function setupMongoDB($dsn, $options) { try { $this->client = new \MongoDB\Client($dsn, $options); $this->dbh = $this->client->selectDatabase($this->dbName); } catch (\MongoDB\Driver\Exception $e) { throw new ModuleException($this, sprintf('Failed to open Mongo connection: %s', $e->getMessage())); } } /** * Connect to the Mongo server using the legacy mongo extension. */ protected function setupMongo($dsn, $options) { try { $this->client = new \MongoClient($dsn, $options); $this->dbh = $this->client->selectDB($this->dbName); } catch (\MongoConnectionException $e) { throw new ModuleException($this, sprintf('Failed to open Mongo connection: %s', $e->getMessage())); } } /** * Clean up the Mongo database using the MongoDB extension. */ protected function cleanupMongoDB() { try { $this->dbh->drop(); } catch (\MongoDB\Driver\Exception $e) { throw new \Exception(sprintf('Failed to drop the DB: %s', $e->getMessage())); } } /** * Clean up the Mongo database using the legacy Mongo extension. */ protected function cleanupMongo() { try { $list = $this->dbh->listCollections(); } catch (\MongoException $e) { throw new \Exception(sprintf('Failed to list collections of the DB: %s', $e->getMessage())); } foreach ($list as $collection) { try { $collection->drop(); } catch (\MongoException $e) { throw new \Exception(sprintf('Failed to drop collection: %s', $e->getMessage())); } } } /** * $dsn has to contain db_name after the host. E.g. "mongodb://localhost:27017/mongo_test_db" * * @static * * @param $dsn * @param $user * @param $password * * @throws ModuleConfigException * @throws \Exception */ public function __construct($dsn, $user, $password) { $this->legacy = extension_loaded('mongodb') === false && class_exists('\\MongoClient') && strpos(\MongoClient::VERSION, 'mongofill') === false; /* defining DB name */ $this->dbName = preg_replace('/\?.*/', '', substr($dsn, strrpos($dsn, '/') + 1)); if (strlen($this->dbName) == 0) { throw new ModuleConfigException($this, 'Please specify valid $dsn with DB name after the host:port'); } /* defining host */ if (strpos($dsn, 'mongodb://') !== false) { $this->host = str_replace('mongodb://', '', preg_replace('/\?.*/', '', $dsn)); } else { $this->host = $dsn; } $this->host = rtrim(str_replace($this->dbName, '', $this->host), '/'); $options = [ 'connect' => true ]; if ($user && $password) { $options += [ 'username' => $user, 'password' => $password ]; } $this->{$this->legacy ? 'setupMongo' : 'setupMongoDB'}($dsn, $options); $this->dsn = $dsn; $this->user = $user; $this->password = $password; } /** * @static * * @param $dsn * @param $user * @param $password * * @return MongoDb */ public static function create($dsn, $user, $password) { return new MongoDb($dsn, $user, $password); } public function cleanup() { $this->{$this->legacy ? 'cleanupMongo' : 'cleanupMongoDB'}(); } /** * dump file has to be a javascript document where one can use all the mongo shell's commands * just FYI: this file can be easily created be RockMongo's export button * * @param $dumpFile */ public function load($dumpFile) { $cmd = sprintf( 'mongo %s %s%s', $this->host . '/' . $this->dbName, $this->createUserPasswordCmdString(), escapeshellarg($dumpFile) ); shell_exec($cmd); } public function loadFromMongoDump($dumpFile) { list($host, $port) = $this->getHostPort(); $cmd = sprintf( "mongorestore %s --host %s --port %s -d %s %s %s", $this->quiet, $host, $port, $this->dbName, $this->createUserPasswordCmdString(), escapeshellarg($dumpFile) ); shell_exec($cmd); } public function loadFromTarGzMongoDump($dumpFile) { list($host, $port) = $this->getHostPort(); $getDirCmd = sprintf( "tar -tf %s | awk 'BEGIN { FS = \"/\" } ; { print $1 }' | uniq", escapeshellarg($dumpFile) ); $dirCountCmd = $getDirCmd . ' | wc -l'; if (trim(shell_exec($dirCountCmd)) !== '1') { throw new ModuleException( $this, 'Archive MUST contain single directory with db dump' ); } $dirName = trim(shell_exec($getDirCmd)); $cmd = sprintf( 'tar -xzf %s && mongorestore %s --host %s --port %s -d %s %s %s && rm -r %s', escapeshellarg($dumpFile), $this->quiet, $host, $port, $this->dbName, $this->createUserPasswordCmdString(), $dirName, $dirName ); shell_exec($cmd); } private function createUserPasswordCmdString() { if ($this->user && $this->password) { return sprintf( '--username %s --password %s ', $this->user, $this->password ); } return ''; } public function getDbh() { return $this->dbh; } public function setDatabase($dbName) { $this->dbh = $this->client->{$this->legacy ? 'selectDB' : 'selectDatabase'}($dbName); } /** * Determine if this driver is using the legacy extension or not. * * @return bool */ public function isLegacy() { return $this->legacy; } private function getHostPort() { $hostPort = explode(':', $this->host); if (count($hostPort) === 2) { return $hostPort; } if (count($hostPort) === 1) { return [$hostPort[0], self::DEFAULT_PORT]; } throw new ModuleException($this, '$dsn MUST be like (mongodb://)<host>:<port>/<db name>'); } public function setQuiet($quiet) { $this->quiet = $quiet ? '--quiet' : ''; } } <?php namespace Codeception\Lib\Driver; use Codeception\Configuration; use Codeception\Exception\ModuleException; class Sqlite extends Db { protected $hasSnapshot = false; protected $filename = ''; protected $con = null; public function __construct($dsn, $user, $password) { $filename = substr($dsn, 7); if ($filename === ':memory:') { throw new ModuleException(__CLASS__, ':memory: database is not supported'); } $this->filename = Configuration::projectDir() . $filename; $this->dsn = 'sqlite:' . $this->filename; parent::__construct($this->dsn, $user, $password); } public function cleanup() { $this->dbh = null; file_put_contents($this->filename, ''); $this->dbh = self::connect($this->dsn, $this->user, $this->password); } public function load($sql) { if ($this->hasSnapshot) { $this->dbh = null; file_put_contents($this->filename, file_get_contents($this->filename . '_snapshot')); $this->dbh = new \PDO($this->dsn, $this->user, $this->password); } else { if (file_exists($this->filename . '_snapshot')) { unlink($this->filename . '_snapshot'); } parent::load($sql); copy($this->filename, $this->filename . '_snapshot'); $this->hasSnapshot = true; } } /** * @param string $tableName * * @return array[string] */ public function getPrimaryKey($tableName) { if (!isset($this->primaryKeys[$tableName])) { if ($this->hasRowId($tableName)) { return $this->primaryKeys[$tableName] = ['_ROWID_']; } $primaryKey = []; $query = 'PRAGMA table_info(' . $this->getQuotedName($tableName) . ')'; $stmt = $this->executeQuery($query, []); $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ($columns as $column) { if ($column['pk'] !== '0') { $primaryKey []= $column['name']; } } $this->primaryKeys[$tableName] = $primaryKey; } return $this->primaryKeys[$tableName]; } /** * @param $tableName * @return bool */ private function hasRowId($tableName) { $params = ['type' => 'table', 'name' => $tableName]; $select = $this->select('sql', 'sqlite_master', $params); $result = $this->executeQuery($select, $params); $sql = $result->fetchColumn(0); return strpos($sql, ') WITHOUT ROWID') === false; } } <?php namespace Codeception\Lib\Driver; use Codeception\Exception\ModuleException; class PostgreSql extends Db { protected $putline = false; protected $connection = null; protected $searchPath = null; /** * Loads a SQL file. * * @param string $sql sql file */ public function load($sql) { $query = ''; $delimiter = ';'; $delimiterLength = 1; $dollarsOpen = false; foreach ($sql as $sqlLine) { if (preg_match('/DELIMITER ([\;\$\|\\\\]+)/i', $sqlLine, $match)) { $delimiter = $match[1]; $delimiterLength = strlen($delimiter); continue; } $parsed = trim($query) == '' && $this->sqlLine($sqlLine); if ($parsed) { continue; } // Ignore $$ inside SQL standard string syntax such as in INSERT statements. if (!preg_match('/\'.*\$\$.*\'/', $sqlLine)) { $pos = strpos($sqlLine, '$$'); if (($pos !== false) && ($pos >= 0)) { $dollarsOpen = !$dollarsOpen; } } if (preg_match('/SET search_path = .*/i', $sqlLine, $match)) { $this->searchPath = $match[0]; } $query .= "\n" . rtrim($sqlLine); if (!$dollarsOpen && substr($query, -1 * $delimiterLength, $delimiterLength) == $delimiter) { $this->sqlQuery(substr($query, 0, -1 * $delimiterLength)); $query = ''; } } if ($query !== '') { $this->sqlQuery($query); } } public function cleanup() { $tables = $this->dbh ->query( "SELECT 'DROP TABLE IF EXISTS \"' || tablename || '\" cascade;' " . "FROM pg_tables WHERE schemaname = 'public';" ) ->fetchAll(); $sequences = $this->dbh ->query( "SELECT 'DROP SEQUENCE IF EXISTS \"' || relname || '\" cascade;' FROM pg_class WHERE relkind = 'S';" ) ->fetchAll(); // @codingStandardsIgnoreStart $types = $this->dbh ->query("SELECT 'DROP TYPE IF EXISTS \"' || t.typname || '\" cascade;' FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' AND pg_catalog.pg_type_is_visible(t.oid);") ->fetchAll(); // @codingStandardsIgnoreEnd $drops = array_merge($tables, $sequences, $types); if ($drops) { foreach ($drops as $drop) { $this->dbh->exec($drop[0]); } } } public function sqlLine($sql) { if (!$this->putline) { return parent::sqlLine($sql); } if ($sql == '\.') { $this->putline = false; pg_put_line($this->connection, $sql . "\n"); pg_end_copy($this->connection); pg_close($this->connection); } else { pg_put_line($this->connection, $sql . "\n"); } return true; } public function sqlQuery($query) { if (strpos(trim($query), 'COPY ') === 0) { if (!extension_loaded('pgsql')) { throw new ModuleException( '\Codeception\Module\Db', "To run 'COPY' commands 'pgsql' extension should be installed" ); } if (defined('HHVM_VERSION')) { throw new ModuleException( '\Codeception\Module\Db', "'COPY' command is not supported on HHVM, please use INSERT instead" ); } $constring = str_replace(';', ' ', substr($this->dsn, 6)); $constring .= ' user=' . $this->user; $constring .= ' password=' . $this->password; $this->connection = pg_connect($constring); if ($this->searchPath !== null) { pg_query($this->connection, $this->searchPath); } pg_query($this->connection, $query); $this->putline = true; } else { $this->dbh->exec($query); } } /** * Get the last inserted ID of table. */ public function lastInsertId($table) { /* * We make an assumption that the sequence name for this table * is based on how postgres names sequences for SERIAL columns */ $sequenceName = $this->getQuotedName($table . '_id_seq'); $lastSequence = null; try { $lastSequence = $this->getDbh()->lastInsertId($sequenceName); } catch (\PDOException $e) { // in this case, the sequence name might be combined with the primary key name } // here we check if for instance, it's something like table_primary_key_seq instead of table_id_seq // this could occur when you use some kind of import tool like pgloader if (!$lastSequence) { $primaryKeys = $this->getPrimaryKey($table); $pkName = array_shift($primaryKeys); $lastSequence = $this->getDbh()->lastInsertId($this->getQuotedName($table . '_' . $pkName . '_seq')); } return $lastSequence; } /** * Gets a quoted name of a variable. */ public function getQuotedName($name) { $name = explode('.', $name); $name = array_map( function ($data) { return '"' . $data . '"'; }, $name ); return implode('.', $name); } /** * Returns the primary key(s) of the table, based on: * https://wiki.postgresql.org/wiki/Retrieve_primary_key_columns. * * @param string $tableName * * @return array[string] */ public function getPrimaryKey($tableName) { if (!isset($this->primaryKeys[$tableName])) { $primaryKey = []; $query = "SELECT a.attname FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '$tableName'::regclass AND i.indisprimary"; $stmt = $this->executeQuery($query, []); $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ($columns as $column) { $primaryKey []= $column['attname']; } $this->primaryKeys[$tableName] = $primaryKey; } return $this->primaryKeys[$tableName]; } } <?php namespace Codeception\Lib\Driver; class MySql extends Db { public function cleanup() { $this->dbh->exec('SET FOREIGN_KEY_CHECKS=0;'); $res = $this->dbh->query("SHOW FULL TABLES WHERE TABLE_TYPE LIKE '%TABLE';")->fetchAll(); foreach ($res as $row) { $this->dbh->exec('drop table `' . $row[0] . '`'); } $this->dbh->exec('SET FOREIGN_KEY_CHECKS=1;'); } protected function sqlQuery($query) { $this->dbh->exec('SET FOREIGN_KEY_CHECKS=0;'); parent::sqlQuery($query); $this->dbh->exec('SET FOREIGN_KEY_CHECKS=1;'); } public function getQuotedName($name) { return '`' . str_replace('.', '`.`', $name) . '`'; } /** * @param string $tableName * * @return array[string] */ public function getPrimaryKey($tableName) { if (!isset($this->primaryKeys[$tableName])) { $primaryKey = []; $stmt = $this->getDbh()->query( 'SHOW KEYS FROM ' . $this->getQuotedName($tableName) . ' WHERE Key_name = "PRIMARY"' ); $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ($columns as $column) { $primaryKey []= $column['Column_name']; } $this->primaryKeys[$tableName] = $primaryKey; } return $this->primaryKeys[$tableName]; } } <?php namespace Codeception\Lib\Driver; use Codeception\Exception\TestRuntime; use Codeception\Lib\Interfaces\Queue; use Aws\Sqs\SqsClient; use Aws\Common\Credentials\Credentials; class AmazonSQS implements Queue { protected $queue; /** * Connect to the queueing server. (AWS, Iron.io and Beanstalkd) * @param array $config * @return */ public function openConnection($config) { $params = [ 'region' => $config['region'] ]; if (! empty($config['key']) && ! empty($config['secret'])) { $params['credentials'] = new Credentials($config['key'], $config['secret']); } if (! empty($config['profile'])) { $params['profile'] = $config['profile']; } $this->queue = SqsClient::factory($params); if (!$this->queue) { throw new TestRuntime('connection failed or timed-out.'); } } /** * Post/Put a message on to the queue server * * @param string $message Message Body to be send * @param string $queue Queue Name */ public function addMessageToQueue($message, $queue) { $this->queue->sendMessage([ 'QueueUrl' => $this->getQueueURL($queue), 'MessageBody' => $message, ]); } /** * Return a list of queues/tubes on the queueing server * * @return array Array of Queues */ public function getQueues() { $queueNames = []; $queues = $this->queue->listQueues(['QueueNamePrefix' => ''])->get('QueueUrls'); foreach ($queues as $queue) { $tokens = explode('/', $queue); $queueNames[] = $tokens[sizeof($tokens) - 1]; } return $queueNames; } /** * Count the current number of messages on the queue. * * @param $queue Queue Name * * @return int Count */ public function getMessagesCurrentCountOnQueue($queue) { return $this->queue->getQueueAttributes([ 'QueueUrl' => $this->getQueueURL($queue), 'AttributeNames' => ['ApproximateNumberOfMessages'], ])->get('Attributes')['ApproximateNumberOfMessages']; } /** * Count the total number of messages on the queue. * * @param $queue Queue Name * * @return int Count */ public function getMessagesTotalCountOnQueue($queue) { return $this->queue->getQueueAttributes([ 'QueueUrl' => $this->getQueueURL($queue), 'AttributeNames' => ['ApproximateNumberOfMessages'], ])->get('Attributes')['ApproximateNumberOfMessages']; } public function clearQueue($queue) { $queueURL = $this->getQueueURL($queue); while (true) { $res = $this->queue->receiveMessage(['QueueUrl' => $queueURL]); if (!$res->getPath('Messages')) { return; } foreach ($res->getPath('Messages') as $msg) { $this->queue->deleteMessage([ 'QueueUrl' => $queueURL, 'ReceiptHandle' => $msg['ReceiptHandle'] ]); } } } /** * Get the queue/tube URL from the queue name (AWS function only) * * @param $queue Queue Name * * @return string Queue URL */ private function getQueueURL($queue) { $queues = $this->queue->listQueues(['QueueNamePrefix' => ''])->get('QueueUrls'); foreach ($queues as $queueURL) { $tokens = explode('/', $queueURL); if (strtolower($queue) == strtolower($tokens[sizeof($tokens) - 1])) { return $queueURL; } } throw new TestRuntime('queue [' . $queue . '] not found'); } public function getRequiredConfig() { return ['region']; } public function getDefaultConfig() { return []; } } <?php namespace Codeception\Lib\Driver; use Codeception\Lib\Interfaces\Queue; use Pheanstalk\Pheanstalk; use Pheanstalk\Exception\ConnectionException; class Beanstalk implements Queue { /** * @var Pheanstalk */ protected $queue; public function openConnection($config) { $this->queue = new Pheanstalk($config['host'], $config['port'], $config['timeout']); } /** * Post/Put a message on to the queue server * * @param string $message Message Body to be send * @param string $queue Queue Name */ public function addMessageToQueue($message, $queue) { $this->queue->putInTube($queue, $message); } /** * Count the total number of messages on the queue. * * @param $queue Queue Name * * @return int Count */ public function getMessagesTotalCountOnQueue($queue) { try { return $this->queue->statsTube($queue)['total-jobs']; } catch (ConnectionException $ex) { \PHPUnit_Framework_Assert::fail("queue [$queue] not found"); } } public function clearQueue($queue = 'default') { while ($job = $this->queue->reserveFromTube($queue, 0)) { $this->queue->delete($job); } } /** * Return a list of queues/tubes on the queueing server * * @return array Array of Queues */ public function getQueues() { return $this->queue->listTubes(); } /** * Count the current number of messages on the queue. * * @param $queue Queue Name * * @return int Count */ public function getMessagesCurrentCountOnQueue($queue) { try { return $this->queue->statsTube($queue)['current-jobs-ready']; } catch (ConnectionException $e) { \PHPUnit_Framework_Assert::fail("queue [$queue] not found"); } } public function getRequiredConfig() { return ['host']; } public function getDefaultConfig() { return ['port' => 11300, 'timeout' => 90]; } } <?php namespace Codeception\Lib\Driver; use Codeception\Exception\ModuleException; class Db { /** * @var \PDO */ protected $dbh; /** * @var string */ protected $dsn; protected $user; protected $password; /** * associative array with table name => primary-key * * @var array */ protected $primaryKeys = []; public static function connect($dsn, $user, $password) { $dbh = new \PDO($dsn, $user, $password); $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); return $dbh; } /** * @static * * @param $dsn * @param $user * @param $password * * @return Db|SqlSrv|MySql|Oci|PostgreSql|Sqlite */ public static function create($dsn, $user, $password) { $provider = self::getProvider($dsn); switch ($provider) { case 'sqlite': return new Sqlite($dsn, $user, $password); case 'mysql': return new MySql($dsn, $user, $password); case 'pgsql': return new PostgreSql($dsn, $user, $password); case 'mssql': case 'dblib': case 'sqlsrv': return new SqlSrv($dsn, $user, $password); case 'oci': return new Oci($dsn, $user, $password); default: return new Db($dsn, $user, $password); } } public static function getProvider($dsn) { return substr($dsn, 0, strpos($dsn, ':')); } public function __construct($dsn, $user, $password) { $this->dbh = new \PDO($dsn, $user, $password); $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->dsn = $dsn; $this->user = $user; $this->password = $password; } public function getDbh() { return $this->dbh; } public function getDb() { $matches = []; $matched = preg_match('~dbname=(\w+)~s', $this->dsn, $matches); if (!$matched) { return false; } return $matches[1]; } public function cleanup() { } public function load($sql) { $query = ''; $delimiter = ';'; $delimiterLength = 1; foreach ($sql as $sqlLine) { if (preg_match('/DELIMITER ([\;\$\|\\\\]+)/i', $sqlLine, $match)) { $delimiter = $match[1]; $delimiterLength = strlen($delimiter); continue; } $parsed = $this->sqlLine($sqlLine); if ($parsed) { continue; } $query .= "\n" . rtrim($sqlLine); if (substr($query, -1 * $delimiterLength, $delimiterLength) == $delimiter) { $this->sqlQuery(substr($query, 0, -1 * $delimiterLength)); $query = ''; } } if ($query !== '') { $this->sqlQuery($query); } } public function insert($tableName, array &$data) { $columns = array_map( [$this, 'getQuotedName'], array_keys($data) ); return sprintf( "INSERT INTO %s (%s) VALUES (%s)", $this->getQuotedName($tableName), implode(', ', $columns), implode(', ', array_fill(0, count($data), '?')) ); } public function select($column, $table, array &$criteria) { $where = $this->generateWhereClause($criteria); $query = "SELECT %s FROM %s %s"; return sprintf($query, $column, $this->getQuotedName($table), $where); } protected function generateWhereClause(array &$criteria) { if (empty($criteria)) { return ''; } $params = []; foreach ($criteria as $k => $v) { if ($v === null) { $params[] = $this->getQuotedName($k) . " IS NULL "; unset($criteria[$k]); } elseif (strpos(strtolower($k), ' like') > 0) { $k = str_replace(' like', '', strtolower($k)); $params[] = $this->getQuotedName($k) . " LIKE ? "; } else { $params[] = $this->getQuotedName($k) . " = ? "; } } return 'WHERE ' . implode('AND ', $params); } /** * @deprecated use deleteQueryByCriteria instead */ public function deleteQuery($table, $id, $primaryKey = 'id') { $query = 'DELETE FROM ' . $this->getQuotedName($table) . ' WHERE ' . $this->getQuotedName($primaryKey) . ' = ?'; $this->executeQuery($query, [$id]); } public function deleteQueryByCriteria($table, array $criteria) { $where = $this->generateWhereClause($criteria); $query = 'DELETE FROM ' . $this->getQuotedName($table) . ' ' . $where; $this->executeQuery($query, array_values($criteria)); } public function lastInsertId($table) { return $this->getDbh()->lastInsertId(); } public function getQuotedName($name) { return '"' . str_replace('.', '"."', $name) . '"'; } protected function sqlLine($sql) { $sql = trim($sql); return ( $sql === '' || $sql === ';' || preg_match('~^((--.*?)|(#))~s', $sql) ); } protected function sqlQuery($query) { try { $this->dbh->exec($query); } catch (\PDOException $e) { throw new ModuleException( 'Codeception\Module\Db', $e->getMessage() . "\nSQL query being executed: " . $query ); } } public function executeQuery($query, array $params) { $sth = $this->dbh->prepare($query); if (!$sth) { throw new \Exception("Query '$query' can't be prepared."); } $i = 0; foreach ($params as $value) { $i++; if (is_bool($value)) { $type = \PDO::PARAM_BOOL; } elseif (is_int($value)) { $type = \PDO::PARAM_INT; } else { $type = \PDO::PARAM_STR; } $sth->bindValue($i, $value, $type); } $sth->execute(); return $sth; } /** * @param string $tableName * * @return string * @throws \Exception * @deprecated use getPrimaryKey instead */ public function getPrimaryColumn($tableName) { $primaryKey = $this->getPrimaryKey($tableName); if (empty($primaryKey)) { return null; } elseif (count($primaryKey) > 1) { throw new \Exception( 'getPrimaryColumn method does not support composite primary keys, use getPrimaryKey instead' ); } return $primaryKey[0]; } /** * @param string $tableName * * @return array[string] */ public function getPrimaryKey($tableName) { return []; } /** * @return bool */ protected function flushPrimaryColumnCache() { $this->primaryKeys = []; return empty($this->primaryKeys); } } <?php namespace Codeception\Lib\Driver; use Codeception\Lib\Interfaces\Queue; class Iron implements Queue { /** * @var \IronMQ */ protected $queue; /** * Connect to the queueing server. (AWS, Iron.io and Beanstalkd) * @param array $config * @return */ public function openConnection($config) { $this->queue = new \IronMQ([ "token" => $config['token'], "project_id" => $config['project'], "host" => $config['host'] ]); if (!$this->queue) { \PHPUnit_Framework_Assert::fail('connection failed or timed-out.'); } } /** * Post/Put a message on to the queue server * * @param string $message Message Body to be send * @param string $queue Queue Name */ public function addMessageToQueue($message, $queue) { $this->queue->postMessage($queue, $message); } /** * Return a list of queues/tubes on the queueing server * * @return array Array of Queues */ public function getQueues() { // Format the output to suit $queues = []; foreach ($this->queue->getQueues() as $queue) { $queues[] = $queue->name; } return $queues; } /** * Count the current number of messages on the queue. * * @param $queue Queue Name * * @return int Count */ public function getMessagesCurrentCountOnQueue($queue) { try { return $this->queue->getQueue($queue)->size; } catch (\Http_Exception $ex) { \PHPUnit_Framework_Assert::fail("queue [$queue] not found"); } } /** * Count the total number of messages on the queue. * * @param $queue Queue Name * * @return int Count */ public function getMessagesTotalCountOnQueue($queue) { try { return $this->queue->getQueue($queue)->total_messages; } catch (\Http_Exception $e) { \PHPUnit_Framework_Assert::fail("queue [$queue] not found"); } } public function clearQueue($queue) { try { $this->queue->clearQueue($queue); } catch (\Http_Exception $ex) { \PHPUnit_Framework_Assert::fail("queue [$queue] not found"); } } public function getRequiredConfig() { return ['host', 'token', 'project']; } public function getDefaultConfig() { return []; } } <?php namespace Codeception\Lib\Driver; class SqlSrv extends Db { public function getDb() { $matches = []; $matched = preg_match('~Database=(.*);~s', $this->dsn, $matches); if (!$matched) { return false; } return $matches[1]; } public function cleanup() { $this->dbh->exec( " DECLARE constraints_cursor CURSOR FOR SELECT name, parent_object_id FROM sys.foreign_keys; OPEN constraints_cursor DECLARE @constraint sysname; DECLARE @parent int; DECLARE @table nvarchar(128); FETCH NEXT FROM constraints_cursor INTO @constraint, @parent; WHILE (@@FETCH_STATUS <> -1) BEGIN SET @table = OBJECT_NAME(@parent) EXEC ('ALTER TABLE [' + @table + '] DROP CONSTRAINT [' + @constraint + ']') FETCH NEXT FROM constraints_cursor INTO @constraint, @parent; END DEALLOCATE constraints_cursor;" ); $this->dbh->exec( " DECLARE tables_cursor CURSOR FOR SELECT name FROM sysobjects WHERE type = 'U'; OPEN tables_cursor DECLARE @tablename sysname; FETCH NEXT FROM tables_cursor INTO @tablename; WHILE (@@FETCH_STATUS <> -1) BEGIN EXEC ('DROP TABLE [' + @tablename + ']') FETCH NEXT FROM tables_cursor INTO @tablename; END DEALLOCATE tables_cursor;" ); } protected function generateWhereClause(array &$criteria) { if (empty($criteria)) { return ''; } $params = []; foreach ($criteria as $k => $v) { if (strpos(strtolower($k), ' like') > 0) { $k = str_replace(' like', '', strtolower($k)); $params[] = $this->getQuotedName($k) . " LIKE ? "; } else { $params[] = $this->getQuotedName($k) . " = ? "; } } return 'WHERE ' . implode('AND ', $params); } public function getQuotedName($name) { return '[' . $name . ']'; } /** * @param string $tableName * * @return array[string] */ public function getPrimaryKey($tableName) { if (!isset($this->primaryKeys[$tableName])) { $primaryKey = []; $query = " SELECT Col.Column_Name from INFORMATION_SCHEMA.TABLE_CONSTRAINTS Tab, INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE Col WHERE Col.Constraint_Name = Tab.Constraint_Name AND Col.Table_Name = Tab.Table_Name AND Constraint_Type = 'PRIMARY KEY' AND Col.Table_Name = ?"; $stmt = $this->executeQuery($query, [$tableName]); $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ($columns as $column) { $primaryKey []= $column['Column_Name']; } $this->primaryKeys[$tableName] = $primaryKey; } return $this->primaryKeys[$tableName]; } } <?php /** * @author tiger */ namespace Codeception\Lib\Driver; use Facebook\Facebook as FacebookSDK; class Facebook { /** * @var callable */ protected $logCallback; /** * @var FacebookSDK */ protected $fb; /** * @var string */ protected $appId; /** * @var string */ protected $appSecret; /** * @var string */ protected $appToken; /** * Facebook constructor. * * @param array $config * @param callable|null $logCallback */ public function __construct($config, $logCallback = null) { if (is_callable($logCallback)) { $this->logCallback = $logCallback; } $this->fb = new FacebookSDK( [ 'app_id' => $config['app_id'], 'app_secret' => $config['secret'], 'default_graph_version' => 'v2.5', //TODO add to config ] ); $this->appId = $config['app_id']; $this->appSecret = $config['secret']; $this->appToken = $this->appId . '|' . $this->appSecret; } /** * @param string $name * @param array $permissions * * @return array */ public function createTestUser($name, array $permissions) { $response = $this->executeFacebookRequest( 'POST', $this->appId . '/accounts/test-users', [ 'name' => $name, 'installed' => true, 'permissions' => $permissions ] ); return $response->getDecodedBody(); } public function deleteTestUser($testUserID) { $this->executeFacebookRequest('DELETE', '/' . $testUserID); } public function getTestUserInfo($testUserAccessToken) { $response = $this->executeFacebookRequest( 'GET', '/me', $parameters = [], $testUserAccessToken ); return $response->getDecodedBody(); } public function getLastPostsForTestUser($testUserAccessToken) { $response = $this->executeFacebookRequest( 'GET', '/me/feed', $parameters = [], $testUserAccessToken ); return $response->getDecodedBody(); } public function getVisitedPlaceTagForTestUser($placeId, $testUserAccessToken) { $response = $this->executeFacebookRequest( 'GET', "/$placeId", $parameters = [], $testUserAccessToken ); return $response->getDecodedBody(); } public function sendPostToFacebook($testUserAccessToken, array $parameters) { $response = $this->executeFacebookRequest( 'POST', '/me/feed', $parameters, $testUserAccessToken ); return $response->getDecodedBody(); } /** * @param string $method * @param string $endpoint * @param array $parameters * @param string $token * * @return \Facebook\FacebookResponse */ private function executeFacebookRequest($method, $endpoint, array $parameters = [], $token = null) { if (is_callable($this->logCallback)) { //used only for debugging: call_user_func($this->logCallback, 'Facebook API request', func_get_args()); } if (!$token) { $token = $this->appToken; } switch ($method) { case 'GET': $response = $this->fb->get($endpoint, $token); break; case 'POST': $response = $this->fb->post($endpoint, $parameters, $token); break; case 'DELETE': $response = $this->fb->delete($endpoint, $parameters, $token); break; default: throw new \Exception("Facebook driver exception, please add support for method: " . $method); break; } if (is_callable($this->logCallback)) { call_user_func($this->logCallback, 'Facebook API response', $response->getDecodedBody()); } return $response; } } <?php namespace Codeception\Coverage; class DummyCodeCoverage extends \PHP_CodeCoverage { public function start($id, $clear = false) { } public function stop($append = true, $linesToBeCovered = [], array $linesToBeUsed = []) { } } <?php namespace Codeception\Coverage; use Codeception\Configuration; use Codeception\Exception\ConfigurationException; use Codeception\Exception\ModuleException; use Symfony\Component\Finder\Finder; class Filter { /** * @var \PHP_CodeCoverage */ protected $phpCodeCoverage = null; /** * @var Filter */ protected static $c3; /** * @var \PHP_CodeCoverage_Filter */ protected $filter = null; public function __construct(\PHP_CodeCoverage $phpCoverage) { $this->phpCodeCoverage = $phpCoverage ? $phpCoverage : new \PHP_CodeCoverage; $this->filter = $this->phpCodeCoverage->filter(); } /** * @param \PHP_CodeCoverage $phpCoverage * @return Filter */ public static function setup(\PHP_CodeCoverage $phpCoverage) { self::$c3 = new self($phpCoverage); return self::$c3; } /** * @return null|\PHP_CodeCoverage */ public function getPhpCodeCoverage() { return $this->phpCodeCoverage; } /** * @param $config * @return Filter */ public function whiteList($config) { $filter = $this->filter; if (!isset($config['coverage'])) { return $this; } $coverage = $config['coverage']; if (!isset($coverage['whitelist'])) { $coverage['whitelist'] = []; if (isset($coverage['include'])) { $coverage['whitelist']['include'] = $coverage['include']; } if (isset($coverage['exclude'])) { $coverage['whitelist']['exclude'] = $coverage['exclude']; } } if (isset($coverage['whitelist']['include'])) { if (!is_array($coverage['whitelist']['include'])) { throw new ConfigurationException('Error parsing yaml. Config `whitelist: include:` should be an array'); } foreach ($coverage['whitelist']['include'] as $fileOrDir) { $finder = strpos($fileOrDir, '*') === false ? [Configuration::projectDir() . DIRECTORY_SEPARATOR . $fileOrDir] : $this->matchWildcardPattern($fileOrDir); foreach ($finder as $file) { $filter->addFileToWhitelist($file); } } } if (isset($coverage['whitelist']['exclude'])) { if (!is_array($coverage['whitelist']['exclude'])) { throw new ConfigurationException('Error parsing yaml. Config `whitelist: exclude:` should be an array'); } foreach ($coverage['whitelist']['exclude'] as $fileOrDir) { $finder = strpos($fileOrDir, '*') === false ? [Configuration::projectDir() . DIRECTORY_SEPARATOR . $fileOrDir] : $this->matchWildcardPattern($fileOrDir); foreach ($finder as $file) { $filter->removeFileFromWhitelist($file); } } } return $this; } /** * @param $config * @return Filter */ public function blackList($config) { $filter = $this->filter; if (!isset($config['coverage'])) { return $this; } $coverage = $config['coverage']; if (isset($coverage['blacklist'])) { if (!method_exists($filter, 'addFileToBlacklist')) { throw new ModuleException($this, 'The blacklist functionality has been removed from PHPUnit 5,' . ' please remove blacklist section from configuration.'); } if (isset($coverage['blacklist']['include'])) { foreach ($coverage['blacklist']['include'] as $fileOrDir) { $finder = strpos($fileOrDir, '*') === false ? [Configuration::projectDir() . DIRECTORY_SEPARATOR . $fileOrDir] : $this->matchWildcardPattern($fileOrDir); foreach ($finder as $file) { $filter->addFileToBlacklist($file); } } } if (isset($coverage['blacklist']['exclude'])) { foreach ($coverage['blacklist']['exclude'] as $fileOrDir) { $finder = strpos($fileOrDir, '*') === false ? [Configuration::projectDir() . DIRECTORY_SEPARATOR . $fileOrDir] : $this->matchWildcardPattern($fileOrDir); foreach ($finder as $file) { $filter->removeFileFromBlacklist($file); } } } } return $this; } protected function matchWildcardPattern($pattern) { $finder = Finder::create(); $fileOrDir = str_replace('\\', '/', $pattern); $parts = explode('/', $fileOrDir); $file = array_pop($parts); $finder->name($file); if (count($parts)) { $last_path = array_pop($parts); if ($last_path === '*') { $finder->in(Configuration::projectDir() . implode('/', $parts)); } else { $finder->in(Configuration::projectDir() . implode('/', $parts) . '/' . $last_path); } } $finder->ignoreVCS(true)->files(); return $finder; } /** * @return \PHP_CodeCoverage_Filter */ public function getFilter() { return $this->filter; } } <?php namespace Codeception\Coverage\Subscriber; use Codeception\Coverage\SuiteSubscriber; use Codeception\Event\SuiteEvent; use Codeception\Events; use Codeception\Lib\Interfaces\Remote; /** * Collects code coverage from unit and functional tests. * Results from all suites are merged. */ class Local extends SuiteSubscriber { public static $events = [ Events::SUITE_BEFORE => 'beforeSuite', Events::SUITE_AFTER => 'afterSuite', ]; /** * @var Remote */ protected $module; protected function isEnabled() { return $this->module === null and $this->settings['enabled']; } public function beforeSuite(SuiteEvent $e) { $this->applySettings($e->getSettings()); $this->module = $this->getServerConnectionModule($e->getSuite()->getModules()); if (!$this->isEnabled()) { return; } $this->applyFilter($e->getResult()); } public function afterSuite(SuiteEvent $e) { if (!$this->isEnabled()) { return; } $this->mergeToPrint($e->getResult()->getCodeCoverage()); } } <?php namespace Codeception\Coverage\Subscriber; use Codeception\Configuration; use Codeception\Event\SuiteEvent; use Codeception\Util\FileSystem; /** * When collecting code coverage on remote server * data is retrieved over HTTP and not merged with the local code coverage results. * * Class RemoteServer * @package Codeception\Coverage\Subscriber */ class RemoteServer extends LocalServer { public function isEnabled() { return $this->module and $this->settings['remote'] and $this->settings['enabled']; } public function afterSuite(SuiteEvent $e) { if (!$this->isEnabled()) { return; } $suite = $e->getSuite()->getName(); if ($this->options['coverage-xml']) { $this->retrieveAndPrintXml($suite); } if ($this->options['coverage-html']) { $this->retrieveAndPrintHtml($suite); } if ($this->options['coverage-crap4j']) { $this->retrieveAndPrintCrap4j($suite); } } protected function retrieveAndPrintHtml($suite) { $tempFile = tempnam(sys_get_temp_dir(), 'C3') . '.tar'; file_put_contents($tempFile, $this->c3Request('html')); $destDir = Configuration::outputDir() . $suite . '.remote.coverage'; if (is_dir($destDir)) { FileSystem::doEmptyDir($destDir); } else { mkdir($destDir, 0777, true); } $phar = new \PharData($tempFile); $phar->extractTo($destDir); unlink($tempFile); } protected function retrieveAndPrintXml($suite) { $destFile = Configuration::outputDir() . $suite . '.remote.coverage.xml'; file_put_contents($destFile, $this->c3Request('clover')); } protected function retrieveAndPrintCrap4j($suite) { $destFile = Configuration::outputDir() . $suite . '.remote.crap4j.xml'; file_put_contents($destFile, $this->c3Request('crap4j')); } } <?php namespace Codeception\Coverage\Subscriber; use Codeception\Configuration; use Codeception\Coverage\Filter; use Codeception\Event\PrintResultEvent; use Codeception\Events; use Codeception\Subscriber\Shared\StaticEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class Printer implements EventSubscriberInterface { use StaticEvents; public static $events = [ Events::RESULT_PRINT_AFTER => 'printResult' ]; protected $settings = [ 'enabled' => true, 'low_limit' => '35', 'high_limit' => '70', 'show_uncovered' => false ]; public static $coverage; protected $options; protected $logDir; protected $destination = []; public function __construct($options) { $this->options = $options; $this->logDir = Configuration::outputDir(); $this->settings = array_merge($this->settings, Configuration::config()['coverage']); self::$coverage = new \PHP_CodeCoverage(); // Apply filter $filter = new Filter(self::$coverage); $filter ->whiteList(Configuration::config()) ->blackList(Configuration::config()); } protected function absolutePath($path) { if ((strpos($path, '/') === 0) || (strpos($path, ':') === 1)) { // absolute path return $path; } return $this->logDir . $path; } public function printResult(PrintResultEvent $e) { $printer = $e->getPrinter(); if (!$this->settings['enabled']) { $printer->write("\nCodeCoverage is disabled in `codeception.yml` config\n"); return; } if (!$this->options['quiet']) { $this->printConsole($printer); } $printer->write("Remote CodeCoverage reports are not printed to console\n"); $this->printPHP(); $printer->write("\n"); if ($this->options['coverage-html']) { $this->printHtml(); $printer->write("HTML report generated in {$this->options['coverage-html']}\n"); } if ($this->options['coverage-xml']) { $this->printXml(); $printer->write("XML report generated in {$this->options['coverage-xml']}\n"); } if ($this->options['coverage-text']) { $this->printText(); $printer->write("Text report generated in {$this->options['coverage-text']}\n"); } if ($this->options['coverage-crap4j']) { $this->printCrap4j(); $printer->write("Crap4j report generated in {$this->options['coverage-crap4j']}\n"); } } protected function printConsole(\PHPUnit_Util_Printer $printer) { $writer = new \PHP_CodeCoverage_Report_Text( $this->settings['low_limit'], $this->settings['high_limit'], $this->settings['show_uncovered'], false ); $printer->write($writer->process(self::$coverage, $this->options['colors'])); } protected function printHtml() { $writer = new \PHP_CodeCoverage_Report_HTML( $this->settings['low_limit'], $this->settings['high_limit'], sprintf( ', <a target="_top" href="/newspapers?url=http://codeception.com">Codeception</a> and <a target="_top" href="/newspapers?url=http://phpunit.de/">PHPUnit %s</a>', \PHPUnit_Runner_Version::id() ) ); $writer->process(self::$coverage, $this->absolutePath($this->options['coverage-html'])); } protected function printXml() { $writer = new \PHP_CodeCoverage_Report_Clover; $writer->process(self::$coverage, $this->absolutePath($this->options['coverage-xml'])); } protected function printPHP() { $writer = new \PHP_CodeCoverage_Report_PHP; $writer->process(self::$coverage, $this->absolutePath($this->options['coverage'])); } protected function printText() { $writer = new \PHP_CodeCoverage_Report_Text( $this->settings['low_limit'], $this->settings['high_limit'], $this->settings['show_uncovered'], false ); file_put_contents( $this->absolutePath($this->options['coverage-text']), $writer->process(self::$coverage, false) ); } protected function printCrap4j() { $writer = new \PHP_CodeCoverage_Report_Crap4j; $writer->process(self::$coverage, $this->absolutePath($this->options['coverage-crap4j'])); } } <?php namespace Codeception\Coverage\Subscriber; use Codeception\Configuration; use Codeception\Coverage\SuiteSubscriber; use Codeception\Event\StepEvent; use Codeception\Event\SuiteEvent; use Codeception\Event\TestEvent; use Codeception\Events; use Codeception\Exception\ModuleException; use Codeception\Exception\RemoteException; /** * When collecting code coverage data from local server HTTP requests are sent to c3.php file. * Coverage Collection is started by sending cookies/headers. * Result is taken from the local file and merged with local code coverage results. * * Class LocalServer * @package Codeception\Coverage\Subscriber */ class LocalServer extends SuiteSubscriber { // headers const COVERAGE_HEADER = 'X-Codeception-CodeCoverage'; const COVERAGE_HEADER_ERROR = 'X-Codeception-CodeCoverage-Error'; const COVERAGE_HEADER_CONFIG = 'X-Codeception-CodeCoverage-Config'; const COVERAGE_HEADER_SUITE = 'X-Codeception-CodeCoverage-Suite'; // cookie names const COVERAGE_COOKIE = 'CODECEPTION_CODECOVERAGE'; const COVERAGE_COOKIE_ERROR = 'CODECEPTION_CODECOVERAGE_ERROR'; protected $suiteName; protected $c3Access = [ 'http' => [ 'method' => "GET", 'header' => '' ] ]; /** * @var \Codeception\Lib\Interfaces\Web */ protected $module; public static $events = [ Events::SUITE_BEFORE => 'beforeSuite', Events::TEST_BEFORE => 'beforeTest', Events::STEP_AFTER => 'afterStep', Events::SUITE_AFTER => 'afterSuite', ]; protected function isEnabled() { return $this->module && !$this->settings['remote'] && $this->settings['enabled']; } public function beforeSuite(SuiteEvent $e) { $this->module = $this->getServerConnectionModule($e->getSuite()->getModules()); $this->applySettings($e->getSettings()); if (!$this->isEnabled()) { return; } $this->suiteName = $e->getSuite()->getBaseName(); if ($this->settings['remote_config']) { $this->addC3AccessHeader(self::COVERAGE_HEADER_CONFIG, $this->settings['remote_config']); } $knock = $this->c3Request('clear'); if ($knock === false) { throw new RemoteException( ' CodeCoverage Error. Check the file "c3.php" is included in your application. We tried to access "/c3/report/clear" but this URI was not accessible. You can review actual error messages in c3tmp dir. ' ); } } public function beforeTest(TestEvent $e) { if (!$this->isEnabled()) { return; } $this->startCoverageCollection($e->getTest()->getName()); } public function afterStep(StepEvent $e) { if (!$this->isEnabled()) { return; } $this->fetchErrors(); } public function afterSuite(SuiteEvent $e) { if (!$this->isEnabled()) { return; } $coverageFile = Configuration::outputDir() . 'c3tmp/codecoverage.serialized'; $retries = 5; while (!file_exists($coverageFile) && --$retries >= 0) { usleep(0.5 * 1000000); // 0.5 sec } if (!file_exists($coverageFile)) { if (file_exists(Configuration::outputDir() . 'c3tmp/error.txt')) { throw new \RuntimeException(file_get_contents(Configuration::outputDir() . 'c3tmp/error.txt')); } return; } $contents = file_get_contents($coverageFile); $coverage = @unserialize($contents); if ($coverage === false) { return; } $this->mergeToPrint($coverage); } protected function c3Request($action) { $this->addC3AccessHeader(self::COVERAGE_HEADER, 'remote-access'); $context = stream_context_create($this->c3Access); $c3Url = $this->settings['c3_url'] ? $this->settings['c3_url'] : $this->module->_getUrl(); $contents = file_get_contents($c3Url . '/c3/report/' . $action, false, $context); $okHeaders = array_filter( $http_response_header, function ($h) { return preg_match('~^HTTP(.*?)\s200~', $h); } ); if (empty($okHeaders)) { throw new RemoteException("Request was not successful. See response header: " . $http_response_header[0]); } if ($contents === false) { $this->getRemoteError($http_response_header); } return $contents; } protected function startCoverageCollection($testName) { $value = [ 'CodeCoverage' => $testName, 'CodeCoverage_Suite' => $this->suiteName, 'CodeCoverage_Config' => $this->settings['remote_config'] ]; $value = json_encode($value); $this->module->amOnPage('/'); $this->module->setCookie(self::COVERAGE_COOKIE, $value); // putting in configuration ensures the cookie is used for all sessions of a MultiSession test $cookies = $this->module->_getConfig('cookies'); if (!$cookies || !is_array($cookies)) { $cookies = []; } $found = false; foreach ($cookies as &$cookie) { if (!is_array($cookie) || !array_key_exists('Name', $cookie) || !array_key_exists('Value', $cookie)) { // \Codeception\Lib\InnerBrowser will complain about this continue; } if ($cookie['Name'] === self::COVERAGE_COOKIE) { $found = true; $cookie['Value'] = $value; break; } } if (!$found) { $cookies[] = [ 'Name' => self::COVERAGE_COOKIE, 'Value' => $value ]; } $this->module->_setConfig(['cookies' => $cookies]); } protected function fetchErrors() { try { $error = $this->module->grabCookie(self::COVERAGE_COOKIE_ERROR); } catch (ModuleException $e) { // when a new session is started we can't get cookies because there is no // current page, but there can be no code coverage error either $error = null; } if (!empty($error)) { $this->module->resetCookie(self::COVERAGE_COOKIE_ERROR); throw new RemoteException($error); } } protected function getRemoteError($headers) { foreach ($headers as $header) { if (strpos($header, self::COVERAGE_HEADER_ERROR) === 0) { throw new RemoteException($header); } } } protected function addC3AccessHeader($header, $value) { $headerString = "$header: $value\r\n"; if (strpos($this->c3Access['http']['header'], $headerString) === false) { $this->c3Access['http']['header'] .= $headerString; } } protected function applySettings($settings) { parent::applySettings($settings); if (isset($settings['coverage']['remote_context_options'])) { $this->c3Access = array_replace_recursive($this->c3Access, $settings['coverage']['remote_context_options']); } } } <?php namespace Codeception\Coverage; use Codeception\Configuration; use Codeception\Coverage\Subscriber\Printer; use Codeception\Lib\Interfaces\Remote; use Codeception\Subscriber\Shared\StaticEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; abstract class SuiteSubscriber implements EventSubscriberInterface { use StaticEvents; protected $defaultSettings = [ 'enabled' => false, 'remote' => false, 'local' => false, 'xdebug_session' => 'codeception', 'remote_config' => null, 'show_uncovered' => false, 'c3_url' => null ]; protected $settings = []; protected $filters = []; protected $modules = []; protected $coverage; protected $logDir; protected $options; public static $events = []; abstract protected function isEnabled(); public function __construct($options = []) { $this->options = $options; $this->logDir = Configuration::outputDir(); } protected function applySettings($settings) { try { $this->coverage = new \PHP_CodeCoverage(); } catch (\PHP_CodeCoverage_Exception $e) { throw new \Exception( 'XDebug is required to collect CodeCoverage. Please install xdebug extension and enable it in php.ini' ); } $this->filters = $settings; $this->settings = $this->defaultSettings; $keys = array_keys($this->defaultSettings); foreach ($keys as $key) { if (isset($settings['coverage'][$key])) { $this->settings[$key] = $settings['coverage'][$key]; } } $this->coverage->setProcessUncoveredFilesFromWhitelist($this->settings['show_uncovered']); } /** * @param array $modules * @return \Codeception\Lib\Interfaces\Remote|null */ protected function getServerConnectionModule(array $modules) { foreach ($modules as $module) { if ($module instanceof Remote) { return $module; } } return null; } public function applyFilter(\PHPUnit_Framework_TestResult $result) { $result->setCodeCoverage(new DummyCodeCoverage()); Filter::setup($this->coverage) ->whiteList($this->filters) ->blackList($this->filters); $result->setCodeCoverage($this->coverage); } protected function mergeToPrint($coverage) { Printer::$coverage->merge($coverage); } } <?php namespace Codeception\Template; use Codeception\InitTemplate; use Codeception\Util\Template; use Symfony\Component\Yaml\Yaml; class Unit extends InitTemplate { protected $configTemplate = <<<EOF suites: unit: path: . {{tester}} settings: shuffle: true lint: true paths: tests: {{dir}} output: {{dir}}/_output support: {{dir}}/_support data: {{dir}} EOF; protected $testerAndModules = <<<EOF actor: UnitTester modules: enabled: # add more modules here - Asserts EOF; public function setup() { $this->sayInfo('This will install Codeception for unit testing only'); $this->say(); $dir = $this->ask("Where tests will be stored?", 'tests'); if (!$this->namespace) { $this->namespace = $this->ask("Enter a default namespace for tests (or skip this step)"); } $this->say(); $this->say("Codeception provides additional features for integration tests"); $this->say("Like accessing frameworks, ORM, Database."); $haveTester = $this->ask("Do you wish to enable them?", false); $this->createEmptyDirectory($outputDir = $dir . DIRECTORY_SEPARATOR . '_output'); $this->createEmptyDirectory($supportDir = $dir . DIRECTORY_SEPARATOR . '_support'); $configFile = (new Template($this->configTemplate)) ->place('dir', $dir) ->place('tester', $haveTester ? $this->testerAndModules : '') ->produce(); if ($this->namespace) { $namespace = rtrim($this->namespace, '\\'); $configFile = "namespace: $namespace\n" . $configFile; } $this->createFile('codeception.yml', $configFile); if ($haveTester) { $this->createHelper('Unit', $supportDir); $this->createActor('UnitTester', $supportDir, Yaml::parse($configFile)['suites']['unit']); } $this->gitIgnore($outputDir); $this->sayInfo("Created test directory inside at $dir"); $this->say(); $this->saySuccess("INSTALLATION COMPLETE"); $this->say(); $this->say('Unit tests will be executed in random order'); $this->say('Use @depends annotation to change the order of tests'); if ($haveTester) { $this->say('To access DI, ORM, Database enable corresponding modules in codeception.yml'); $this->say('Use <bold>$this->tester</bold> object inside Codeception\Test\Unit to call their methods'); $this->say("For example: \$this->tester->seeInDatabase('users', ['name' => 'davert'])"); } $this->say(); $this->say("<bold>Next steps:</bold>"); $this->say("Create the first test using <comment>codecept g:test unit MyTest</comment>"); $this->say("Run tests with <comment>codecept run</comment>"); $this->say("<bold>Happy testing!</bold>"); } } <?php namespace Codeception\Template; use Codeception\InitTemplate; use Codeception\Util\Template; use Symfony\Component\Yaml\Yaml; class Api extends InitTemplate { protected $configTemplate = <<<EOF # suite config suites: api: actor: ApiTester path: . modules: enabled: - REST: url: {{url}} depends: PhpBrowser paths: tests: {{baseDir}} output: {{baseDir}}/_output data: {{baseDir}}/_data support: {{baseDir}}/_support settings: shuffle: false lint: true EOF; protected $firstTest = <<<EOF <?php class ApiCest { public function tryApi(ApiTester \$I) { \$I->sendGET('/'); \$I->seeResponseCodeIs(200); \$I->seeResponseIsJson(); } } EOF; public function setup() { $this->checkInstalled(); $this->say("Let's prepare Codeception for REST API testing"); $this->say(""); $dir = $this->ask("Where tests will be stored?", 'tests'); $url = $this->ask("Start url for tests", "http://localhost/api"); $this->createEmptyDirectory($outputDir = $dir . DIRECTORY_SEPARATOR . '_output'); $this->createEmptyDirectory($dir . DIRECTORY_SEPARATOR . '_data'); $this->createDirectoryFor($supportDir = $dir . DIRECTORY_SEPARATOR . '_support'); $this->gitIgnore($outputDir); $this->gitIgnore($supportDir . DIRECTORY_SEPARATOR . '_generated'); $this->sayInfo("Created test directories inside at $dir"); $configFile = (new Template($this->configTemplate)) ->place('url', $url) ->place('baseDir', $dir) ->produce(); if ($this->namespace) { $namespace = rtrim($this->namespace, '\\'); $configFile = "namespace: $namespace\n" . $configFile; } $this->createFile('codeception.yml', $configFile); $this->createHelper('Api', $supportDir); $this->createActor('ApiTester', $supportDir, Yaml::parse($configFile)['suites']['api']); $this->sayInfo("Created global config codeception.yml inside the root directory"); $this->createFile($dir . DIRECTORY_SEPARATOR . 'ApiCest.php', $this->firstTest); $this->sayInfo("Created a demo test ApiCest.php"); $this->say(); $this->saySuccess("INSTALLATION COMPLETE"); $this->say(); $this->say("<bold>Next steps:</bold>"); $this->say("1. Edit <bold>$dir/ApiCest.php</bold> to write first API tests"); $this->say("2. Run tests using: <comment>codecept run</comment>"); $this->say(); $this->say("<bold>Happy testing!</bold>"); } } <?php namespace Codeception\Template; use Codeception\InitTemplate; use Symfony\Component\Yaml\Yaml; class Bootstrap extends InitTemplate { // defaults protected $supportDir = 'tests/_support'; protected $outputDir = 'tests/_output'; protected $dataDir = 'tests/_data'; protected $envsDir = 'tests/_envs'; public function setup() { $this->checkInstalled($this->workDir); $input = $this->input; if ($input->getOption('namespace')) { $this->namespace = trim($input->getOption('namespace'), '\\') . '\\'; } if ($input->hasOption('actor') && $input->getOption('actor')) { $this->actorSuffix = $input->getOption('actor'); } $this->say( "<fg=white;bg=magenta> Bootstrapping Codeception </fg=white;bg=magenta>\n" ); $this->createGlobalConfig(); $this->say("File codeception.yml created <- global configuration"); $this->createDirs(); if ($input->hasOption('empty') && $input->getOption('empty')) { return; } $this->createUnitSuite(); $this->say("tests/unit created <- unit tests"); $this->say("tests/unit.suite.yml written <- unit tests suite configuration"); $this->createFunctionalSuite(); $this->say("tests/functional created <- functional tests"); $this->say("tests/functional.suite.yml written <- functional tests suite configuration"); $this->createAcceptanceSuite(); $this->say("tests/acceptance created <- acceptance tests"); $this->say("tests/acceptance.suite.yml written <- acceptance tests suite configuration"); $this->say(" --- "); $this->say(); $this->saySuccess('Codeception is installed for acceptance, functional, and unit testing'); $this->say(); $this->say("<bold>Next steps:</bold>"); $this->say('1. Edit <bold>tests/acceptance.suite.yml</bold> to set url of your application. Change PhpBrowser to WebDriver to enable browser testing'); $this->say("2. Edit <bold>tests/functional.suite.yml</bold> to enable a framework module. Remove this file if you don't use a framework"); $this->say("3. Create your first acceptance tests using <comment>codecept g:cest acceptance First</comment>"); $this->say("4. Write first test in <bold>tests/acceptance/FirstCest.php</bold>"); $this->say("5. Run tests using: <comment>codecept run</comment>"); } protected function createDirs() { $this->createDirectoryFor('tests'); $this->createEmptyDirectory($this->outputDir); $this->createEmptyDirectory($this->dataDir); $this->createDirectoryFor($this->supportDir . DIRECTORY_SEPARATOR . '_generated'); $this->createDirectoryFor($this->supportDir . DIRECTORY_SEPARATOR . "Helper"); $this->gitIgnore('tests/_output'); $this->gitIgnore('tests/_support/_generated'); } protected function createFunctionalSuite($actor = 'Functional') { $suiteConfig = <<<EOF # Codeception Test Suite Configuration # # Suite for functional tests # Emulate web requests and make application process them # Include one of framework modules (Symfony2, Yii2, Laravel5) to use it # Remove this suite if you don't use frameworks actor: $actor{$this->actorSuffix} modules: enabled: # add a framework module here - \\{$this->namespace}Helper\Functional EOF; $this->createSuite('functional', $actor, $suiteConfig); } protected function createAcceptanceSuite($actor = 'Acceptance') { $suiteConfig = <<<EOF # Codeception Test Suite Configuration # # Suite for acceptance tests. # Perform tests in browser using the WebDriver or PhpBrowser. # If you need both WebDriver and PHPBrowser tests - create a separate suite. actor: $actor{$this->actorSuffix} modules: enabled: - PhpBrowser: url: http://localhost/myapp - \\{$this->namespace}Helper\Acceptance EOF; $this->createSuite('acceptance', $actor, $suiteConfig); } protected function createUnitSuite($actor = 'Unit') { $suiteConfig = <<<EOF # Codeception Test Suite Configuration # # Suite for unit or integration tests. actor: $actor{$this->actorSuffix} modules: enabled: - Asserts - \\{$this->namespace}Helper\Unit EOF; $this->createSuite('unit', $actor, $suiteConfig); } public function createGlobalConfig() { $basicConfig = [ 'paths' => [ 'tests' => 'tests', 'output' => $this->outputDir, 'data' => $this->dataDir, 'support' => $this->supportDir, 'envs' => $this->envsDir, ], 'actor_suffix' => 'Tester', 'extensions' => [ 'enabled' => ['Codeception\Extension\RunFailed'] ] ]; $str = Yaml::dump($basicConfig, 4); if ($this->namespace) { $namespace = rtrim($this->namespace, '\\'); $str = "namespace: $namespace\n" . $str; } $this->createFile('codeception.yml', $str); } protected function createSuite($suite, $actor, $config) { $this->createDirectoryFor("tests/$suite", "$suite.suite.yml"); $this->createHelper($actor, $this->supportDir); $this->createActor($actor . $this->actorSuffix, $this->supportDir, Yaml::parse($config)); $this->createFile('tests' . DIRECTORY_SEPARATOR . "$suite.suite.yml", $config); } } <?php namespace Codeception\Template; use Codeception\InitTemplate; use Codeception\Util\Template; use Symfony\Component\Yaml\Yaml; class Acceptance extends InitTemplate { protected $configTemplate = <<<EOF # suite config suites: acceptance: actor: AcceptanceTester path: . modules: enabled: - WebDriver: url: {{url}} browser: {{browser}} - \Helper\Acceptance extensions: enabled: [Codeception\Extension\RunFailed] params: - env gherkin: [] # additional paths paths: tests: {{baseDir}} output: {{baseDir}}/_output data: {{baseDir}}/_data support: {{baseDir}}/_support envs: {{baseDir}}/_envs settings: shuffle: false lint: true EOF; protected $firstTest = <<<EOF <?php class LoginCest { public function _before(AcceptanceTester \$I) { \$I->amOnPage('/'); } public function loginSuccessfully(AcceptanceTester \$I) { // write a positive login test } public function loginWithInvalidPassword(AcceptanceTester \$I) { // write a negative login test } } EOF; public function setup() { $this->checkInstalled(); $this->say("Let's prepare Codeception for acceptance testing"); $this->say("Create your tests and run them in real browser"); $this->say(""); $dir = $this->ask("Where tests will be stored?", 'tests'); $browser = $this->ask("Select a browser for testing", ['chrome', 'phantomjs', 'firefox']); if ($browser === 'phantomjs') { $this->sayInfo("Ensure that you have Phantomjs running before starting tests"); } if ($browser === 'chrome') { $this->sayInfo("Ensure that you have Selenium Server and ChromeDriver installed before running tests"); } if ($browser === 'firefox') { $this->sayInfo("Ensure that you have Selenium Server and GeckoDriver installed before running tests"); } $url = $this->ask("Start url for tests", "http://localhost"); $this->createEmptyDirectory($outputDir = $dir . DIRECTORY_SEPARATOR . '_output'); $this->createEmptyDirectory($dir . DIRECTORY_SEPARATOR . '_data'); $this->createDirectoryFor($supportDir = $dir . DIRECTORY_SEPARATOR . '_support'); $this->gitIgnore($outputDir); $this->gitIgnore($supportDir . DIRECTORY_SEPARATOR . '_generated'); $this->sayInfo("Created test directories inside at $dir"); $configFile = (new Template($this->configTemplate)) ->place('url', $url) ->place('browser', $browser) ->place('baseDir', $dir) ->produce(); if ($this->namespace) { $namespace = rtrim($this->namespace, '\\'); $configFile = "namespace: $namespace\n" . $configFile; } $this->createFile('codeception.yml', $configFile); $this->createHelper('Acceptance', $supportDir); $this->createActor('AcceptanceTester', $supportDir, Yaml::parse($configFile)['suites']['acceptance']); $this->sayInfo("Created global config codeception.yml inside the root directory"); $this->createFile($dir . DIRECTORY_SEPARATOR . 'LoginCest.php', $this->firstTest); $this->sayInfo("Created a demo test LoginCest.php"); $this->say(); $this->saySuccess("INSTALLATION COMPLETE"); $this->say(); $this->say("<bold>Next steps:</bold>"); $this->say('1. Launch Selenium Server or PhantomJS and webserver'); $this->say("2. Edit <bold>$dir/LoginCest.php</bold> to test login of your application"); $this->say("3. Run tests using: <comment>codecept run</comment>"); $this->say(); $this->say("<bold>Happy testing!</bold>"); } } <?php namespace Codeception; use Codeception\Event\TestEvent; abstract class GroupObject extends Extension { public static $group; public function _before(TestEvent $e) { } public function _after(TestEvent $e) { } public static function getSubscribedEvents() { $inheritedEvents = parent::getSubscribedEvents(); $events = []; if (static::$group) { $events = [ Events::TEST_BEFORE . '.' . static::$group => '_before', Events::TEST_AFTER . '.' . static::$group => '_after', ]; } return array_merge($events, $inheritedEvents); } } <?php namespace Codeception\Util; /** * Holds matcher and value of mocked method */ class StubMarshaler { private $methodMatcher; private $methodValue; public function __construct(\PHPUnit_Framework_MockObject_Matcher_InvokedRecorder $matcher, $value) { $this->methodMatcher = $matcher; $this->methodValue = $value; } public function getMatcher() { return $this->methodMatcher; } public function getValue() { return $this->methodValue; } } <?php namespace Codeception\Util; class PropertyAccess { /** * @deprecated Use ReflectionHelper::readPrivateProperty() * @param object $object * @param string $property * @param string|null $class * @return mixed */ public static function readPrivateProperty($object, $property, $class = null) { return ReflectionHelper::readPrivateProperty($object, $property, $class); } } <?php use Codeception\Module\Sequence; if (!function_exists('sq')) { function sq($id = null) { if ($id and isset(Sequence::$hash[$id])) { return Sequence::$hash[$id]; } $prefix = str_replace('{id}', $id, Sequence::$prefix); $sequence = $prefix . uniqid($id); if ($id) { Sequence::$hash[$id] = $sequence; } return $sequence; } } if (!function_exists('sqs')) { function sqs($id = null) { if ($id and isset(Sequence::$suiteHash[$id])) { return Sequence::$suiteHash[$id]; } $prefix = str_replace('{id}', $id, Sequence::$prefix); $sequence = $prefix . uniqid($id); if ($id) { Sequence::$suiteHash[$id] = $sequence; } return $sequence; } } <?php namespace Codeception\Util; /** * Class to represent any type of content. * This class can act as an object, array, or string. * Method or property calls to this class won't cause any errors. * * Maybe was used in Codeception 1.x to represent data on parsing step. * Not widely used in 2.0 anymore, but left for compatibility. * * For instance, you may use `Codeception\Util\Maybe` as a test dummies. * * ```php * <?php * $user = new Maybe; * $user->posts->comments->count(); * ?> * ``` */ class Maybe implements \ArrayAccess, \Iterator, \JsonSerializable { protected $position = 0; protected $val = null; protected $assocArray = null; public function __construct($val = null) { $this->val = $val; if (is_array($this->val)) { $this->assocArray = $this->isAssocArray($this->val); } $this->position = 0; } private function isAssocArray($arr) { return array_keys($arr) !== range(0, count($arr) - 1); } public function __toString() { if ($this->val === null) { return "?"; } if (is_scalar($this->val)) { return (string)$this->val; } if (is_object($this->val) && method_exists($this->val, '__toString')) { return $this->val->__toString(); } return $this->val; } public function __get($key) { if ($this->val === null) { return new Maybe(); } if (is_object($this->val)) { if (isset($this->val->{$key}) || property_exists($this->val, $key)) { return $this->val->{$key}; } } return $this->val->key; } public function __set($key, $val) { if ($this->val === null) { return; } if (is_object($this->val)) { $this->val->{$key} = $val; return; } $this->val->key = $val; } public function __call($method, $args) { if ($this->val === null) { return new Maybe(); } return call_user_func_array([$this->val, $method], $args); } public function __clone() { if (is_object($this->val)) { $this->val = clone $this->val; } } public function __unset($key) { if (is_object($this->val)) { if (isset($this->val->{$key}) || property_exists($this->val, $key)) { unset($this->val->{$key}); return; } } } public function offsetExists($offset) { if (is_array($this->val) || ($this->val instanceof \ArrayAccess)) { return isset($this->val[$offset]); } return false; } public function offsetGet($offset) { if (is_array($this->val) || ($this->val instanceof \ArrayAccess)) { return $this->val[$offset]; } return new Maybe(); } public function offsetSet($offset, $value) { if (is_array($this->val) || ($this->val instanceof \ArrayAccess)) { $this->val[$offset] = $value; } } public function offsetUnset($offset) { if (is_array($this->val) || ($this->val instanceof \ArrayAccess)) { unset($this->val[$offset]); } } public function __value() { $val = $this->val; if (is_array($val)) { foreach ($val as $k => $v) { if ($v instanceof self) { $v = $v->__value(); } $val[$k] = $v; } } return $val; } /** * (PHP 5 >= 5.0.0)<br/> * Return the current element * @link http://php.net/manual/en/iterator.current.php * @return mixed Can return any type. */ public function current() { if (!is_array($this->val)) { return null; } if ($this->assocArray) { $keys = array_keys($this->val); return $this->val[$keys[$this->position]]; } else { return $this->val[$this->position]; } } /** * (PHP 5 >= 5.0.0)<br/> * Move forward to next element * @link http://php.net/manual/en/iterator.next.php * @return void Any returned value is ignored. */ public function next() { ++$this->position; } /** * (PHP 5 >= 5.0.0)<br/> * Return the key of the current element * @link http://php.net/manual/en/iterator.key.php * @return mixed scalar on success, or null on failure. */ public function key() { if ($this->assocArray) { $keys = array_keys($this->val); return $keys[$this->position]; } else { return $this->position; } } /** * (PHP 5 >= 5.0.0)<br/> * Checks if current position is valid * @link http://php.net/manual/en/iterator.valid.php * @return boolean The return value will be casted to boolean and then evaluated. * Returns true on success or false on failure. */ public function valid() { if (!is_array($this->val)) { return null; } if ($this->assocArray) { $keys = array_keys($this->val); return isset($keys[$this->position]); } else { return isset($this->val[$this->position]); } } /** * (PHP 5 >= 5.0.0)<br/> * Rewind the Iterator to the first element * @link http://php.net/manual/en/iterator.rewind.php * @return void Any returned value is ignored. */ public function rewind() { if (is_array($this->val)) { $this->assocArray = $this->isAssocArray($this->val); } $this->position = 0; } /** * (PHP 5 >= 5.4.0) * Serializes the object to a value that can be serialized natively by json_encode(). * @link http://docs.php.net/manual/en/jsonserializable.jsonserialize.php * @return mixed Returns data which can be serialized by json_encode(), which is a value of any type other than a resource. */ public function jsonSerialize() { return $this->__value(); } } <?php namespace Codeception\Util\Shared; trait Namespaces { protected function breakParts($class) { $class = str_replace('/', '\\', $class); $namespaces = explode('\\', $class); if (count($namespaces)) { $namespaces[0] = ltrim($namespaces[0], '\\'); } if (!$namespaces[0]) { array_shift($namespaces); } // remove empty namespace caused of \\ return $namespaces; } protected function getShortClassName($class) { $namespaces = $this->breakParts($class); return array_pop($namespaces); } protected function getNamespaceString($class) { $namespaces = $this->getNamespaces($class); return implode('\\', $namespaces); } protected function getNamespaceHeader($class) { $str = $this->getNamespaceString($class); if (!$str) { return ""; } return "namespace $str;\n"; } protected function getNamespaces($class) { $namespaces = $this->breakParts($class); array_pop($namespaces); $namespaces = array_filter($namespaces, 'strlen'); return $namespaces; } } <?php namespace Codeception\Util\Shared; trait Asserts { protected function assert($arguments, $not = false) { $not = $not ? 'Not' : ''; $method = ucfirst(array_shift($arguments)); if (($method === 'True') && $not) { $method = 'False'; $not = ''; } if (($method === 'False') && $not) { $method = 'True'; $not = ''; } call_user_func_array(['\PHPUnit_Framework_Assert', 'assert' . $not . $method], $arguments); } protected function assertNot($arguments) { $this->assert($arguments, true); } /** * Checks that two variables are equal. * * @param $expected * @param $actual * @param string $message * @param float $delta */ protected function assertEquals($expected, $actual, $message = '', $delta = 0.0) { \PHPUnit_Framework_Assert::assertEquals($expected, $actual, $message, $delta); } /** * Checks that two variables are not equal * * @param $expected * @param $actual * @param string $message * @param float $delta */ protected function assertNotEquals($expected, $actual, $message = '', $delta = 0.0) { \PHPUnit_Framework_Assert::assertNotEquals($expected, $actual, $message, $delta); } /** * Checks that two variables are same * * @param $expected * @param $actual * @param string $message */ protected function assertSame($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertSame($expected, $actual, $message); } /** * Checks that two variables are not same * * @param $expected * @param $actual * @param string $message */ protected function assertNotSame($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertNotSame($expected, $actual, $message); } /** * Checks that actual is greater than expected * * @param $expected * @param $actual * @param string $message */ protected function assertGreaterThan($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertGreaterThan($expected, $actual, $message); } /** * @deprecated */ protected function assertGreaterThen($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertGreaterThan($expected, $actual, $message); } /** * Checks that actual is greater or equal than expected * * @param $expected * @param $actual * @param string $message */ protected function assertGreaterThanOrEqual($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertGreaterThanOrEqual($expected, $actual, $message); } /** * @deprecated */ protected function assertGreaterThenOrEqual($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertGreaterThanOrEqual($expected, $actual, $message); } /** * Checks that actual is less than expected * * @param $expected * @param $actual * @param string $message */ protected function assertLessThan($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertLessThan($expected, $actual, $message); } /** * Checks that actual is less or equal than expected * * @param $expected * @param $actual * @param string $message */ protected function assertLessThanOrEqual($expected, $actual, $message = '') { \PHPUnit_Framework_Assert::assertLessThanOrEqual($expected, $actual, $message); } /** * Checks that haystack contains needle * * @param $needle * @param $haystack * @param string $message */ protected function assertContains($needle, $haystack, $message = '') { \PHPUnit_Framework_Assert::assertContains($needle, $haystack, $message); } /** * Checks that haystack doesn't contain needle. * * @param $needle * @param $haystack * @param string $message */ protected function assertNotContains($needle, $haystack, $message = '') { \PHPUnit_Framework_Assert::assertNotContains($needle, $haystack, $message); } /** * Checks that string match with pattern * * @param string $pattern * @param string $string * @param string $message */ protected function assertRegExp($pattern, $string, $message = '') { \PHPUnit_Framework_Assert::assertRegExp($pattern, $string, $message); } /** * Checks that string not match with pattern * * @param string $pattern * @param string $string * @param string $message */ protected function assertNotRegExp($pattern, $string, $message = '') { \PHPUnit_Framework_Assert::assertNotRegExp($pattern, $string, $message); } /** * Checks that variable is empty. * * @param $actual * @param string $message */ protected function assertEmpty($actual, $message = '') { \PHPUnit_Framework_Assert::assertEmpty($actual, $message); } /** * Checks that variable is not empty. * * @param $actual * @param string $message */ protected function assertNotEmpty($actual, $message = '') { \PHPUnit_Framework_Assert::assertNotEmpty($actual, $message); } /** * Checks that variable is NULL * * @param $actual * @param string $message */ protected function assertNull($actual, $message = '') { \PHPUnit_Framework_Assert::assertNull($actual, $message); } /** * Checks that variable is not NULL * * @param $actual * @param string $message */ protected function assertNotNull($actual, $message = '') { \PHPUnit_Framework_Assert::assertNotNull($actual, $message); } /** * Checks that condition is positive. * * @param $condition * @param string $message */ protected function assertTrue($condition, $message = '') { \PHPUnit_Framework_Assert::assertTrue($condition, $message); } /** * Checks that condition is negative. * * @param $condition * @param string $message */ protected function assertFalse($condition, $message = '') { \PHPUnit_Framework_Assert::assertFalse($condition, $message); } /** * * @param $haystack * @param $constraint * @param string $message */ protected function assertThat($haystack, $constraint, $message = '') { \PHPUnit_Framework_Assert::assertThat($haystack, $constraint, $message); } /** * Checks that haystack doesn't attend * * @param $haystack * @param $constraint * @param string $message */ protected function assertThatItsNot($haystack, $constraint, $message = '') { $constraint = new \PHPUnit_Framework_Constraint_Not($constraint); \PHPUnit_Framework_Assert::assertThat($haystack, $constraint, $message); } /** * Checks if file exists * * @param string $filename * @param string $message */ protected function assertFileExists($filename, $message = '') { \PHPUnit_Framework_Assert::assertFileExists($filename, $message); } /** * Checks if file doesn't exist * * @param string $filename * @param string $message */ protected function assertFileNotExists($filename, $message = '') { \PHPUnit_Framework_Assert::assertFileNotExists($filename, $message); } /** * @param $expected * @param $actual * @param $description */ protected function assertGreaterOrEquals($expected, $actual, $description = '') { \PHPUnit_Framework_Assert::assertGreaterThanOrEqual($expected, $actual, $description); } /** * @param $expected * @param $actual * @param $description */ protected function assertLessOrEquals($expected, $actual, $description = '') { \PHPUnit_Framework_Assert::assertLessThanOrEqual($expected, $actual, $description); } /** * @param $actual * @param $description */ protected function assertIsEmpty($actual, $description = '') { \PHPUnit_Framework_Assert::assertEmpty($actual, $description); } /** * @param $key * @param $actual * @param $description */ protected function assertArrayHasKey($key, $actual, $description = '') { \PHPUnit_Framework_Assert::assertArrayHasKey($key, $actual, $description); } /** * @param $key * @param $actual * @param $description */ protected function assertArrayNotHasKey($key, $actual, $description = '') { \PHPUnit_Framework_Assert::assertArrayNotHasKey($key, $actual, $description); } /** * @param $expectedCount * @param $actual * @param $description */ protected function assertCount($expectedCount, $actual, $description = '') { \PHPUnit_Framework_Assert::assertCount($expectedCount, $actual, $description); } /** * @param $class * @param $actual * @param $description */ protected function assertInstanceOf($class, $actual, $description = '') { \PHPUnit_Framework_Assert::assertInstanceOf($class, $actual, $description); } /** * @param $class * @param $actual * @param $description */ protected function assertNotInstanceOf($class, $actual, $description = '') { \PHPUnit_Framework_Assert::assertNotInstanceOf($class, $actual, $description); } /** * @param $type * @param $actual * @param $description */ protected function assertInternalType($type, $actual, $description = '') { \PHPUnit_Framework_Assert::assertInternalType($type, $actual, $description); } /** * Fails the test with message. * * @param $message */ protected function fail($message) { \PHPUnit_Framework_Assert::fail($message); } } <?php namespace Codeception\Util; /** * Holds matcher and value of mocked method */ class ConsecutiveMap { private $consecutiveMap = []; public function __construct(array $consecutiveMap) { $this->consecutiveMap = $consecutiveMap; } public function getMap() { return $this->consecutiveMap; } } <?php namespace Codeception\Util; class ArrayContainsComparator { /** * @var array */ protected $haystack = []; public function __construct($haystack) { $this->haystack = $haystack; } /** * @return array */ public function getHaystack() { return $this->haystack; } public function containsArray(array $needle) { return $needle == $this->arrayIntersectRecursive($needle, $this->haystack); } /** * @author nleippe@integr8ted.com * @author tiger.seo@gmail.com * @link http://www.php.net/manual/en/function.array-intersect-assoc.php#39822 * * @param mixed $arr1 * @param mixed $arr2 * * @return array|bool */ private function arrayIntersectRecursive($arr1, $arr2) { if (!is_array($arr1) || !is_array($arr2)) { return false; } // if it is not an associative array we do not compare keys if ($this->arrayIsSequential($arr1) && $this->arrayIsSequential($arr2)) { return $this->sequentialArrayIntersect($arr1, $arr2); } return $this->associativeArrayIntersect($arr1, $arr2); } /** * This array has sequential keys? * * @param array $array * * @return bool */ private function arrayIsSequential(array $array) { return array_keys($array) === range(0, count($array) - 1); } /** * @param array $arr1 * @param array $arr2 * @return array */ private function sequentialArrayIntersect(array $arr1, array $arr2) { $ret = []; // Do not match the same item of $arr2 against multiple items of $arr1 $matchedKeys = []; foreach ($arr1 as $key1 => $value1) { foreach ($arr2 as $key2 => $value2) { if (isset($matchedKeys[$key2])) { continue; } $return = $this->arrayIntersectRecursive($value1, $value2); if ($return !== false && $return == $value1) { $ret[$key1] = $return; $matchedKeys[$key2] = true; break; } if ($this->isEqualValue($value1, $value2)) { $ret[$key1] = $value1; $matchedKeys[$key2] = true; break; } } } return $ret; } /** * @param array $arr1 * @param array $arr2 * * @return array|bool|null */ private function associativeArrayIntersect(array $arr1, array $arr2) { $commonKeys = array_intersect(array_keys($arr1), array_keys($arr2)); $ret = []; foreach ($commonKeys as $key) { $return = $this->arrayIntersectRecursive($arr1[$key], $arr2[$key]); if ($return) { $ret[$key] = $return; continue; } if ($this->isEqualValue($arr1[$key], $arr2[$key])) { $ret[$key] = $arr1[$key]; } } if (empty($commonKeys)) { foreach ($arr2 as $arr) { $return = $this->arrayIntersectRecursive($arr1, $arr); if ($return && $return == $arr1) { return $return; } } } if (count($ret) < min(count($arr1), count($arr2))) { return null; } return $ret; } private function isEqualValue($val1, $val2) { if (is_numeric($val1)) { $val1 = (string) $val1; } if (is_numeric($val2)) { $val2 = (string) $val2; } return $val1 === $val2; } } <?php namespace Codeception\Util; /** * This class contains helper methods to help with common Reflection tasks. */ class ReflectionHelper { /** * Read a private property of an object. * * @param object $object * @param string $property * @param string|null $class * @return mixed */ public static function readPrivateProperty($object, $property, $class = null) { if (is_null($class)) { $class = $object; } $property = new \ReflectionProperty($class, $property); $property->setAccessible(true); return $property->getValue($object); } /** * Invoke a private method of an object. * * @param object $object * @param string $method * @param array $args * @param string|null $class * @return mixed */ public static function invokePrivateMethod($object, $method, $args = [], $class = null) { if (is_null($class)) { $class = $object; } $method = new \ReflectionMethod($class, $method); $method->setAccessible(true); return $method->invokeArgs($object, $args); } /** * Returns class name without namespace * * (does not use reflection actually) * * @param $object * @return mixed */ public static function getClassShortName($object) { $path = explode('\\', get_class($object)); return array_pop($path); } } <?php namespace Codeception\Util; /** * Class containing constants of HTTP Status Codes * and method to print HTTP code with its description. * * Usage: * * ```php * <?php * use \Codeception\Util\HttpCode; * * // using REST, PhpBrowser, or any Framework module * $I->seeResponseCodeIs(HttpCode::OK); * $I->dontSeeResponseCodeIs(HttpCode::NOT_FOUND); * ``` * * */ class HttpCode { const SWITCHING_PROTOCOLS = 101; const PROCESSING = 102; // RFC2518 const OK = 200; const CREATED = 201; const ACCEPTED = 202; const NON_AUTHORITATIVE_INFORMATION = 203; const NO_CONTENT = 204; const RESET_CONTENT = 205; const PARTIAL_CONTENT = 206; const MULTI_STATUS = 207; // RFC4918 const ALREADY_REPORTED = 208; // RFC5842 const IM_USED = 226; // RFC3229 const MULTIPLE_CHOICES = 300; const MOVED_PERMANENTLY = 301; const FOUND = 302; const SEE_OTHER = 303; const NOT_MODIFIED = 304; const USE_PROXY = 305; const RESERVED = 306; const TEMPORARY_REDIRECT = 307; const PERMANENTLY_REDIRECT = 308; // RFC7238 const BAD_REQUEST = 400; const UNAUTHORIZED = 401; const PAYMENT_REQUIRED = 402; const FORBIDDEN = 403; const NOT_FOUND = 404; const METHOD_NOT_ALLOWED = 405; const NOT_ACCEPTABLE = 406; const PROXY_AUTHENTICATION_REQUIRED = 407; const REQUEST_TIMEOUT = 408; const CONFLICT = 409; const GONE = 410; const LENGTH_REQUIRED = 411; const PRECONDITION_FAILED = 412; const REQUEST_ENTITY_TOO_LARGE = 413; const REQUEST_URI_TOO_LONG = 414; const UNSUPPORTED_MEDIA_TYPE = 415; const REQUESTED_RANGE_NOT_SATISFIABLE = 416; const EXPECTATION_FAILED = 417; const I_AM_A_TEAPOT = 418; // RFC2324 const UNPROCESSABLE_ENTITY = 422; // RFC4918 const LOCKED = 423; // RFC4918 const FAILED_DEPENDENCY = 424; // RFC4918 const RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 const UPGRADE_REQUIRED = 426; // RFC2817 const PRECONDITION_REQUIRED = 428; // RFC6585 const TOO_MANY_REQUESTS = 429; // RFC6585 const REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 const INTERNAL_SERVER_ERROR = 500; const NOT_IMPLEMENTED = 501; const BAD_GATEWAY = 502; const SERVICE_UNAVAILABLE = 503; const GATEWAY_TIMEOUT = 504; const VERSION_NOT_SUPPORTED = 505; const VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 const INSUFFICIENT_STORAGE = 507; // RFC4918 const LOOP_DETECTED = 508; // RFC5842 const NOT_EXTENDED = 510; // RFC2774 const NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 private static $codes = [ 100 => 'Continue', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 421 => 'Misdirected Request', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required' ]; /** * Returns string with HTTP code and its description * * ```php * <?php * HttpCode::getDescription(200); // '200 (OK)' * HttpCode::getDescription(401); // '401 (Unauthorized)' * ``` * * @param $code * @return mixed */ public static function getDescription($code) { if (isset(self::$codes[$code])) { return sprintf('%d (%s)', $code, self::$codes[$code]); } return $code; } } <?php namespace Codeception\Util; /** * That's a pretty simple yet powerful class to build XML structures in jQuery-like style. * With no XML line actually written! * Uses DOM extension to manipulate XML data. * * * ```php * <?php * $xml = new \Codeception\Util\XmlBuilder(); * $xml->users * ->user * ->val(1) * ->email * ->val('davert@mail.ua') * ->attr('valid','true') * ->parent() * ->cart * ->attr('empty','false') * ->items * ->item * ->val('useful item'); * ->parents('user') * ->active * ->val(1); * echo $xml; * ``` * * This will produce this XML * * ```xml * <?xml version="1.0"?> * <users> * <user> * 1 * <email valid="true">davert@mail.ua</email> * <cart empty="false"> * <items> * <item>useful item</item> * </items> * </cart> * <active>1</active> * </user> * </users> * ``` * * ### Usage * * Builder uses chained calls. So each call to builder returns a builder object. * Except for `getDom` and `__toString` methods. * * * `$xml->node` - create new xml node and go inside of it. * * `$xml->node->val('value')` - sets the inner value of node * * `$xml->attr('name','value')` - set the attribute of node * * `$xml->parent()` - go back to parent node. * * `$xml->parents('user')` - go back through all parents to `user` node. * * Export: * * * `$xml->getDom` - get a DOMDocument object * * `$xml->__toString` - get a string representation of XML. * * [Source code](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Util/XmlBuilder.php) */ class XmlBuilder { /** * @var \DOMDocument */ protected $__dom__; /** * @var \DOMElement */ protected $__currentNode__; public function __construct() { $this->__dom__ = new \DOMDocument(); $this->__currentNode__ = $this->__dom__; } /** * Appends child node * * @param $tag * * @return XmlBuilder */ public function __get($tag) { $node = $this->__dom__->createElement($tag); $this->__currentNode__->appendChild($node); $this->__currentNode__ = $node; return $this; } /** * @param $val * * @return XmlBuilder */ public function val($val) { $this->__currentNode__->nodeValue = $val; return $this; } /** * Sets attribute for current node * * @param $attr * @param $val * * @return XmlBuilder */ public function attr($attr, $val) { $this->__currentNode__->setAttribute($attr, $val); return $this; } /** * Traverses to parent * * @return XmlBuilder */ public function parent() { $this->__currentNode__ = $this->__currentNode__->parentNode; return $this; } /** * Traverses to parent with $name * * @param $tag * * @return XmlBuilder * @throws \Exception */ public function parents($tag) { $traverseNode = $this->__currentNode__; $elFound = false; while ($traverseNode->parentNode) { $traverseNode = $traverseNode->parentNode; if ($traverseNode->tagName == $tag) { $this->__currentNode__ = $traverseNode; $elFound = true; break; } } if (!$elFound) { throw new \Exception("Parent $tag not found in XML"); } return $this; } public function __toString() { return $this->__dom__->saveXML(); } /** * @return \DOMDocument */ public function getDom() { return $this->__dom__; } } <?php namespace Codeception\Util; class Xml { /** * @static * * @param \DOMDocument $xml * @param \DOMNode $node * @param array $array * * @return \DOMDocument */ public static function arrayToXml(\DOMDocument $xml, \DOMNode $node, $array = []) { foreach ($array as $el => $val) { if (is_array($val)) { self::arrayToXml($xml, $node->$el, $val); } else { $node->appendChild($xml->createElement($el, $val)); } } return $xml; } /** * @static * * @param $xml * * @return \DOMDocument|\DOMNode */ public static function toXml($xml) { if ($xml instanceof XmlBuilder) { return $xml->getDom(); } if ($xml instanceof \DOMDocument) { return $xml; } $dom = new \DOMDocument(); $dom->preserveWhiteSpace = false; if ($xml instanceof \DOMNode) { $xml = $dom->importNode($xml, true); $dom->appendChild($xml); return $dom; } if (is_array($xml)) { return self::arrayToXml($dom, $dom, $xml); } if (!empty($xml)) { $dom->loadXML($xml); } return $dom; } public static function build() { return new XmlBuilder(); } } <?php namespace Codeception\Util; use GuzzleHttp\Psr7\Uri as Psr7Uri; class Uri { /** * Merges the passed $add argument onto $base. * * If a relative URL is passed as the 'path' part of the $add url * array, the relative URL is mapped using the base 'path' part as * its base. * * @param string $baseUri the base URL * @param string $uri the URL to merge * @return array the merged array */ public static function mergeUrls($baseUri, $uri) { $base = new Psr7Uri($baseUri); $parts = parse_url($uri); if ($parts === false) { throw new \InvalidArgumentException("Invalid URI $uri"); } if (isset($parts['host']) and isset($parts['scheme'])) { // if this is an absolute url, replace with it return $uri; } if (isset($parts['host'])) { $base = $base->withHost($parts['host']); $base = $base->withPath(''); $base = $base->withQuery(''); $base = $base->withFragment(''); } if (isset($parts['path'])) { $path = $parts['path']; $basePath = $base->getPath(); if ((strpos($path, '/') !== 0) && !empty($path)) { if ($basePath) { // if it ends with a slash, relative paths are below it if (preg_match('~/$~', $basePath)) { $path = $basePath . $path; } else { // remove double slashes $dir = rtrim(dirname($basePath), '\\/'); $path = $dir . '/' . $path; } } else { $path = '/' . ltrim($path, '/'); } } $base = $base->withPath($path); $base = $base->withQuery(''); $base = $base->withFragment(''); } if (isset($parts['query'])) { $base = $base->withQuery($parts['query']); $base = $base->withFragment(''); } if (isset($parts['fragment'])) { $base = $base->withFragment($parts['fragment']); } return (string) $base; } /** * Retrieve /path?query#fragment part of URL * @param $url * @return string */ public static function retrieveUri($url) { $uri = new Psr7Uri($url); return (string)(new Psr7Uri()) ->withPath($uri->getPath()) ->withQuery($uri->getQuery()) ->withFragment($uri->getFragment()); } public static function retrieveHost($url) { $urlParts = parse_url($url); if (!isset($urlParts['host']) or !isset($urlParts['scheme'])) { throw new \InvalidArgumentException("Wrong URL passes, host and scheme not set"); } $host = $urlParts['scheme'] . '://' . $urlParts['host']; if (isset($urlParts['port'])) { $host .= ':' . $urlParts['port']; } return $host; } public static function appendPath($url, $path) { $uri = new Psr7Uri($url); $cutUrl = (string)$uri->withQuery('')->withFragment(''); if ($path === '' || $path[0] === '#') { return $cutUrl . $path; } else { return rtrim($cutUrl, '/') . '/' . ltrim($path, '/'); } } } <?php namespace Codeception\Util; /** * Basic template engine used for generating initial Cept/Cest/Test files. */ class Template { protected $template; protected $vars = []; protected $placehodlerStart; protected $placeholderEnd; /** * Takes a template string * * @param $template */ public function __construct($template, $placeholderStart = '{{', $placeholderEnd = '}}') { $this->template = $template; $this->placehodlerStart = $placeholderStart; $this->placeholderEnd = $placeholderEnd; } /** * Replaces {{var}} string with provided value * * @param $var * @param $val * @return $this */ public function place($var, $val) { $this->vars[$var] = $val; return $this; } /** * Sets all template vars * * @param array $vars */ public function setVars(array $vars) { $this->vars = $vars; } /** * Fills up template string with placed variables. * * @return mixed */ public function produce() { $result = $this->template; $regex = sprintf('~%s([\w\.]+)%s~m', $this->placehodlerStart, $this->placeholderEnd); $matched = preg_match_all($regex, $result, $matches, PREG_SET_ORDER); if (!$matched) { return $result; } foreach ($matches as $match) { // fill in placeholders $placeholder = $match[1]; $value = $this->vars; foreach (explode('.', $placeholder) as $segment) { if (is_array($value) && array_key_exists($segment, $value)) { $value = $value[$segment]; } else { continue 2; } } $result = str_replace($this->placehodlerStart . $placeholder . $this->placeholderEnd, $value, $result); } return $result; } } <?php namespace Codeception\Util; use Codeception\Lib\Console\Output; /** * This class is used only when Codeception is executed in `--debug` mode. * In other cases method of this class won't be seen. */ class Debug { /** * @var Output null */ protected static $output = null; public static function setOutput(Output $output) { self::$output = $output; } /** * Prints data to screen. Message can be any time of data * * @param $message */ public static function debug($message) { if (!self::$output) { return; } self::$output->debug($message); } /** * Pauses execution and waits for user input to proceed. */ public static function pause() { if (!self::$output) { return; } self::$output->writeln("<info>The execution has been paused. Press ENTER to continue</info>"); if (trim(fgets(STDIN)) != chr(13)) { return; } } } <?php namespace Codeception\Util; /** * Autoloader, which is fully compatible with PSR-4, * and can be used to autoload your `Helper`, `Page`, and `Step` classes. */ class Autoload { protected static $registered = false; /** * An associative array where the key is a namespace prefix and the value * is an array of base directories for classes in that namespace. * @var array */ protected static $map = []; private function __construct() { } /** * Adds a base directory for a namespace prefix. * * Example: * * ```php * <?php * // app\Codeception\UserHelper will be loaded from '/path/to/helpers/UserHelper.php' * Autoload::addNamespace('app\Codeception', '/path/to/helpers'); * * // LoginPage will be loaded from '/path/to/pageobjects/LoginPage.php' * Autoload::addNamespace('', '/path/to/pageobjects'); * * Autoload::addNamespace('app\Codeception', '/path/to/controllers'); * ?> * ``` * * @param string $prefix The namespace prefix. * @param string $base_dir A base directory for class files in the namespace. * @param bool $prepend If true, prepend the base directory to the stack instead of appending it; * this causes it to be searched first rather than last. * @return void */ public static function addNamespace($prefix, $base_dir, $prepend = false) { if (!self::$registered) { spl_autoload_register([__CLASS__, 'load']); self::$registered = true; } // normalize namespace prefix $prefix = trim($prefix, '\\') . '\\'; // normalize the base directory with a trailing separator $base_dir = rtrim($base_dir, '/') . DIRECTORY_SEPARATOR; $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/'; // initialize the namespace prefix array if (isset(self::$map[$prefix]) === false) { self::$map[$prefix] = []; } // retain the base directory for the namespace prefix if ($prepend) { array_unshift(self::$map[$prefix], $base_dir); } else { array_push(self::$map[$prefix], $base_dir); } } /** * @deprecated Use self::addNamespace() instead. */ public static function register($namespace, $suffix, $path) { self::addNamespace($namespace, $path); } /** * @deprecated Use self::addNamespace() instead. */ public static function registerSuffix($suffix, $path) { self::addNamespace('', $path); } public static function load($class) { // the current namespace prefix $prefix = $class; // work backwards through the namespace names of the fully-qualified class name to find a mapped file name while (false !== ($pos = strrpos($prefix, '\\'))) { // retain the trailing namespace separator in the prefix $prefix = substr($class, 0, $pos + 1); // the rest is the relative class name $relative_class = substr($class, $pos + 1); // try to load a mapped file for the prefix and relative class $mapped_file = self::loadMappedFile($prefix, $relative_class); if ($mapped_file) { return $mapped_file; } // remove the trailing namespace separator for the next iteration of strrpos() $prefix = rtrim($prefix, '\\'); } // fix for empty prefix if (isset(self::$map['\\']) && ($class[0] != '\\')) { return self::load('\\' . $class); } // backwards compatibility with old autoloader // :TODO: it should be removed if (strpos($class, '\\') !== false) { $relative_class = substr(strrchr($class, '\\'), 1); // Foo\Bar\ClassName -> ClassName $mapped_file = self::loadMappedFile('\\', $relative_class); if ($mapped_file) { return $mapped_file; } } return false; } /** * Load the mapped file for a namespace prefix and relative class. * * @param string $prefix The namespace prefix. * @param string $relative_class The relative class name. * @return mixed Boolean false if no mapped file can be loaded, or the name of the mapped file that was loaded. */ protected static function loadMappedFile($prefix, $relative_class) { if (!isset(self::$map[$prefix])) { return false; } foreach (self::$map[$prefix] as $base_dir) { $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php'; // 'static' is for testing purposes if (static::requireFile($file)) { return $file; } } return false; } protected static function requireFile($file) { if (file_exists($file)) { require_once $file; return true; } return false; } } <?php namespace Codeception\Util; /** * JsonType matches JSON structures against templates. * You can specify the type of fields in JSON or add additional validation rules. * * JsonType is used by REST module in `seeResponseMatchesJsonType` and `dontSeeResponseMatchesJsonType` methods. * * Usage example: * * ```php * <?php * $jsonType = new JsonType(['name' => 'davert', 'id' => 1]); * $jsonType->matches([ * 'name' => 'string:!empty', * 'id' => 'integer:>0|string:>0', * ]); // => true * * $jsonType->matches([ * 'id' => 'string', * ]); // => `id: 1` is not of type string * ?> * ``` * * Class JsonType * @package Codeception\Util */ class JsonType { protected $jsonArray; protected static $customFilters = []; /** * Creates instance of JsonType * Pass an array or `\Codeception\Util\JsonArray` with data. * If non-associative array is passed - the very first element of it will be used for matching. * * @param $jsonArray array|\Codeception\Util\JsonArray */ public function __construct($jsonArray) { if ($jsonArray instanceof JsonArray) { $jsonArray = $jsonArray->toArray(); } $this->jsonArray = $jsonArray; } /** * Adds custom filter to JsonType list. * You should specify a name and parameters of a filter. * * Example: * * ```php * <?php * JsonType::addCustomFilter('slug', function($value) { * return strpos(' ', $value) !== false; * }); * // => use it as 'string:slug' * * // add custom function to matcher with `len($val)` syntax * // parameter matching patterns should be valid regex and start with `/` char * JsonType::addCustomFilter('/len\((.*?)\)/', function($value, $len) { * return strlen($value) == $len; * }); * // use it as 'string:len(5)' * ?> * ``` * * @param $name * @param callable $callable */ public static function addCustomFilter($name, callable $callable) { static::$customFilters[$name] = $callable; } /** * Removes all custom filters */ public static function cleanCustomFilters() { static::$customFilters = []; } /** * Checks data against passed JsonType. * If matching fails function returns a string with a message describing failure. * On success returns `true`. * * @param array $jsonType * @return bool|string */ public function matches(array $jsonType) { if (array_key_exists(0, $this->jsonArray)) { // sequential array $msg = ''; foreach ($this->jsonArray as $array) { $res = $this->typeComparison($array, $jsonType); if ($res !== true) { $msg .= "\n" . $res; } } if ($msg) { return $msg; } return true; } return $this->typeComparison($this->jsonArray, $jsonType); } protected function typeComparison($data, $jsonType) { foreach ($jsonType as $key => $type) { if (!array_key_exists($key, $data)) { return "Key `$key` doesn't exist in " . json_encode($data); } if (is_array($jsonType[$key])) { $message = $this->typeComparison($data[$key], $jsonType[$key]); if (is_string($message)) { return $message; } continue; } $matchTypes = preg_split("#(?![^]\(]*\))\|#", $type); $matched = false; $currentType = strtolower(gettype($data[$key])); if ($currentType == 'double') { $currentType = 'float'; } foreach ($matchTypes as $matchType) { $filters = preg_split("#(?![^]\(]*\))\:#", $matchType); $expectedType = trim(strtolower(array_shift($filters))); if ($expectedType != $currentType) { continue; } $matched = true; foreach ($filters as $filter) { $matched = $matched && $this->matchFilter($filter, $data[$key]); } if ($matched) { break; } } if (!$matched) { return sprintf("`$key: %s` is of type `$type`", var_export($data[$key], true)); } } return true; } protected function matchFilter($filter, $value) { $filter = trim($filter); if (strpos($filter, '!') === 0) { return !$this->matchFilter(substr($filter, 1), $value); } // apply custom filters foreach (static::$customFilters as $customFilter => $callable) { if (strpos($customFilter, '/') === 0) { if (preg_match($customFilter, $filter, $matches)) { array_shift($matches); return call_user_func_array($callable, array_merge([$value], $matches)); } } if ($customFilter == $filter) { return $callable($value); } } if (strpos($filter, '=') === 0) { return $value == substr($filter, 1); } if ($filter === 'url') { return filter_var($value, FILTER_VALIDATE_URL); } if ($filter === 'date') { return preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(?:Z|(\+|-)([\d|:]*))?$/', $value ); } if ($filter === 'email') { // from http://emailregex.com/ // @codingStandardsIgnoreStart return preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD', $value); // @codingStandardsIgnoreEnd } if ($filter === 'empty') { return empty($value); } if (preg_match('~^regex\((.*?)\)$~', $filter, $matches)) { return preg_match($matches[1], $value); } if (preg_match('~^>([\d\.]+)$~', $filter, $matches)) { return (float)$value > (float)$matches[1]; } if (preg_match('~^<([\d\.]+)$~', $filter, $matches)) { return (float)$value < (float)$matches[1]; } } } <?php namespace Codeception\Util; /** * Really basic class to store data in global array and use it in Cests/Tests. * * ```php * <?php * Fixtures::add('user1', ['name' => 'davert']); * Fixtures::get('user1'); * * ?> * ``` * */ class Fixtures { protected static $fixtures = []; public static function add($name, $data) { self::$fixtures[$name] = $data; } public static function get($name) { if (!isset(self::$fixtures[$name])) { throw new \RuntimeException("$name not found in fixtures"); } return self::$fixtures[$name]; } public static function cleanup() { self::$fixtures = []; } } <?php namespace Codeception\Util; use Facebook\WebDriver\WebDriverBy; use Symfony\Component\CssSelector\CssSelectorConverter; use Symfony\Component\CssSelector\Exception\ParseException; use Symfony\Component\CssSelector\XPath\Translator; /** * Set of useful functions for using CSS and XPath locators. * Please check them before writing complex functional or acceptance tests. * */ class Locator { /** * Applies OR operator to any number of CSS or XPath selectors. * You can mix up CSS and XPath selectors here. * * ```php * <?php * use \Codeception\Util\Locator; * * $I->see('Title', Locator::combine('h1','h2','h3')); * ?> * ``` * * This will search for `Title` text in either `h1`, `h2`, or `h3` tag. * You can also combine CSS selector with XPath locator: * * ```php * <?php * use \Codeception\Util\Locator; * * $I->fillField(Locator::combine('form input[type=text]','//form/textarea[2]'), 'qwerty'); * ?> * ``` * * As a result the Locator will produce a mixed XPath value that will be used in fillField action. * * @static * * @param $selector1 * @param $selector2 * * @throws \Exception * * @return string */ public static function combine($selector1, $selector2) { $selectors = func_get_args(); foreach ($selectors as $k => $v) { $selectors[$k] = self::toXPath($v); if (!$selectors[$k]) { throw new \Exception("$v is invalid CSS or XPath"); } } return implode(' | ', $selectors); } /** * Matches the *a* element with given URL * * ```php * <?php * use \Codeception\Util\Locator; * * $I->see('Log In', Locator::href('/login.php')); * ?> * ``` * * @static * * @param $url * * @return string */ public static function href($url) { return sprintf('//a[@href=normalize-space(%s)]', Translator::getXpathLiteral($url)); } /** * Matches the element with given tab index * * Do you often use the `TAB` key to navigate through the web page? How do your site respond to this navigation? * You could try to match elements by their tab position using `tabIndex` method of `Locator` class. * ```php * <?php * use \Codeception\Util\Locator; * * $I->fillField(Locator::tabIndex(1), 'davert'); * $I->fillField(Locator::tabIndex(2) , 'qwerty'); * $I->click('Login'); * ?> * ``` * * @static * * @param $index * * @return string */ public static function tabIndex($index) { return sprintf('//*[@tabindex = normalize-space(%d)]', $index); } /** * Matches option by text: * * ```php * <?php * use Codeception\Util\Locator; * * $I->seeElement(Locator::option('Male'), '#select-gender'); * ``` * * @param $value * * @return string */ public static function option($value) { return sprintf('//option[.=normalize-space("%s")]', $value); } protected static function toXPath($selector) { try { $xpath = (new CssSelectorConverter())->toXPath($selector); return $xpath; } catch (ParseException $e) { if (self::isXPath($selector)) { return $selector; } } return null; } /** * Finds element by it's attribute(s) * * ```php * <?php * use \Codeception\Util\Locator; * * $I->seeElement(Locator::find('img', ['title' => 'diagram'])); * ``` * * @static * * @param $element * @param $attributes * * @return string */ public static function find($element, array $attributes) { $operands = []; foreach ($attributes as $attribute => $value) { if (is_int($attribute)) { $operands[] = '@' . $value; } else { $operands[] = '@' . $attribute . ' = ' . Translator::getXpathLiteral($value); } } return sprintf('//%s[%s]', $element, implode(' and ', $operands)); } /** * Checks that provided string is CSS selector * * ```php * <?php * Locator::isCSS('#user .hello') => true * Locator::isCSS('body') => true * Locator::isCSS('//body/p/user') => false * ``` * * @param $selector * * @return bool */ public static function isCSS($selector) { try { (new CssSelectorConverter())->toXPath($selector); } catch (ParseException $e) { return false; } return true; } /** * Checks that locator is an XPath * * ```php * <?php * Locator::isCSS('#user .hello') => false * Locator::isCSS('body') => false * Locator::isCSS('//body/p/user') => true * ``` * * @param $locator * * @return bool */ public static function isXPath($locator) { $document = new \DOMDocument('1.0', 'UTF-8'); $xpath = new \DOMXPath($document); return @$xpath->evaluate($locator, $document) !== false; } /** * Checks that a string is valid CSS ID * * @param $id * * @return bool */ public static function isID($id) { return (bool)preg_match('~^#[\w\.\-\[\]\=\^\~\:]+$~', $id); } /** * Checks that a string is valid CSS class * * @param $class * @return bool */ public static function isClass($class) { return (bool)preg_match('~^\.[\w\.\-\[\]\=\^\~\:]+$~', $class); } /** * Locates an element containing a text inside. * Either CSS or XPath locator can be passed, however they will be converted to XPath. * * ```php * <?php * use Codeception\Util\Locator; * * Locator::contains('label', 'Name'); // label containing name * Locator::contains('div[@contenteditable=true]', 'hello world'); * ``` * * @param $element * @param $text * * @return string */ public static function contains($element, $text) { $text = Translator::getXpathLiteral($text); return sprintf('%s[%s]', self::toXPath($element), "contains(., $text)"); } /** * Locates element at position. * Either CSS or XPath locator can be passed as locator, * position is an integer. If a negative value is provided, counting starts from the last element. * First element has index 1 * * ```php * <?php * use Codeception\Util\Locator; * * Locator::elementAt('//table/tr', 2); // second row * Locator::elementAt('//table/tr', -1); // last row * Locator::elementAt('table#grind>tr', -2); // previous than last row * ``` * * @param string $element CSS or XPath locator * @param int $position xpath index * * @return mixed */ public static function elementAt($element, $position) { if (is_int($position) && $position < 0) { $position++; // -1 points to the last element $position = 'last()-'.abs($position); } if ($position === 0) { throw new \InvalidArgumentException( '0 is not valid element position. XPath expects first element to have index 1' ); } return sprintf('(%s)[position()=%s]', self::toXPath($element), $position); } /** * Locates first element of group elements. * Either CSS or XPath locator can be passed as locator, * Equal to `Locator::elementAt($locator, 1)` * * ```php * <?php * use Codeception\Util\Locator; * * Locator::firstElement('//table/tr'); * ``` * * @param $element * * @return mixed */ public static function firstElement($element) { return self::elementAt($element, 1); } /** * Locates last element of group elements. * Either CSS or XPath locator can be passed as locator, * Equal to `Locator::elementAt($locator, -1)` * * ```php * <?php * use Codeception\Util\Locator; * * Locator::lastElement('//table/tr'); * ``` * * @param $element * * @return mixed */ public static function lastElement($element) { return self::elementAt($element, 'last()'); } /** * Transforms strict locator, \Facebook\WebDriver\WebDriverBy into a string represenation * * @param $selector * * @return string */ public static function humanReadableString($selector) { if (is_string($selector)) { return "'$selector'"; } if (is_array($selector)) { $type = strtolower(key($selector)); $locator = $selector[$type]; return "$type '$locator'"; } if (class_exists('\Facebook\WebDriver\WebDriverBy')) { if ($selector instanceof WebDriverBy) { $type = $selector->getMechanism(); $locator = $selector->getValue(); return "$type '$locator'"; } } throw new \InvalidArgumentException("Unrecognized selector"); } } <?php namespace Codeception\Util; /** * Set of functions to work with Filesystem * */ class FileSystem { /** * @param $path */ public static function doEmptyDir($path) { /** @var $iterator \RecursiveIteratorIterator|\SplFileObject[] */ $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $path) { $basename = basename((string)$path); if ($basename === '.' || $basename === '..' || $basename === '.gitignore') { continue; } if ($path->isDir()) { rmdir((string)$path); } else { unlink((string)$path); } } } /** * @param $dir * @return bool */ public static function deleteDir($dir) { if (!file_exists($dir)) { return true; } if (!is_dir($dir) || is_link($dir)) { return @unlink($dir); } if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $dir = str_replace('/', '\\', $dir); exec('rd /s /q "'.$dir.'"'); return true; } foreach (scandir($dir) as $item) { if ($item === '.' || $item === '..') { continue; } if (!self::deleteDir($dir . DIRECTORY_SEPARATOR . $item)) { chmod($dir . DIRECTORY_SEPARATOR . $item, 0777); if (!self::deleteDir($dir . DIRECTORY_SEPARATOR . $item)) { return false; } } } return @rmdir($dir); } /** * @param $src * @param $dst */ public static function copyDir($src, $dst) { $dir = opendir($src); @mkdir($dst); while (false !== ($file = readdir($dir))) { if (($file != '.') && ($file != '..')) { if (is_dir($src . DIRECTORY_SEPARATOR . $file)) { self::copyDir($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file); } else { copy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file); } } } closedir($dir); } } <?php namespace Codeception\Util; use Codeception\Exception\ConfigurationException; class PathResolver { /** * Returns path to a given directory relative to $projDir. * @param string $path * @param string $projDir * @param string $dirSep * @return string */ public static function getRelativeDir($path, $projDir, $dirSep = DIRECTORY_SEPARATOR) { // ensure $projDir ends with a trailing $dirSep $projDir = preg_replace('/'.preg_quote($dirSep, '/').'*$/', $dirSep, $projDir); // if $path is a within $projDir if (self::fsCaseStrCmp(substr($path, 0, strlen($projDir)), $projDir, $dirSep) == 0) { // simply chop it off the front return substr($path, strlen($projDir)); } // Identify any absoluteness prefix (like '/' in Unix or "C:\\" in Windows) $pathAbsPrefix = self::getPathAbsolutenessPrefix($path, $dirSep); $projDirAbsPrefix = self::getPathAbsolutenessPrefix($projDir, $dirSep); $sameAbsoluteness = (self::fsCaseStrCmp($pathAbsPrefix['wholePrefix'], $projDirAbsPrefix['wholePrefix'], $dirSep) == 0); if (!$sameAbsoluteness) { // if the $projDir and $path aren't relative to the same // thing, we can't make a relative path. // if we're relative to the same device ... if (strlen($pathAbsPrefix['devicePrefix']) && (self::fsCaseStrCmp($pathAbsPrefix['devicePrefix'], $projDirAbsPrefix['devicePrefix'], $dirSep) == 0) ) { // ... shave that off return substr($path, strlen($pathAbsPrefix['devicePrefix'])); } // Return the input unaltered return $path; } // peel off optional absoluteness prefixes and convert // $path and $projDir to an subdirectory path array $relPathParts = array_filter(explode($dirSep, substr($path, strlen($pathAbsPrefix['wholePrefix']))), 'strlen'); $relProjDirParts = array_filter(explode($dirSep, substr($projDir, strlen($projDirAbsPrefix['wholePrefix']))), 'strlen'); // While there are any, peel off any common parent directories // from the beginning of the $projDir and $path while ((count($relPathParts) > 0) && (count($relProjDirParts) > 0) && (self::fsCaseStrCmp($relPathParts[0], $relProjDirParts[0], $dirSep) == 0) ) { array_shift($relPathParts); array_shift($relProjDirParts); } if (count($relProjDirParts) > 0) { // prefix $relPath with '..' for all remaining unmatched $projDir // subdirectories $relPathParts = array_merge(array_fill(0, count($relProjDirParts), '..'), $relPathParts); } // only append a trailing seperator if one is already present $trailingSep = preg_match('/'.preg_quote($dirSep, '/').'$/', $path) ? $dirSep : ''; // convert array of dir paths back into a string path return implode($dirSep, $relPathParts).$trailingSep; } /** * FileSystem Case String Compare * compare two strings with the filesystem's case-sensitiveness * * @param string $str1 * @param string $str2 * @param string $dirSep * @return int -1 / 0 / 1 for < / = / > respectively */ private static function fsCaseStrCmp($str1, $str2, $dirSep = DIRECTORY_SEPARATOR) { $cmpFn = self::isWindows($dirSep) ? 'strcasecmp' : 'strcmp'; return $cmpFn($str1, $str2); } /** * What part of this path (leftmost 0-3 characters) what * it is absolute relative to: * * On Unix: * This is simply '/' for an absolute path or * '' for a relative path * * On Windows this is more complicated: * If the first two characters are a letter followed * by a ':', this indicates that the path is * on a specific device. * With or without a device specified, a path MAY * start with a '\\' to indicate an absolute path * on the device or '' to indicate a path relative * to the device's CWD * * @param string $path * @param string $dirSep * @return string */ private static function getPathAbsolutenessPrefix($path, $dirSep = DIRECTORY_SEPARATOR) { $devLetterPrefixPattern = ''; if (self::isWindows($dirSep)) { $devLetterPrefixPattern = '([A-Za-z]:|)'; } $matches = []; if (!preg_match('/^'.$devLetterPrefixPattern.preg_quote($dirSep, '/').'?/', $path, $matches)) { // This should match, even if it matches 0 characters throw new ConfigurationException("INTERNAL ERROR: This must be a regex problem."); } return [ 'wholePrefix' => $matches[0], // The optional device letter followed by the optional $dirSep 'devicePrefix' => self::isWindows($dirSep) ? $matches[1] : '']; } /** * Are we in a Windows style filesystem? * * @param string $dirSep * @return bool */ private static function isWindows($dirSep = DIRECTORY_SEPARATOR) { return ($dirSep == '\\'); } } <?php namespace Codeception\Util; use Codeception\Exception\ElementNotFound; use Codeception\Exception\MalformedLocatorException; use Symfony\Component\CssSelector\CssSelectorConverter; use Symfony\Component\CssSelector\Exception\ParseException; use Codeception\Util\Soap as XmlUtils; class XmlStructure { /** * @var \DOMDocument|\DOMNode */ protected $xml; public function __construct($xml) { $this->xml = XmlUtils::toXml($xml); } public function matchesXpath($xpath) { $path = new \DOMXPath($this->xml); $res = $path->query($xpath); if ($res === false) { throw new MalformedLocatorException($xpath); } return $res->length > 0; } /** * @param $cssOrXPath * @return \DOMElement */ public function matchElement($cssOrXPath) { $xpath = new \DOMXpath($this->xml); try { $selector = (new CssSelectorConverter())->toXPath($cssOrXPath); $els = $xpath->query($selector); if ($els) { return $els->item(0); } } catch (ParseException $e) { } $els = $xpath->query($cssOrXPath); if ($els->length) { return $els->item(0); } throw new ElementNotFound($cssOrXPath); } /** * @param $xml * @return bool */ public function matchXmlStructure($xml) { $xml = XmlUtils::toXml($xml); $root = $xml->firstChild; $els = $this->xml->getElementsByTagName($root->nodeName); if (empty($els)) { throw new ElementNotFound($root->nodeName, 'Element'); } $matches = false; foreach ($els as $node) { $matches |= $this->matchForNode($root, $node); } return $matches; } protected function matchForNode($schema, $xml) { foreach ($schema->childNodes as $node1) { $matched = false; foreach ($xml->childNodes as $node2) { if ($node1->nodeName == $node2->nodeName) { $matched = $this->matchForNode($node1, $node2); if ($matched) { break; } } } if (!$matched) { return false; } } return true; } } <?php namespace Codeception\Util; /** * Simple annotation parser. Take only key-value annotations for methods or class. */ class Annotation { protected static $reflectedClasses = []; protected static $regex = '/@%s(?:[ \t]*(.*?))?[ \t]*\r?$/m'; protected static $lastReflected = null; /** * @var \ReflectionClass */ protected $reflectedClass; protected $currentReflectedItem; /** * Grabs annotation values. * * Usage example: * * ``` php * <?php * Annotation::forClass('MyTestCase')->fetch('guy'); * Annotation::forClass('MyTestCase')->method('testData')->fetch('depends'); * Annotation::forClass('MyTestCase')->method('testData')->fetchAll('depends'); * * ?> * ``` * * @param $class * * @return $this */ public static function forClass($class) { if (is_object($class)) { $class = get_class($class); } if (!isset(static::$reflectedClasses[$class])) { static::$reflectedClasses[$class] = new \ReflectionClass($class); } return new static(static::$reflectedClasses[$class]); } /** * @param $class * @param $method * * @return $this */ public static function forMethod($class, $method) { return self::forClass($class)->method($method); } /** * Parses raw comment for annotations * * @param $comment * @param $annotation * @return array */ public static function fetchAllFromComment($annotation, $comment) { if (preg_match_all(sprintf(self::$regex, $annotation), $comment, $matched)) { return $matched[1]; } return []; } public function __construct(\ReflectionClass $class) { $this->currentReflectedItem = $this->reflectedClass = $class; } /** * @param $method * * @return $this */ public function method($method) { $this->currentReflectedItem = $this->reflectedClass->getMethod($method); return $this; } /** * @param $annotation * @return null */ public function fetch($annotation) { $docBlock = $this->currentReflectedItem->getDocComment(); if (preg_match(sprintf(self::$regex, $annotation), $docBlock, $matched)) { return $matched[1]; } return null; } /** * @param $annotation * @return array */ public function fetchAll($annotation) { $docBlock = $this->currentReflectedItem->getDocComment(); if (preg_match_all(sprintf(self::$regex, $annotation), $docBlock, $matched)) { return $matched[1]; } return []; } public function raw() { return $this->currentReflectedItem->getDocComment(); } /** * Returns an associative array value of annotation * Either JSON or Doctrine-annotation style allowed * Returns null if not a valid array data * * @param $annotation * @return array|mixed|string */ public static function arrayValue($annotation) { $annotation = trim($annotation); $openingBrace = substr($annotation, 0, 1); // json-style data format if (in_array($openingBrace, ['{', '['])) { return json_decode($annotation, true); } // doctrine-style data format if ($openingBrace === '(') { preg_match_all('~(\w+)\s*?=\s*?"(.*?)"\s*?[,)]~', $annotation, $matches, PREG_SET_ORDER); $data = []; foreach ($matches as $item) { $data[$item[1]] = $item[2]; } return $data; } return null; } } <?php namespace Codeception\Util; use Flow\JSONPath\JSONPath; use InvalidArgumentException; use DOMDocument; class JsonArray { /** * @var array */ protected $jsonArray = []; /** * @var DOMDocument */ protected $jsonXml = null; public function __construct($jsonString) { if (!is_string($jsonString)) { throw new InvalidArgumentException('$jsonString param must be a string.'); } $this->jsonArray = json_decode($jsonString, true); if (JSON_ERROR_NONE !== json_last_error()) { throw new InvalidArgumentException( sprintf( "Invalid json: %s. System message: %s.", $jsonString, json_last_error_msg() ), json_last_error() ); } } public function toXml() { if ($this->jsonXml) { return $this->jsonXml; } $root = 'root'; $jsonArray = $this->jsonArray; if (count($jsonArray) == 1) { $value = reset($jsonArray); if (is_array($value)) { $root = key($jsonArray); $jsonArray = $value; } } $dom = new DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = false; $root = $dom->createElement($root); $dom->appendChild($root); $this->arrayToXml($dom, $root, $jsonArray); $this->jsonXml = $dom; return $dom; } /** * @return array */ public function toArray() { return $this->jsonArray; } public function filterByXPath($xpath) { $path = new \DOMXPath($this->toXml()); return $path->query($xpath); } public function filterByJsonPath($jsonPath) { if (!class_exists('Flow\JSONPath\JSONPath')) { throw new \Exception('JSONPath library not installed. Please add `flow/jsonpath` to composer.json'); } return (new JSONPath($this->jsonArray))->find($jsonPath)->data(); } public function getXmlString() { return $this->toXml()->saveXML(); } public function containsArray(array $needle) { return (new ArrayContainsComparator($this->jsonArray))->containsArray($needle); } private function arrayToXml(\DOMDocument $doc, \DOMNode $node, $array) { foreach ($array as $key => $value) { if (is_numeric($key)) { $subNode = $doc->createElement($node->nodeName); $node->parentNode->appendChild($subNode); } else { try { $subNode = $doc->createElement($key); } catch (\Exception $e) { $key = $this->getValidTagNameForInvalidKey($key); $subNode = $doc->createElement($key); } $node->appendChild($subNode); } if (is_array($value)) { $this->arrayToXml($doc, $subNode, $value); } else { $subNode->nodeValue = htmlspecialchars((string)$value); } } } private function getValidTagNameForInvalidKey($key) { static $map = []; if (!isset($map[$key])) { $tagName = 'invalidTag' . (count($map) + 1); $map[$key] = $tagName; codecept_debug($tagName . ' is "' . $key . '"'); } return $map[$key]; } } <?php namespace Codeception\Util; use Codeception\Step\Action; /** * Class for defining an array actions to be executed inside `performOn` of WebDriver * * ```php * <?php * (new ActionSequence)->click('do')->click('undo'); * ActionSequence::build()->click('do')->click('undo'); * ``` * * @method $this see([optional]) * @method $this dontSee([optional]) * @method $this seeElement([optional]) * @method $this dontSeeElement([optional]) * @method $this click([optional]) * @method $this wait([optional]) * @method $this waitForElementChange([optional]) * @method $this waitForElement([optional]) * @method $this waitForElementVisible([optional]) * @method $this waitForElementNotVisible([optional]) * @method $this waitForText([optional]) * @method $this submitForm([optional]) * @method $this seeLink([optional]) * @method $this dontSeeLink([optional]) * @method $this seeCheckboxIsChecked([optional]) * @method $this dontSeeCheckboxIsChecked([optional]) * @method $this seeInField([optional]) * @method $this dontSeeInField([optional]) * @method $this seeInFormFields([optional]) * @method $this dontSeeInFormFields([optional]) * @method $this selectOption([optional]) * @method $this checkOption([optional]) * @method $this uncheckOption([optional]) * @method $this fillField([optional]) * @method $this attachFile([optional]) * @method $this seeNumberOfElements([optional]) * @method $this seeOptionIsSelected([optional]) * @method $this dontSeeOptionIsSelected([optional]) */ class ActionSequence { protected $actions = []; /** * Creates an instance * @return ActionSequence */ public static function build() { return new self; } public function __call($action, $arguments) { $this->addAction($action, $arguments); return $this; } protected function addAction($action, $arguments) { if (!is_array($arguments)) { $arguments = [$arguments]; } $this->actions[] = new Action($action, $arguments); } /** * Creates action sequence from associative array, * where key is action, and value is action arguments * * @param array $actions * @return $this */ public function fromArray(array $actions) { foreach ($actions as $action => $arguments) { $this->addAction($action, $arguments); } return $this; } /** * Returns a list of logged actions as associative array * @return array */ public function toArray() { return $this->actions; } /** * Executes sequence of action as methods of passed object. * * @param $context */ public function run($context) { foreach ($this->actions as $step) { /** @var $step Action **/ codecept_debug("- $step"); try { call_user_func_array([$context, $step->getAction()], $step->getArguments()); } catch (\Exception $e) { $class = get_class($e); // rethrow exception for a specific action throw new $class($e->getMessage() . "\nat $step"); } } } public function __toString() { $actionsLog = []; foreach ($this->actions as $step) { $args = str_replace('"', "'", $step->getArgumentsAsString(20)); $actionsLog[] = $step->getAction() . ": $args"; } return implode(', ', $actionsLog); } } <?php namespace Codeception\Util; class Stub { public static $magicMethods = ['__isset', '__get', '__set']; /** * Instantiates a class without executing a constructor. * Properties and methods can be set as a second parameter. * Even protected and private properties can be set. * * ``` php * <?php * Stub::make('User'); * Stub::make('User', array('name' => 'davert')); * ?> * ``` * * Accepts either name of class or object of that class * * ``` php * <?php * Stub::make(new User, array('name' => 'davert')); * ?> * ``` * * To replace method provide it's name as a key in second parameter * and it's return value or callback function as parameter * * ``` php * <?php * Stub::make('User', array('save' => function () { return true; })); * Stub::make('User', array('save' => true })); * ?> * ``` * * @param mixed $class - A class to be mocked * @param array $params - properties and methods to set * @param bool|\PHPUnit_Framework_TestCase $testCase * * @return object - mock * @throws \RuntimeException when class does not exist */ public static function make($class, $params = [], $testCase = false) { $class = self::getClassname($class); if (!class_exists($class)) { if (interface_exists($class)) { throw new \RuntimeException("Stub::make can't mock interfaces, please use Stub::makeEmpty instead."); } throw new \RuntimeException("Stubbed class $class doesn't exist."); } $reflection = new \ReflectionClass($class); $callables = self::getMethodsToReplace($reflection, $params); if ($reflection->isAbstract()) { $arguments = empty($callables) ? [] : array_keys($callables); $mock = self::generateMockForAbstractClass($class, $arguments, '', false, $testCase); } else { $arguments = empty($callables) ? null : array_keys($callables); $mock = self::generateMock($class, $arguments, [], '', false, $testCase); } self::bindParameters($mock, $params); return self::markAsMock($mock, $reflection); } /** * Set __mock flag, if at all possible * * @param object $mock * @param \ReflectionClass $reflection * @return object */ private static function markAsMock($mock, \ReflectionClass $reflection) { if (!$reflection->hasMethod('__set')) { $mock->__mocked = $reflection->getName(); } return $mock; } /** * Creates $num instances of class through `Stub::make`. * * @param mixed $class * @param int $num * @param array $params * * @return array */ public static function factory($class, $num = 1, $params = []) { $objects = []; for ($i = 0; $i < $num; $i++) { $objects[] = self::make($class, $params); } return $objects; } /** * Instantiates class having all methods replaced with dummies except one. * Constructor is not triggered. * Properties and methods can be replaced. * Even protected and private properties can be set. * * ``` php * <?php * Stub::makeEmptyExcept('User', 'save'); * Stub::makeEmptyExcept('User', 'save', array('name' => 'davert')); * ?> * ``` * * Accepts either name of class or object of that class * * ``` php * <?php * * Stub::makeEmptyExcept(new User, 'save'); * ?> * ``` * * To replace method provide it's name as a key in second parameter * and it's return value or callback function as parameter * * ``` php * <?php * Stub::makeEmptyExcept('User', 'save', array('isValid' => function () { return true; })); * Stub::makeEmptyExcept('User', 'save', array('isValid' => true })); * ?> * ``` * * @param mixed $class * @param string $method * @param array $params * @param bool|\PHPUnit_Framework_TestCase $testCase * * @return object */ public static function makeEmptyExcept($class, $method, $params = [], $testCase = false) { $class = self::getClassname($class); $reflectionClass = new \ReflectionClass($class); $methods = $reflectionClass->getMethods(); $methods = array_filter( $methods, function ($m) { return !in_array($m->name, Stub::$magicMethods); } ); $methods = array_filter( $methods, function ($m) use ($method) { return $method != $m->name; } ); $methods = array_map( function ($m) { return $m->name; }, $methods ); $methods = count($methods) ? $methods : null; $mock = self::generateMock($class, $methods, [], '', false, $testCase); self::bindParameters($mock, $params); return self::markAsMock($mock, $reflectionClass); } /** * Instantiates class having all methods replaced with dummies. * Constructor is not triggered. * Properties and methods can be set as a second parameter. * Even protected and private properties can be set. * * ``` php * <?php * Stub::makeEmpty('User'); * Stub::makeEmpty('User', array('name' => 'davert')); * ?> * ``` * * Accepts either name of class or object of that class * * ``` php * <?php * Stub::makeEmpty(new User, array('name' => 'davert')); * ?> * ``` * * To replace method provide it's name as a key in second parameter * and it's return value or callback function as parameter * * ``` php * <?php * Stub::makeEmpty('User', array('save' => function () { return true; })); * Stub::makeEmpty('User', array('save' => true })); * ?> * ``` * * @param mixed $class * @param array $params * @param bool|\PHPUnit_Framework_TestCase $testCase * * @return object */ public static function makeEmpty($class, $params = [], $testCase = false) { $class = self::getClassname($class); $reflection = new \ReflectionClass($class); $methods = get_class_methods($class); $methods = array_filter( $methods, function ($i) { return !in_array($i, Stub::$magicMethods); } ); $mock = self::generateMock($class, $methods, [], '', false, $testCase); self::bindParameters($mock, $params); return self::markAsMock($mock, $reflection); } /** * Clones an object and redefines it's properties (even protected and private) * * @param $obj * @param array $params * * @return mixed */ public static function copy($obj, $params = []) { $copy = clone($obj); self::bindParameters($copy, $params); return $copy; } /** * Instantiates a class instance by running constructor. * Parameters for constructor passed as second argument * Properties and methods can be set in third argument. * Even protected and private properties can be set. * * ``` php * <?php * Stub::construct('User', array('autosave' => false)); * Stub::construct('User', array('autosave' => false), array('name' => 'davert')); * ?> * ``` * * Accepts either name of class or object of that class * * ``` php * <?php * Stub::construct(new User, array('autosave' => false), array('name' => 'davert')); * ?> * ``` * * To replace method provide it's name as a key in third parameter * and it's return value or callback function as parameter * * ``` php * <?php * Stub::construct('User', array(), array('save' => function () { return true; })); * Stub::construct('User', array(), array('save' => true })); * ?> * ``` * * @param mixed $class * @param array $constructorParams * @param array $params * @param bool|\PHPUnit_Framework_TestCase $testCase * * @return object */ public static function construct($class, $constructorParams = [], $params = [], $testCase = false) { $class = self::getClassname($class); $reflection = new \ReflectionClass($class); $callables = self::getMethodsToReplace($reflection, $params); $arguments = empty($callables) ? null : array_keys($callables); $mock = self::generateMock($class, $arguments, $constructorParams, $testCase); self::bindParameters($mock, $params); return self::markAsMock($mock, $reflection); } /** * Instantiates a class instance by running constructor with all methods replaced with dummies. * Parameters for constructor passed as second argument * Properties and methods can be set in third argument. * Even protected and private properties can be set. * * ``` php * <?php * Stub::constructEmpty('User', array('autosave' => false)); * Stub::constructEmpty('User', array('autosave' => false), array('name' => 'davert')); * ?> * ``` * * Accepts either name of class or object of that class * * ``` php * <?php * Stub::constructEmpty(new User, array('autosave' => false), array('name' => 'davert')); * ?> * ``` * * To replace method provide it's name as a key in third parameter * and it's return value or callback function as parameter * * ``` php * <?php * Stub::constructEmpty('User', array(), array('save' => function () { return true; })); * Stub::constructEmpty('User', array(), array('save' => true })); * ?> * ``` * * @param mixed $class * @param array $constructorParams * @param array $params * @param bool|\PHPUnit_Framework_TestCase $testCase * * @return object */ public static function constructEmpty($class, $constructorParams = [], $params = [], $testCase = false) { $class = self::getClassname($class); $reflection = new \ReflectionClass($class); $methods = get_class_methods($class); $methods = array_filter( $methods, function ($i) { return !in_array($i, Stub::$magicMethods); } ); $mock = self::generateMock($class, $methods, $constructorParams, $testCase); self::bindParameters($mock, $params); return self::markAsMock($mock, $reflection); } /** * Instantiates a class instance by running constructor with all methods replaced with dummies, except one. * Parameters for constructor passed as second argument * Properties and methods can be set in third argument. * Even protected and private properties can be set. * * ``` php * <?php * Stub::constructEmptyExcept('User', 'save'); * Stub::constructEmptyExcept('User', 'save', array('autosave' => false), array('name' => 'davert')); * ?> * ``` * * Accepts either name of class or object of that class * * ``` php * <?php * Stub::constructEmptyExcept(new User, 'save', array('autosave' => false), array('name' => 'davert')); * ?> * ``` * * To replace method provide it's name as a key in third parameter * and it's return value or callback function as parameter * * ``` php * <?php * Stub::constructEmptyExcept('User', 'save', array(), array('save' => function () { return true; })); * Stub::constructEmptyExcept('User', 'save', array(), array('save' => true })); * ?> * ``` * * @param mixed $class * @param string $method * @param array $constructorParams * @param array $params * @param bool|\PHPUnit_Framework_TestCase $testCase * * @return object */ public static function constructEmptyExcept( $class, $method, $constructorParams = [], $params = [], $testCase = false ) { $class = self::getClassname($class); $reflectionClass = new \ReflectionClass($class); $methods = $reflectionClass->getMethods(); $methods = array_filter( $methods, function ($m) { return !in_array($m->name, Stub::$magicMethods); } ); $methods = array_filter( $methods, function ($m) use ($method) { return $method != $m->name; } ); $methods = array_map( function ($m) { return $m->name; }, $methods ); $methods = count($methods) ? $methods : null; $mock = self::generateMock($class, $methods, $constructorParams, $testCase); self::bindParameters($mock, $params); return self::markAsMock($mock, $reflectionClass); } private static function generateMock() { return self::doGenerateMock(func_get_args()); } /** * Returns a mock object for the specified abstract class with all abstract * methods of the class mocked. Concrete methods to mock can be specified with * the last parameter * * @param string $originalClassName * @param array $arguments * @param string $mockClassName * @param boolean $callOriginalConstructor * @param boolean $callOriginalClone * @param boolean $callAutoload * @param array $mockedMethods * @param boolean $cloneArguments * * @return object * @since Method available since Release 1.0.0 * @throws \InvalidArgumentException */ private static function generateMockForAbstractClass() { return self::doGenerateMock(func_get_args(), true); } private static function doGenerateMock($args, $isAbstract = false) { $testCase = self::extractTestCaseFromArgs($args); $methodName = $isAbstract ? 'getMockForAbstractClass' : 'getMock'; $generatorClass = new \PHPUnit_Framework_MockObject_Generator; // using PHPUnit 5.4 mocks registration if (version_compare(\PHPUnit_Runner_Version::series(), '5.4', '>=') && $testCase instanceof \PHPUnit_Framework_TestCase ) { $mock = call_user_func_array([$generatorClass, $methodName], $args); $testCase->registerMockObject($mock); return $mock; } if ($testCase instanceof \PHPUnit_Framework_TestCase) { $generatorClass = $testCase; } return call_user_func_array([$generatorClass, $methodName], $args); } private static function extractTestCaseFromArgs(&$args) { $argsLength = count($args) - 1; $testCase = $args[$argsLength]; unset($args[$argsLength]); return $testCase; } /** * Replaces properties of current stub * * @param \PHPUnit_Framework_MockObject_MockObject $mock * @param array $params * * @return mixed * @throws \LogicException */ public static function update($mock, array $params) { //do not rely on __mocked property, check typ eof $mock if (!$mock instanceof \PHPUnit_Framework_MockObject_MockObject) { throw new \LogicException('You can update only stubbed objects'); } self::bindParameters($mock, $params); return $mock; } /** * @param \PHPUnit_Framework_MockObject_MockObject $mock * @param array $params */ protected static function bindParameters($mock, $params) { $reflectionClass = new \ReflectionClass($mock); if ($mock instanceof \PHPUnit_Framework_MockObject_MockObject) { $parentClass = $reflectionClass->getParentClass(); if ($parentClass !== false) { $reflectionClass = $reflectionClass->getParentClass(); } } foreach ($params as $param => $value) { // redefine method if ($reflectionClass->hasMethod($param)) { if ($value instanceof StubMarshaler) { $marshaler = $value; $mock ->expects($marshaler->getMatcher()) ->method($param) ->will(new \PHPUnit_Framework_MockObject_Stub_ReturnCallback($marshaler->getValue())); } elseif ($value instanceof \Closure) { $mock ->expects(new \PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) ->method($param) ->will(new \PHPUnit_Framework_MockObject_Stub_ReturnCallback($value)); } elseif ($value instanceof ConsecutiveMap) { $consecutiveMap = $value; $mock ->expects(new \PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) ->method($param) ->will(new \PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($consecutiveMap->getMap())); } else { $mock ->expects(new \PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) ->method($param) ->will(new \PHPUnit_Framework_MockObject_Stub_Return($value)); } } elseif ($reflectionClass->hasProperty($param)) { $reflectionProperty = $reflectionClass->getProperty($param); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($mock, $value); continue; } else { if ($reflectionClass->hasMethod('__set')) { try { $mock->{$param} = $value; } catch (\Exception $e) { throw new \PHPUnit_Framework_Exception( sprintf( 'Could not add property %1$s, class %2$s implements __set method, ' . 'and no %1$s property exists', $param, $reflectionClass->getName() ), $e->getCode(), $e ); } } else { $mock->{$param} = $value; } continue; } } } /** * @todo should be simplified */ protected static function getClassname($object) { if (is_object($object)) { return get_class($object); } if (is_callable($object)) { return call_user_func($object); } return $object; } /** * @param \ReflectionClass $reflection * @param $params * @return array */ protected static function getMethodsToReplace(\ReflectionClass $reflection, $params) { $callables = []; foreach ($params as $method => $value) { if ($reflection->hasMethod($method)) { $callables[$method] = $value; } } return $callables; } /** * Checks if a method never has been invoked * * If method invoked, it will immediately throw an * exception. * * ``` php * <?php * $user = Stub::make('User', array('getName' => Stub::never(), 'someMethod' => function() {})); * $user->someMethod(); * ?> * ``` * * @param mixed $params * * @return StubMarshaler */ public static function never($params = null) { return new StubMarshaler( new \PHPUnit_Framework_MockObject_Matcher_InvokedCount(0), self::closureIfNull($params) ); } /** * Checks if a method has been invoked exactly one * time. * * If the number is less or greater it will later be checked in verify() and also throw an * exception. * * ``` php * <?php * $user = Stub::make( * 'User', * array( * 'getName' => Stub::once(function() { return 'Davert';}), * 'someMethod' => function() {} * ) * ); * $userName = $user->getName(); * $this->assertEquals('Davert', $userName); * ?> * ``` * * @param mixed $params * * @return StubMarshaler */ public static function once($params = null) { return new StubMarshaler( new \PHPUnit_Framework_MockObject_Matcher_InvokedCount(1), self::closureIfNull($params) ); } /** * Checks if a method has been invoked at least one * time. * * If the number of invocations is 0 it will throw an exception in verify. * * ``` php * <?php * $user = Stub::make( * 'User', * array( * 'getName' => Stub::atLeastOnce(function() { return 'Davert';}), * 'someMethod' => function() {} * ) * ); * $user->getName(); * $user->getName(); * ?> * ``` * * @param mixed $params * * @return StubMarshaler */ public static function atLeastOnce($params = null) { return new StubMarshaler( new \PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce, self::closureIfNull($params) ); } /** * Checks if a method has been invoked a certain amount * of times. * If the number of invocations exceeds the value it will immediately throw an * exception, * If the number is less it will later be checked in verify() and also throw an * exception. * * ``` php * <?php * $user = Stub::make( * 'User', * array( * 'getName' => Stub::exactly(3, function() { return 'Davert';}), * 'someMethod' => function() {} * ) * ); * $user->getName(); * $user->getName(); * $user->getName(); * ?> * ``` * * @param int $count * @param mixed $params * * @return StubMarshaler */ public static function exactly($count, $params = null) { return new StubMarshaler( new \PHPUnit_Framework_MockObject_Matcher_InvokedCount($count), self::closureIfNull($params) ); } private static function closureIfNull($params) { if ($params == null) { return function () { }; } else { return $params; } } /** * Stubbing a method call to return a list of values in the specified order. * * ``` php * <?php * $user = Stub::make('User', array('getName' => Stub::consecutive('david', 'emma', 'sam', 'amy'))); * $user->getName(); //david * $user->getName(); //emma * $user->getName(); //sam * $user->getName(); //amy * ?> * ``` * * @return ConsecutiveMap */ public static function consecutive() { return new ConsecutiveMap(func_get_args()); } } <?php namespace Codeception\Util; /** * This class is left for BC compatibility. * Most of its contents moved to parent * * Class Soap * @package Codeception\Util */ class Soap extends Xml { public static function request() { return new XmlBuilder(); } public static function response() { return new XmlBuilder(); } } <?php namespace Codeception; use Codeception\Lib\Actor\Shared\Comment; use Codeception\Lib\Actor\Shared\Friend; use Codeception\Step\Executor; abstract class Actor { use Comment; use Friend; /** * @var \Codeception\Scenario */ protected $scenario; public function __construct(Scenario $scenario) { $this->scenario = $scenario; } /** * @return \Codeception\Scenario */ protected function getScenario() { return $this->scenario; } public function wantToTest($text) { $this->wantTo('test ' . $text); } public function wantTo($text) { $this->scenario->setFeature(mb_strtolower($text, 'utf-8')); } public function __call($method, $arguments) { $class = get_class($this); throw new \RuntimeException("Call to undefined method $class::$method"); } /** * Lazy-execution given anonymous function * @param $callable \Closure * @return $this */ public function execute($callable) { $this->scenario->addStep(new Executor($callable, [])); $callable(); return $this; } } <?php namespace Codeception\Test\Interfaces; interface StrictCoverage { public function getLinesToBeCovered(); public function getLinesToBeUsed(); } <?php namespace Codeception\Test\Interfaces; interface Dependent { public function getDependencies(); } <?php namespace Codeception\Test\Interfaces; /** * TestCases that do not follow OOP */ interface Plain { } <?php namespace Codeception\Test\Interfaces; interface ScenarioDriven { public function getFeature(); /** * @return \Codeception\Scenario */ public function getScenario(); public function getScenarioText($format = 'text'); public function preload(); public function getSourceCode(); } <?php namespace Codeception\Test\Interfaces; interface Descriptive extends \PHPUnit_Framework_SelfDescribing { public function getFileName(); public function getSignature(); } <?php namespace Codeception\Test\Interfaces; interface Reported { /** * Field values for XML/JSON/TAP reports * * @return array */ public function getReportFields(); } <?php namespace Codeception\Test; use Codeception\TestInterface; use Codeception\Util\ReflectionHelper; /** * The most simple testcase (with only one test in it) which can be executed by PHPUnit/Codeception. * It can be extended with included traits. Turning on/off a trait should not break class functionality. * * Class has exactly one method to be executed for testing, wrapped with before/after callbacks delivered from included traits. * A trait providing before/after callback should contain corresponding protected methods: `{traitName}Start` and `{traitName}End`, * then this trait should be enabled in `hooks` property. * * Inherited class must implement `test` method. */ abstract class Test implements TestInterface, Interfaces\Descriptive { use Feature\AssertionCounter; use Feature\CodeCoverage; use Feature\ErrorLogger; use Feature\MetadataCollector; use Feature\IgnoreIfMetadataBlocked; private $testResult; private $ignored = false; /** * Enabled traits with methods to be called before and after the test. * * @var array */ protected $hooks = [ 'ignoreIfMetadataBlocked', 'codeCoverage', 'assertionCounter', 'errorLogger' ]; const STATUS_FAIL = 'fail'; const STATUS_ERROR = 'error'; const STATUS_OK = 'ok'; const STATUS_PENDING = 'pending'; /** * Everything inside this method is treated as a test. * * @return mixed */ abstract public function test(); /** * Test representation * * @return mixed */ abstract public function toString(); /** * Runs a test and collects its result in a TestResult instance. * Executes before/after hooks coming from traits. * * @param \PHPUnit_Framework_TestResult $result * @return \PHPUnit_Framework_TestResult */ final public function run(\PHPUnit_Framework_TestResult $result = null) { $this->testResult = $result; $status = self::STATUS_PENDING; $time = 0; $e = null; $result->startTest($this); foreach ($this->hooks as $hook) { if (method_exists($this, $hook.'Start')) { $this->{$hook.'Start'}(); } } $failedToStart = ReflectionHelper::readPrivateProperty($result, 'lastTestFailed'); if (!$this->ignored && !$failedToStart) { \PHP_Timer::start(); try { $this->test(); $status = self::STATUS_OK; } catch (\PHPUnit_Framework_AssertionFailedError $e) { $status = self::STATUS_FAIL; } catch (\PHPUnit_Framework_Exception $e) { $status = self::STATUS_ERROR; } catch (\Throwable $e) { $e = new \PHPUnit_Framework_ExceptionWrapper($e); $status = self::STATUS_ERROR; } catch (\Exception $e) { $e = new \PHPUnit_Framework_ExceptionWrapper($e); $status = self::STATUS_ERROR; } $time = \PHP_Timer::stop(); } foreach (array_reverse($this->hooks) as $hook) { if (method_exists($this, $hook.'End')) { $this->{$hook.'End'}($status, $time, $e); } } $result->endTest($this, $time); return $result; } public function getTestResultObject() { return $this->testResult; } /** * This class represents exactly one test * @return int */ public function count() { return 1; } /** * Should a test be skipped (can be set from hooks) * * @param boolean $ignored */ protected function ignore($ignored) { $this->ignored = $ignored; } } <?php namespace Codeception\Test; use Codeception\Test\Interfaces\Descriptive; use Codeception\Test\Interfaces\Plain; use Codeception\Util\ReflectionHelper; class Descriptor { /** * Provides a test name which can be located by * * @param \PHPUnit_Framework_SelfDescribing $testCase * @return string */ public static function getTestSignature(\PHPUnit_Framework_SelfDescribing $testCase) { if ($testCase instanceof Descriptive) { return $testCase->getSignature(); } if ($testCase instanceof \PHPUnit_Framework_TestCase) { return get_class($testCase) . ':' . $testCase->getName(false); } return $testCase->toString(); } public static function getTestAsString(\PHPUnit_Framework_SelfDescribing $testCase) { if ($testCase instanceof \PHPUnit_Framework_TestCase) { $text = $testCase->getName(); $text = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\\1 \\2', $text); $text = preg_replace('/([a-z\d])([A-Z])/', '\\1 \\2', $text); $text = preg_replace('/^test /', '', $text); $text = ucfirst(strtolower($text)); $text = str_replace(['::', 'with data set'], [':', '|'], $text); return ReflectionHelper::getClassShortName($testCase) . ': ' . $text; } return $testCase->toString(); } /** * Provides a test file name relative to Codeception root * * @param \PHPUnit_Framework_SelfDescribing $testCase * @return mixed */ public static function getTestFileName(\PHPUnit_Framework_SelfDescribing $testCase) { if ($testCase instanceof Descriptive) { return codecept_relative_path(realpath($testCase->getFileName())); } return (new \ReflectionClass($testCase))->getFileName(); } /** * @param \PHPUnit_Framework_SelfDescribing $testCase * @return mixed|string */ public static function getTestFullName(\PHPUnit_Framework_SelfDescribing $testCase) { if ($testCase instanceof Plain) { return self::getTestFileName($testCase); } if ($testCase instanceof Descriptive) { $signature = $testCase->getSignature(); // cut everything before ":" from signature return self::getTestFileName($testCase) . ':' . preg_replace('~^(.*?):~', '', $signature); } if ($testCase instanceof \PHPUnit_Framework_TestCase) { return self::getTestFileName($testCase) . ':' . $testCase->getName(false); } return self::getTestFileName($testCase) . ':' . $testCase->toString(); } } <?php namespace Codeception\Test; use Codeception\Exception\TestParseException; use Codeception\Lib\Parser; use Codeception\Lib\Console\Message; /** * Executes tests delivered in Cept format. * Prepares metadata, parses test body on preload, and executes a test in `test` method. */ class Cept extends Test implements Interfaces\Plain, Interfaces\ScenarioDriven, Interfaces\Reported, Interfaces\Dependent { use Feature\ScenarioLoader; /** * @var Parser */ protected $parser; public function __construct($name, $file) { $metadata = new Metadata(); $metadata->setName($name); $metadata->setFilename($file); $this->setMetadata($metadata); $this->createScenario(); $this->parser = new Parser($this->getScenario(), $this->getMetadata()); } public function preload() { $this->getParser()->prepareToRun($this->getSourceCode()); } public function test() { $scenario = $this->getScenario(); $testFile = $this->getMetadata()->getFilename(); /** @noinspection PhpIncludeInspection */ try { require $testFile; } catch (\ParseError $e) { throw new TestParseException($testFile, $e->getMessage()); } } public function getSignature() { return $this->getMetadata()->getName() . 'Cept'; } public function toString() { return $this->getSignature() . ': ' . Message::ucfirst($this->getFeature()); } public function getSourceCode() { return file_get_contents($this->getFileName()); } public function getReportFields() { return [ 'name' => basename($this->getFileName(), 'Cept.php'), 'file' => $this->getFileName(), 'feature' => $this->getFeature() ]; } /** * @return Parser */ protected function getParser() { return $this->parser; } public function getDependencies() { return $this->getMetadata()->getDependencies(); } } <?php namespace Codeception\Test; use Codeception\Configuration; use Codeception\Exception\ModuleException; use Codeception\Lib\Di; use Codeception\Lib\Notification; use Codeception\Scenario; use Codeception\TestInterface; /** * Represents tests from PHPUnit compatible format. */ class Unit extends \PHPUnit_Framework_TestCase implements Interfaces\Reported, Interfaces\Dependent, TestInterface { /** * @var Metadata */ private $metadata; public function getMetadata() { if (!$this->metadata) { $this->metadata = new Metadata(); } return $this->metadata; } protected function setUp() { if ($this->getMetadata()->isBlocked()) { if ($this->getMetadata()->getSkip() !== null) { $this->markTestSkipped($this->getMetadata()->getSkip()); } if ($this->getMetadata()->getIncomplete() !== null) { $this->markTestIncomplete($this->getMetadata()->getIncomplete()); } return; } /** @var $di Di **/ $di = $this->getMetadata()->getService('di'); $di->set(new Scenario($this)); // auto-inject $tester property if (($this->getMetadata()->getCurrent('actor')) && ($property = lcfirst(Configuration::config()['actor_suffix']))) { $this->$property = $di->instantiate($this->getMetadata()->getCurrent('actor')); } // Auto inject into the _inject method $di->injectDependencies($this); // injecting dependencies $this->_before(); } /** * @Override */ protected function _before() { } protected function tearDown() { $this->_after(); } /** * @Override */ protected function _after() { } /** * If the method exists (PHPUnit 5) forward the call to the parent class, otherwise * call `expectException` instead (PHPUnit 6) */ public function setExpectedException($exception, $message = '', $code = null) { if (is_callable('parent::setExpectedException')) { parent::setExpectedException($exception, $message, $code); } else { Notification::deprecate('PHPUnit\Framework\TestCase::setExpectedException deprecated in favor of expectException, expectExceptionMessage, and expectExceptionCode'); $this->expectException($exception); if ($message !== '') { $this->expectExceptionMessage($message); } if ($code !== null) { $this->expectExceptionCode($code); } } } /** * @param $module * @return \Codeception\Module * @throws ModuleException */ public function getModule($module) { $modules = $this->getMetadata()->getCurrent('modules'); if (!isset($modules[$module])) { throw new ModuleException($module, "Module can't be accessed"); } return $modules[$module]; } /** * Returns current values */ public function getCurrent($current) { return $this->getMetadata()->getCurrent($current); } /** * @return array */ public function getReportFields() { return [ 'name' => $this->getName(), 'class' => get_class($this), 'file' => $this->getMetadata()->getFilename() ]; } public function getDependencies() { $names = []; foreach ($this->getMetadata()->getDependencies() as $required) { if ((strpos($required, ':') === false) and method_exists($this, $required)) { $required = get_class($this) . ":$required"; } $names[] = $required; } return $names; } /** * Reset PHPUnit's dependencies * @return bool */ public function handleDependencies() { $dependencies = $this->getDependencies(); if (empty($dependencies)) { return true; } $passed = $this->getTestResultObject()->passed(); $dependencyInput = []; foreach ($dependencies as $dependency) { $dependency = str_replace(':', '::', $dependency); // Codeception => PHPUnit format if (strpos($dependency, '::') === false) { // check it is method of same class $dependency = get_class($this) . '::' . $dependency; } if (isset($passed[$dependency])) { $dependencyInput[] = $passed[$dependency]['result']; } else { $dependencyInput[] = null; } } $this->setDependencyInput($dependencyInput); return true; } } <?php namespace Codeception\Test; use Codeception\Test\Loader\Cept as CeptLoader; use Codeception\Test\Loader\Cest as CestLoader; use Codeception\Test\Loader\Unit as UnitLoader; use Codeception\Test\Loader\Gherkin as GherkinLoader; use Symfony\Component\Finder\Finder; /** * Loads all Codeception supported test formats from a directory. * * ``` php * <?php * $testLoader = new \Codeception\TestLoader('tests/unit'); * $testLoader->loadTests(); * $tests = $testLoader->getTests(); * ?> * ``` * You can load specific file * * ``` php * <?php * $testLoader = new \Codeception\TestLoader('tests/unit'); * $testLoader->loadTest('UserTest.php'); * $testLoader->loadTest('PostTest.php'); * $tests = $testLoader->getTests(); * ?> * ``` * or a subdirectory * * ``` php * <?php * $testLoader = new \Codeception\TestLoader('tests/unit'); * $testLoader->loadTest('models'); // all tests from tests/unit/models * $tests = $testLoader->getTests(); * ?> * ``` * */ class Loader { protected $formats = []; protected $tests = []; protected $path; public function __construct(array $suiteSettings) { $this->path = $suiteSettings['path']; $this->formats = [ new CeptLoader(), new CestLoader(), new UnitLoader(), new GherkinLoader($suiteSettings) ]; } public function getTests() { return $this->tests; } protected function relativeName($file) { return str_replace([$this->path, '\\'], ['', '/'], $file); } protected function findPath($path) { if (!file_exists($path) && substr($path, -strlen('.php')) !== '.php' && file_exists($newPath = $path . '.php') ) { return $newPath; } return $path; } protected function makePath($originalPath) { $path = $this->path . $this->relativeName($originalPath); if (file_exists($newPath = $this->findPath($path)) || file_exists($newPath = $this->findPath(getcwd() . "/{$originalPath}")) ) { $path = $newPath; } if (!file_exists($path)) { throw new \Exception("File or path $originalPath not found"); } return $path; } public function loadTest($path) { $path = $this->makePath($path); foreach ($this->formats as $format) { /** @var $format Loader **/ if (preg_match($format->getPattern(), $path)) { $format->loadTests($path); $this->tests = $format->getTests(); return; } } if (is_dir($path)) { $currentPath = $this->path; $this->path = $path; $this->loadTests(); $this->path = $currentPath; return; } throw new \Exception('Test format not supported. Please, check you use the right suffix. Available filetypes: Cept, Cest, Test'); } public function loadTests($fileName = null) { if ($fileName) { return $this->loadTest($fileName); } $finder = Finder::create()->files()->sortByName()->in($this->path)->followLinks(); foreach ($this->formats as $format) { /** @var $format Loader **/ $formatFinder = clone($finder); $testFiles = $formatFinder->name($format->getPattern()); foreach ($testFiles as $test) { $pathname = str_replace(["//", "\\\\"], ["/", "\\"], $test->getPathname()); $format->loadTests($pathname); } $this->tests = array_merge($this->tests, $format->getTests()); } } } <?php namespace Codeception\Test; use Codeception\Example; use Codeception\Lib\Console\Message; use Codeception\Lib\Parser; use Codeception\Step\Comment; use Codeception\Util\Annotation; use Codeception\Util\ReflectionHelper; /** * Executes tests delivered in Cest format. * * Handles loading of Cest cases, executing specific methods, following the order from `@before` and `@after` annotations. */ class Cest extends Test implements Interfaces\ScenarioDriven, Interfaces\Reported, Interfaces\Dependent, Interfaces\StrictCoverage { use Feature\ScenarioLoader; /** * @var Parser */ protected $parser; protected $testClassInstance; protected $testMethod; public function __construct($testClass, $methodName, $fileName) { $metadata = new Metadata(); $metadata->setName($methodName); $metadata->setFilename($fileName); $this->setMetadata($metadata); $this->testClassInstance = $testClass; $this->testMethod = $methodName; $this->createScenario(); $this->parser = new Parser($this->getScenario(), $this->getMetadata()); } public function preload() { $this->scenario->setFeature($this->getSpecFromMethod()); $code = $this->getSourceCode(); $this->parser->parseFeature($code); $this->parser->attachMetadata(Annotation::forMethod($this->testClassInstance, $this->testMethod)->raw()); $this->getMetadata()->getService('di')->injectDependencies($this->testClassInstance); // add example params to feature if ($this->getMetadata()->getCurrent('example')) { $step = new Comment('', $this->getMetadata()->getCurrent('example')); $this->getScenario()->setFeature($this->getScenario()->getFeature() . ' | '. $step->getArgumentsAsString(100)); } } public function getSourceCode() { $method = new \ReflectionMethod($this->testClassInstance, $this->testMethod); $start_line = $method->getStartLine() - 1; // it's actually - 1, otherwise you wont get the function() block $end_line = $method->getEndLine(); $source = file($method->getFileName()); return implode("", array_slice($source, $start_line, $end_line - $start_line)); } public function getSpecFromMethod() { $text = $this->testMethod; $text = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\\1 \\2', $text); $text = preg_replace('/([a-z\d])([A-Z])/', '\\1 \\2', $text); $text = strtolower($text); return $text; } public function test() { $actorClass = $this->getMetadata()->getCurrent('actor'); $I = new $actorClass($this->getScenario()); try { $this->executeHook($I, 'before'); $this->executeBeforeMethods($this->testMethod, $I); $this->executeTestMethod($I); $this->executeAfterMethods($this->testMethod, $I); $this->executeHook($I, 'after'); } catch (\Exception $e) { $this->executeHook($I, 'failed'); // fails and errors are now handled by Codeception\PHPUnit\Listener throw $e; } } protected function executeHook($I, $hook) { if (is_callable([$this->testClassInstance, "_$hook"])) { $this->invoke("_$hook", [$I, $this->scenario]); } } protected function executeBeforeMethods($testMethod, $I) { $annotations = \PHPUnit_Util_Test::parseTestMethodAnnotations(get_class($this->testClassInstance), $testMethod); if (!empty($annotations['method']['before'])) { foreach ($annotations['method']['before'] as $m) { $this->executeContextMethod(trim($m), $I); } } } protected function executeAfterMethods($testMethod, $I) { $annotations = \PHPUnit_Util_Test::parseTestMethodAnnotations(get_class($this->testClassInstance), $testMethod); if (!empty($annotations['method']['after'])) { foreach ($annotations['method']['after'] as $m) { $this->executeContextMethod(trim($m), $I); } } } protected function executeContextMethod($context, $I) { if (method_exists($this->testClassInstance, $context)) { $this->executeBeforeMethods($context, $I); $this->invoke($context, [$I, $this->scenario]); $this->executeAfterMethods($context, $I); return; } throw new \LogicException( "Method $context defined in annotation but does not exist in " . get_class($this->testClassInstance) ); } protected function invoke($methodName, array $context) { foreach ($context as $class) { $this->getMetadata()->getService('di')->set($class); } $this->getMetadata()->getService('di')->injectDependencies($this->testClassInstance, $methodName, $context); } protected function executeTestMethod($I) { if (!method_exists($this->testClassInstance, $this->testMethod)) { throw new \Exception("Method {$this->testMethod} can't be found in tested class"); } if ($this->getMetadata()->getCurrent('example')) { $this->invoke($this->testMethod, [$I, $this->scenario, new Example($this->getMetadata()->getCurrent('example'))]); return; } $this->invoke($this->testMethod, [$I, $this->scenario]); } public function toString() { return sprintf('%s: %s', ReflectionHelper::getClassShortName($this->getTestClass()), Message::ucfirst($this->getFeature())); } public function getSignature() { return get_class($this->getTestClass()) . ":" . $this->getTestMethod(); } public function getTestClass() { return $this->testClassInstance; } public function getTestMethod() { return $this->testMethod; } /** * @return array */ public function getReportFields() { return [ 'file' => $this->getFileName(), 'name' => $this->getTestMethod(), 'class' => get_class($this->getTestClass()), 'feature' => $this->getFeature() ]; } protected function getParser() { return $this->parser; } public function getDependencies() { $names = []; foreach ($this->getMetadata()->getDependencies() as $required) { if ((strpos($required, ':') === false) and method_exists($this->getTestClass(), $required)) { $required = get_class($this->getTestClass()) . ":$required"; } $names[] = $required; } return $names; } public function getLinesToBeCovered() { $class = get_class($this->getTestClass()); $method = $this->getTestMethod(); return \PHPUnit_Util_Test::getLinesToBeCovered($class, $method); } public function getLinesToBeUsed() { $class = get_class($this->getTestClass()); $method = $this->getTestMethod(); return \PHPUnit_Util_Test::getLinesToBeUsed($class, $method); } } <?php namespace Codeception\Test\Feature; use Codeception\Test\Metadata; trait MetadataCollector { /** * @var Metadata */ private $metadata; protected function setMetadata(Metadata $metadata) { $this->metadata = $metadata; } public function getMetadata() { return $this->metadata; } public function getName() { return $this->getMetadata()->getName(); } public function getFileName() { return $this->getMetadata()->getFilename(); } } <?php namespace Codeception\Test\Feature; use Codeception\Test\Descriptor; use Codeception\Test\Interfaces\StrictCoverage; trait CodeCoverage { /** * @return \PHPUnit_Framework_TestResult */ abstract public function getTestResultObject(); public function codeCoverageStart() { $codeCoverage = $this->getTestResultObject()->getCodeCoverage(); if (!$codeCoverage) { return; } $codeCoverage->start(Descriptor::getTestSignature($this)); } public function codeCoverageEnd($status, $time) { $codeCoverage = $this->getTestResultObject()->getCodeCoverage(); if (!$codeCoverage) { return; } if ($this instanceof StrictCoverage) { $linesToBeCovered = $this->getLinesToBeCovered(); $linesToBeUsed = $this->getLinesToBeUsed(); } else { $linesToBeCovered = []; $linesToBeUsed = []; } try { $codeCoverage->stop(true, $linesToBeCovered, $linesToBeUsed); } catch (\PHP_CodeCoverage_Exception $cce) { if ($status === \Codeception\Test\Test::STATUS_OK) { $this->getTestResultObject()->addError($this, $cce, $time); } } } } <?php namespace Codeception\Test\Feature; use Codeception\Test\Metadata; trait IgnoreIfMetadataBlocked { /** * @return Metadata */ abstract protected function getMetadata(); abstract protected function ignore($ignored); /** * @return \PHPUnit_Framework_TestResult */ abstract protected function getTestResultObject(); protected function ignoreIfMetadataBlockedStart() { if (!$this->getMetadata()->isBlocked()) { return; } $this->ignore(true); if ($this->getMetadata()->getSkip() !== null) { $this->getTestResultObject()->addFailure($this, new \PHPUnit_Framework_SkippedTestError((string)$this->getMetadata()->getSkip()), 0); return; } if ($this->getMetadata()->getIncomplete() !== null) { $this->getTestResultObject()->addFailure($this, new \PHPUnit_Framework_IncompleteTestError((string)$this->getMetadata()->getIncomplete()), 0); return; } } } <?php namespace Codeception\Test\Feature; use Codeception\Test\Test as CodeceptionTest; trait ErrorLogger { /** * @return \PHPUnit_Framework_TestResult */ abstract public function getTestResultObject(); public function errorLoggerEnd($status, $time, $exception = null) { if (!$exception) { return; } if ($status === CodeceptionTest::STATUS_ERROR) { $this->getTestResultObject()->addError($this, $exception, $time); } if ($status === CodeceptionTest::STATUS_FAIL) { $this->getTestResultObject()->addFailure($this, $exception, $time); } } } <?php namespace Codeception\Test\Feature; use Codeception\Lib\Parser; use Codeception\Scenario; use Codeception\Test\Metadata; trait ScenarioLoader { /** * @var Scenario */ private $scenario; /** * @return Metadata */ abstract public function getMetadata(); protected function createScenario() { $this->scenario = new Scenario($this); } /** * @return Scenario */ public function getScenario() { return $this->scenario; } public function getFeature() { return $this->getScenario()->getFeature(); } public function getScenarioText($format = 'text') { $code = $this->getSourceCode(); $this->getParser()->parseFeature($code); $this->getParser()->parseSteps($code); if ($format == 'html') { return $this->getScenario()->getHtml(); } return $this->getScenario()->getText(); } /** * @return Parser */ abstract protected function getParser(); abstract public function getSourceCode(); } <?php namespace Codeception\Test\Feature; trait AssertionCounter { protected $numAssertions = 0; public function getNumAssertions() { return $this->numAssertions; } protected function assertionCounterStart() { \PHPUnit_Framework_Assert::resetCount(); } protected function assertionCounterEnd() { $this->numAssertions = \PHPUnit_Framework_Assert::getCount(); } } <?php namespace Codeception\Test\Loader; use Codeception\Lib\Parser; use Codeception\Test\Cept as CeptFormat; class Cept implements LoaderInterface { protected $tests = []; public function getPattern() { return '~Cept\.php$~'; } function loadTests($file) { Parser::validate($file); $name = basename($file, 'Cept.php'); $cept = new CeptFormat($name, $file); $this->tests[] = $cept; } public function getTests() { return $this->tests; } } <?php namespace Codeception\Test\Loader; use Codeception\Lib\Parser; use Codeception\Test\Descriptor; use Codeception\Test\Unit as UnitFormat; use Codeception\Util\Annotation; class Unit implements LoaderInterface { protected $tests = []; public function getPattern() { return '~Test\.php$~'; } public function loadTests($path) { Parser::load($path); $testClasses = Parser::getClassesFromFile($path); foreach ($testClasses as $testClass) { $reflected = new \ReflectionClass($testClass); if (!$reflected->isInstantiable()) { continue; } foreach ($reflected->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { $test = $this->createTestFromPhpUnitMethod($reflected, $method); if (!$test) { continue; } $this->tests[] = $test; } } } public function getTests() { return $this->tests; } protected function createTestFromPhpUnitMethod(\ReflectionClass $class, \ReflectionMethod $method) { if (!\PHPUnit_Framework_TestSuite::isTestMethod($method)) { return; } $test = \PHPUnit_Framework_TestSuite::createTest($class, $method->name); if ($test instanceof \PHPUnit_Framework_TestSuite_DataProvider) { foreach ($test->tests() as $t) { $this->enhancePhpunitTest($t); } return $test; } $this->enhancePhpunitTest($test); return $test; } protected function enhancePhpunitTest(\PHPUnit_Framework_TestCase $test) { $className = get_class($test); $methodName = $test->getName(false); $dependencies = \PHPUnit_Util_Test::getDependencies($className, $methodName); $test->setDependencies($dependencies); if ($test instanceof UnitFormat) { $test->getMetadata()->setFilename(Descriptor::getTestFileName($test)); $test->getMetadata()->setDependencies($dependencies); $test->getMetadata()->setEnv(Annotation::forMethod($test, $methodName)->fetchAll('env')); } } } <?php namespace Codeception\Test\Loader; use Codeception\Exception\TestParseException; use Codeception\Lib\Parser; use Codeception\Test\Cest as CestFormat; use Codeception\Util\Annotation; use Codeception\Util\ReflectionHelper; class Cest implements LoaderInterface { protected $tests = []; public function getTests() { return $this->tests; } public function getPattern() { return '~Cest\.php$~'; } public function loadTests($file) { Parser::load($file); $testClasses = Parser::getClassesFromFile($file); foreach ($testClasses as $testClass) { if (substr($testClass, -strlen('Cest')) !== 'Cest') { continue; } if (!(new \ReflectionClass($testClass))->isInstantiable()) { continue; } $unit = new $testClass; $methods = get_class_methods($testClass); foreach ($methods as $method) { if (strpos($method, '_') === 0) { continue; } $examples = []; // example Annotation $rawExamples = Annotation::forMethod($unit, $method)->fetchAll('example'); if (count($rawExamples)) { $examples = array_map( function ($v) { return Annotation::arrayValue($v); }, $rawExamples ); } // dataProvider Annotation $dataMethod = Annotation::forMethod($testClass, $method)->fetch('dataProvider'); // lowercase for back compatible if (empty($dataMethod)) { $dataMethod = Annotation::forMethod($testClass, $method)->fetch('dataprovider'); } if (!empty($dataMethod)) { try { $data = ReflectionHelper::invokePrivateMethod($unit, $dataMethod); // allow to mix example and dataprovider annotations $examples = array_merge($examples, $data); } catch (\Exception $e) { throw new TestParseException( $file, "DataProvider '$dataMethod' for $testClass->$method is invalid or not callable.\n" . "Make sure that the dataprovider exist within the test class." ); } } if (count($examples)) { $dataProvider = new \PHPUnit_Framework_TestSuite_DataProvider(); foreach ($examples as $k => $example) { if ($example === null) { throw new TestParseException( $file, "Example for $testClass->$method contains invalid data:\n" . $rawExamples[$k] . "\n" . "Make sure this is a valid JSON (Hint: \"-char for strings) or a single-line annotation in Doctrine-style" ); } $test = new CestFormat($unit, $method, $file); $test->getMetadata()->setCurrent(['example' => $example]); $dataProvider->addTest($test); } $this->tests[] = $dataProvider; continue; } $this->tests[] = new CestFormat($unit, $method, $file); } } } } <?php namespace Codeception\Test\Loader; use Behat\Gherkin\Filter\RoleFilter; use Behat\Gherkin\Keywords\ArrayKeywords as GherkinKeywords; use Behat\Gherkin\Lexer as GherkinLexer; use Behat\Gherkin\Node\ExampleNode; use Behat\Gherkin\Node\OutlineNode; use Behat\Gherkin\Node\ScenarioInterface; use Behat\Gherkin\Node\ScenarioNode; use Behat\Gherkin\Parser as GherkinParser; use Codeception\Configuration; use Codeception\Exception\ParseException; use Codeception\Exception\TestParseException; use Codeception\Test\Gherkin as GherkinFormat; use Codeception\Util\Annotation; class Gherkin implements LoaderInterface { protected static $defaultSettings = [ 'namespace' => '', 'actor' => '', 'gherkin' => [ 'contexts' => [ 'default' => [], 'tag' => [], 'role' => [] ] ] ]; protected $tests = []; /** * @var GherkinParser */ protected $parser; protected $settings = []; protected $steps = []; public function __construct($settings = []) { $this->settings = Configuration::mergeConfigs(self::$defaultSettings, $settings); if (!class_exists('Behat\Gherkin\Keywords\ArrayKeywords')) { throw new TestParseException('Feature file can only be parsed with Behat\Gherkin library. Please install `behat/gherkin` with Composer'); } $gherkin = new \ReflectionClass('Behat\Gherkin\Gherkin'); $gherkinClassPath = dirname($gherkin->getFileName()); $i18n = require $gherkinClassPath . '/../../../i18n.php'; $keywords = new GherkinKeywords($i18n); $lexer = new GherkinLexer($keywords); $this->parser = new GherkinParser($lexer); $this->fetchGherkinSteps(); } protected function fetchGherkinSteps() { $contexts = $this->settings['gherkin']['contexts']; foreach ($contexts['tag'] as $tag => $tagContexts) { $this->addSteps($tagContexts, "tag:$tag"); } foreach ($contexts['role'] as $role => $roleContexts) { $this->addSteps($roleContexts, "role:$role"); } if (empty($this->steps) && empty($contexts['default']) && $this->settings['actor']) { // if no context is set, actor to be a context $actorContext = $this->settings['namespace'] ? rtrim($this->settings['namespace'] . '\\' . $this->settings['actor'], '\\') : $this->settings['actor']; if ($actorContext) { $contexts['default'][] = $actorContext; } } $this->addSteps($contexts['default']); } protected function addSteps(array $contexts, $group = 'default') { $this->steps[$group] = []; foreach ($contexts as $context) { $methods = get_class_methods($context); if (!$methods) { continue; } foreach ($methods as $method) { $annotation = Annotation::forMethod($context, $method); foreach (['Given', 'When', 'Then'] as $type) { $patterns = $annotation->fetchAll($type); foreach ($patterns as $pattern) { if (!$pattern) { continue; } $this->validatePattern($pattern); $pattern = $this->makePlaceholderPattern($pattern); $this->steps[$group][$pattern] = [$context, $method]; } } } } } public function makePlaceholderPattern($pattern) { if (isset($this->settings['describe_steps'])) { return $pattern; } if (strpos($pattern, '/') !== 0) { $pattern = preg_quote($pattern); $pattern = preg_replace('~(\w+)\/(\w+)~', '(?:$1|$2)', $pattern); // or $pattern = preg_replace('~\\\\\((\w)\\\\\)~', '$1?', $pattern); // (s) $replacePattern = sprintf( '(?|\"%s\"|%s)', "((?|[^\"\\\\\\]|\\\\\\.)*?)", // matching escaped string in "" '[\D]{0,1}([\d\,\.]+)[\D]{0,1}' ); // or matching numbers with optional $ or € chars // params converting from :param to match 11 and "aaa" and "aaa\"aaa" $pattern = preg_replace('~"?\\\:(\w+)"?~', $replacePattern, $pattern); $pattern = "/^$pattern$/u"; // validating this pattern is slow, so we skip it now } return $pattern; } private function validatePattern($pattern) { if (strpos($pattern, '/') !== 0) { return; // not a user-regex but a string with placeholder } if (@preg_match($pattern, ' ') === false) { throw new ParseException("Loading Gherkin step with regex\n \n$pattern\n \nfailed. This regular expression is invalid."); } } public function loadTests($filename) { $featureNode = $this->parser->parse(file_get_contents($filename), $filename); if (!$featureNode) { return; } foreach ($featureNode->getScenarios() as $scenarioNode) { /** @var $scenarioNode ScenarioInterface **/ $steps = $this->steps['default']; // load default context foreach (array_merge($scenarioNode->getTags(), $featureNode->getTags()) as $tag) { // load tag contexts if (isset($this->steps["tag:$tag"])) { $steps = array_merge($steps, $this->steps["tag:$tag"]); } } $roles = $this->settings['gherkin']['contexts']['role']; // load role contexts foreach ($roles as $role => $context) { $filter = new RoleFilter($role); if ($filter->isFeatureMatch($featureNode)) { $steps = array_merge($steps, $this->steps["role:$role"]); break; } } if ($scenarioNode instanceof OutlineNode) { foreach ($scenarioNode->getExamples() as $example) { /** @var $example ExampleNode **/ $params = implode(', ', $example->getTokens()); $exampleNode = new ScenarioNode($scenarioNode->getTitle() . " | $params", $scenarioNode->getTags(), $example->getSteps(), $example->getKeyword(), $example->getLine()); $this->tests[] = new GherkinFormat($featureNode, $exampleNode, $steps); } continue; } $this->tests[] = new GherkinFormat($featureNode, $scenarioNode, $steps); } } public function getTests() { return $this->tests; } public function getPattern() { return '~\.feature$~'; } /** * @return array */ public function getSteps() { return $this->steps; } } <?php namespace Codeception\Test\Loader; interface LoaderInterface { public function loadTests($filename); public function getTests(); public function getPattern(); } <?php namespace Codeception\Test; use Behat\Gherkin\Node\FeatureNode; use Behat\Gherkin\Node\ScenarioNode; use Behat\Gherkin\Node\ScenarioInterface; use Behat\Gherkin\Node\StepNode; use Behat\Gherkin\Node\TableNode; use Codeception\Lib\Di; use Codeception\Lib\Generator\GherkinSnippets; use Codeception\Scenario; use Codeception\Step\Comment; use Codeception\Step\Meta; use Codeception\Test\Interfaces\Reported; use Codeception\Test\Interfaces\ScenarioDriven; class Gherkin extends Test implements ScenarioDriven, Reported { protected $steps = []; /** * @var FeatureNode */ protected $featureNode; /** * @var ScenarioNode */ protected $scenarioNode; /** * @var Scenario */ protected $scenario; public function __construct(FeatureNode $featureNode, ScenarioInterface $scenarioNode, $steps = []) { $this->featureNode = $featureNode; $this->scenarioNode = $scenarioNode; $this->steps = $steps; $this->setMetadata(new Metadata()); $this->scenario = new Scenario($this); $this->getMetadata()->setName($featureNode->getTitle()); $this->getMetadata()->setFeature($scenarioNode->getTitle()); $this->getMetadata()->setFilename($featureNode->getFile()); } public function preload() { $this->getMetadata()->setGroups($this->featureNode->getTags()); $this->getMetadata()->setGroups($this->scenarioNode->getTags()); $this->scenario->setMetaStep(null); if ($background = $this->featureNode->getBackground()) { foreach ($background->getSteps() as $step) { $this->validateStep($step); } } foreach ($this->scenarioNode->getSteps() as $step) { $this->validateStep($step); } if ($this->getMetadata()->getIncomplete()) { $this->getMetadata()->setIncomplete($this->getMetadata()->getIncomplete() . "\nRun gherkin:snippets to define missing steps"); } } public function getSignature() { return basename($this->getFileName(), '.feature') . ':' . $this->getFeature(); } public function test() { $this->makeContexts(); $description = explode("\n", $this->featureNode->getDescription()); foreach ($description as $line) { $this->getScenario()->runStep(new Comment($line)); } if ($background = $this->featureNode->getBackground()) { foreach ($background->getSteps() as $step) { $this->runStep($step); } } foreach ($this->scenarioNode->getSteps() as $step) { $this->runStep($step); } } protected function validateStep(StepNode $stepNode) { $stepText = $stepNode->getText(); if (GherkinSnippets::stepHasPyStringArgument($stepNode)) { $stepText .= ' ""'; } foreach ($this->steps as $pattern => $context) { $res = preg_match($pattern, $stepText); if (!$res) { continue; } return; } $incomplete = $this->getMetadata()->getIncomplete(); $this->getMetadata()->setIncomplete("$incomplete\nStep definition for `$stepText` not found in contexts"); } protected function runStep(StepNode $stepNode) { $params = []; if ($stepNode->hasArguments()) { $args = $stepNode->getArguments(); $table = $args[0]; if ($table instanceof TableNode) { $params = [$table->getTableAsString()]; } } $meta = new Meta($stepNode->getText(), $params); $meta->setPrefix($stepNode->getKeyword()); $this->scenario->setMetaStep($meta); // enable metastep $stepText = $stepNode->getText(); $hasPyStringArg = GherkinSnippets::stepHasPyStringArgument($stepNode); if ($hasPyStringArg) { // pretend it is inline argument $stepText .= ' ""'; } $this->getScenario()->comment(null); // make metastep to be printed even if no steps in it foreach ($this->steps as $pattern => $context) { $matches = []; if (!preg_match($pattern, $stepText, $matches)) { continue; } array_shift($matches); if ($hasPyStringArg) { // get rid off last fake argument array_pop($matches); } if ($stepNode->hasArguments()) { $matches = array_merge($matches, $stepNode->getArguments()); } call_user_func_array($context, $matches); // execute the step break; } $this->scenario->setMetaStep(null); // disable metastep } protected function makeContexts() { /** @var $di Di **/ $di = $this->getMetadata()->getService('di'); $di->set($this->getScenario()); $actorClass = $this->getMetadata()->getCurrent('actor'); if ($actorClass) { $di->set(new $actorClass($this->getScenario())); } foreach ($this->steps as $pattern => $step) { $di->instantiate($step[0]); $this->steps[$pattern][0] = $di->get($step[0]); } } public function toString() { return $this->featureNode->getTitle() . ': ' . $this->getFeature(); } public function getFeature() { return $this->getMetadata()->getFeature(); } /** * @return \Codeception\Scenario */ public function getScenario() { return $this->scenario; } public function getScenarioText($format = 'text') { return file_get_contents($this->getFileName()); } public function getSourceCode() { } /** * @return ScenarioNode */ public function getScenarioNode() { return $this->scenarioNode; } /** * @return FeatureNode */ public function getFeatureNode() { return $this->featureNode; } /** * Field values for XML/JSON/TAP reports * * @return array */ public function getReportFields() { return [ 'file' => $this->getFileName(), 'name' => $this->toString(), 'feature' => $this->getFeature() ]; } } <?php namespace Codeception\Test; use Codeception\Exception\InjectionException; class Metadata { protected $name; protected $filename; protected $feature; protected $env = []; protected $groups = []; protected $dependencies = []; protected $skip = null; protected $incomplete = null; protected $current = []; protected $services = []; protected $reports = []; /** * @return mixed */ public function getEnv() { return $this->env; } /** * @param mixed $env */ public function setEnv($env) { $this->env = $env; } /** * @return array */ public function getGroups() { return array_unique($this->groups); } /** * @param mixed $groups */ public function setGroups($groups) { $this->groups = array_merge($this->groups, $groups); } /** * @return mixed */ public function getSkip() { return $this->skip; } /** * @param mixed $skip */ public function setSkip($skip) { $this->skip = $skip; } /** * @return mixed */ public function getIncomplete() { return $this->incomplete; } /** * @param mixed $incomplete */ public function setIncomplete($incomplete) { $this->incomplete = $incomplete; } /** * @param string|null $key * @return mixed */ public function getCurrent($key = null) { if ($key) { if (isset($this->current[$key])) { return $this->current[$key]; } if ($key === 'name') { return $this->getName(); } return null; } return $this->current; } public function setCurrent(array $currents) { $this->current = array_merge($this->current, $currents); } /** * @return mixed */ public function getName() { return $this->name; } /** * @param mixed $name */ public function setName($name) { $this->name = $name; } /** * @return mixed */ public function getFilename() { return $this->filename; } /** * @param mixed $filename */ public function setFilename($filename) { $this->filename = $filename; } /** * @return array */ public function getDependencies() { return $this->dependencies; } /** * @param array $dependencies */ public function setDependencies($dependencies) { $this->dependencies = $dependencies; } public function isBlocked() { return $this->skip !== null || $this->incomplete !== null; } /** * @return mixed */ public function getFeature() { return $this->feature; } /** * @param mixed $feature */ public function setFeature($feature) { $this->feature = $feature; } /** * @param $service * @return array * @throws InjectionException */ public function getService($service) { if (!isset($this->services[$service])) { throw new InjectionException("Service $service is not defined and can't be accessed from a test"); } return $this->services[$service]; } /** * @param array $services */ public function setServices($services) { $this->services = $services; } /** * @return array */ public function getReports() { return $this->reports; } /** * @param $type * @param $report */ public function addReport($type, $report) { $this->reports[$type] = $report; } } <?php namespace Codeception; use Codeception\Exception\ModuleException; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\Lib\ModuleContainer; use Codeception\Util\Shared\Asserts; /** * Basic class for Modules and Helpers. * You must extend from it while implementing own helpers. * * Public methods of this class start with `_` prefix in order to ignore them in actor classes. * Module contains **HOOKS** which allow to handle test execution routine. * */ abstract class Module { use Asserts; /** * @var ModuleContainer */ protected $moduleContainer; /** * By setting it to false module wan't inherit methods of parent class. * * @var bool */ public static $includeInheritedActions = true; /** * Allows to explicitly set what methods have this class. * * @var array */ public static $onlyActions = []; /** * Allows to explicitly exclude actions from module. * * @var array */ public static $excludeActions = []; /** * Allows to rename actions * * @var array */ public static $aliases = []; protected $storage = []; protected $config = []; protected $backupConfig = []; protected $requiredFields = []; /** * Module constructor. * * Requires module container (to provide access between modules of suite) and config. * * @param ModuleContainer $moduleContainer * @param null $config */ public function __construct(ModuleContainer $moduleContainer, $config = null) { $this->moduleContainer = $moduleContainer; $this->backupConfig = $this->config; if (is_array($config)) { $this->_setConfig($config); } } /** * Allows to define initial module config. * Can be used in `_beforeSuite` hook of Helpers or Extensions * * ```php * <?php * public function _beforeSuite($settings = []) { * $this->getModule('otherModule')->_setConfig($this->myOtherConfig); * } * ``` * * @param $config * @throws Exception\ModuleConfigException * @throws ModuleException */ public function _setConfig($config) { $this->config = $this->backupConfig = array_merge($this->config, $config); $this->validateConfig(); } /** * Allows to redefine config for a specific test. * Config is restored at the end of a test. * * ```php * <?php * // cleanup DB only for specific group of tests * public function _before(Test $test) { * if (in_array('cleanup', $test->getMetadata()->getGroups()) { * $this->getModule('Db')->_reconfigure(['cleanup' => true]); * } * } * ``` * * @param $config * @throws Exception\ModuleConfigException * @throws ModuleException */ public function _reconfigure($config) { $this->config = array_merge($this->backupConfig, $config); $this->onReconfigure(); $this->validateConfig(); } /** * HOOK to be executed when config changes with `_reconfigure`. */ protected function onReconfigure() { // update client on reconfigurations } /** * Reverts config changed by `_reconfigure` */ public function _resetConfig() { $this->config = $this->backupConfig; } /** * Validates current config for required fields and required packages. * * @throws Exception\ModuleConfigException * @throws ModuleException */ protected function validateConfig() { $fields = array_keys($this->config); if (array_intersect($this->requiredFields, $fields) != $this->requiredFields) { throw new Exception\ModuleConfigException( get_class($this), "\nOptions: " . implode(', ', $this->requiredFields) . " are required\n" . "Please, update the configuration and set all the required fields\n\n" ); } if ($this instanceof RequiresPackage) { $errorMessage = ''; foreach ($this->_requires() as $className => $package) { if (class_exists($className)) { continue; } $errorMessage .= "Class $className can't be loaded, please add $package to composer.json\n"; } if ($errorMessage) { throw new ModuleException($this, $errorMessage); } } } /** * Returns a module name for a Module, a class name for Helper * * @return string */ public function _getName() { $moduleName = '\\'.get_class($this); if (strpos($moduleName, ModuleContainer::MODULE_NAMESPACE) === 0) { return substr($moduleName, strlen(ModuleContainer::MODULE_NAMESPACE)); } return $moduleName; } /** * Checks if a module has required fields * * @return bool */ public function _hasRequiredFields() { return !empty($this->requiredFields); } /** * **HOOK** triggered after module is created and configuration is loaded */ public function _initialize() { } /** * **HOOK** executed before suite * * @param array $settings */ public function _beforeSuite($settings = []) { } /** * **HOOK** executed after suite */ public function _afterSuite() { } /** * **HOOK** executed before step * * @param Step $step */ public function _beforeStep(Step $step) { } /** * **HOOK** executed after step * * @param Step $step */ public function _afterStep(Step $step) { } /** * **HOOK** executed before test * * @param TestInterface $test */ public function _before(TestInterface $test) { } /** * **HOOK** executed after test * * @param TestInterface $test */ public function _after(TestInterface $test) { } /** * **HOOK** executed when test fails but before `_after` * * @param TestInterface $test * @param \Exception $fail */ public function _failed(TestInterface $test, $fail) { } /** * Print debug message to the screen. * * @param $message */ protected function debug($message) { codecept_debug($message); } /** * Print debug message with a title * * @param $title * @param $message */ protected function debugSection($title, $message) { if (is_array($message) or is_object($message)) { $message = stripslashes(json_encode($message)); } $this->debug("[$title] $message"); } /** * Checks that module is enabled. * * @param $name * @return bool */ protected function hasModule($name) { return $this->moduleContainer->hasModule($name); } /** * Get all enabled modules * * @return array */ protected function getModules() { return $this->moduleContainer->all(); } /** * Get another module by its name: * * ```php * <?php * $this->getModule('WebDriver')->_findElements('.items'); * ``` * * @param $name * @return Module * @throws ModuleException */ protected function getModule($name) { if (!$this->hasModule($name)) { throw new Exception\ModuleException(__CLASS__, "Module $name couldn't be connected"); } return $this->moduleContainer->getModule($name); } /** * Get config values or specific config item. * * @param null $key * @return array|mixed|null */ public function _getConfig($key = null) { if (!$key) { return $this->config; } if (isset($this->config[$key])) { return $this->config[$key]; } return null; } protected function scalarizeArray($array) { foreach ($array as $k => $v) { if (!is_null($v) && !is_scalar($v)) { $array[$k] = (is_array($v) || $v instanceof \ArrayAccess) ? $this->scalarizeArray($v) : (string)$v; } } return $array; } } <?php namespace Codeception\Step; use Codeception\Step as CodeceptionStep; use Codeception\Lib\ModuleContainer; class Skip extends CodeceptionStep { public function run(ModuleContainer $container = null) { throw new \PHPUnit_Framework_SkippedTestError($this->getAction()); } public function __toString() { return $this->getAction(); } } <?php namespace Codeception\Step; use Codeception\Step; class Action extends Step { } <?php namespace Codeception\Step; use Codeception\Exception\ConditionalAssertionFailed; use Codeception\Lib\ModuleContainer; class ConditionalAssertion extends Assertion { public function run(ModuleContainer $container = null) { try { parent::run($container); } catch (\PHPUnit_Framework_AssertionFailedError $e) { throw new ConditionalAssertionFailed($e->getMessage(), $e->getCode(), $e); } } public function getAction() { $action = 'can' . ucfirst($this->action); $action = preg_replace('/^canDont/', 'cant', $action); return $action; } public function getHumanizedAction() { return $this->humanize($this->action . ' ' . $this->getHumanizedArguments()); } } <?php namespace Codeception\Step; use Codeception\Step as CodeceptionStep; class Condition extends CodeceptionStep { } <?php namespace Codeception\Step; use Codeception\Lib\ModuleContainer; use Codeception\Step as CodeceptionStep; class Meta extends CodeceptionStep { public function run(ModuleContainer $container = null) { } public function setTraceInfo($file, $line) { $this->file = $file; $this->line = $line; } public function setPrefix($actor) { $this->prefix = $actor; } public function getArgumentsAsString($maxLength = 200) { $argumentBackup = $this->arguments; $lastArgAsString = ''; $lastArg = end($this->arguments); if (is_string($lastArg) && strpos($lastArg, "\n") !== false) { $lastArgAsString = "\r\n " . str_replace("\n", "\n ", $lastArg); array_pop($this->arguments); } $result = parent::getArgumentsAsString($maxLength) . $lastArgAsString; $this->arguments = $argumentBackup; return $result; } public function setFailed($failed) { $this->failed = $failed; } } <?php namespace Codeception\Step; use Codeception\Lib\ModuleContainer; use Codeception\Step as CodeceptionStep; class Comment extends CodeceptionStep { public function __toString() { return (string) $this->getAction(); } public function toString($maxLength) { return mb_strcut($this->__toString(), 0, $maxLength, 'utf-8'); } public function getHtml($highlightColor = '#732E81') { return '<strong>' . $this->getAction() . '</strong>'; } public function getPhpCode($maxLength) { return '// ' . $this->getAction(); } public function run(ModuleContainer $container = null) { // don't do anything, let's rest } public function getPrefix() { return ''; } } <?php namespace Codeception\Step; use Codeception\Step as CodeceptionStep; class Assertion extends CodeceptionStep { } <?php namespace Codeception\Step; use Codeception\Step as CodeceptionStep; use Codeception\Lib\ModuleContainer; class Incomplete extends CodeceptionStep { public function run(ModuleContainer $container = null) { throw new \PHPUnit_Framework_IncompleteTestError($this->getAction()); } public function __toString() { return $this->getAction(); } } <?php namespace Codeception\Step; use Codeception\Step as CodeceptionStep; use Codeception\Lib\ModuleContainer; class Executor extends CodeceptionStep { protected $callable = null; public function __construct(\Closure $callable, $arguments = []) { parent::__construct('execute callable function', []); $this->callable = $callable; } public function run(ModuleContainer $container = null) { $callable = $this->callable; return $callable(); } } <?php namespace Codeception; use Codeception\Exception\ConfigurationException; use Codeception\Lib\ParamsLoader; use Codeception\Util\Autoload; use Codeception\Util\Template; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; class Configuration { protected static $suites = []; /** * @var array Current configuration */ protected static $config = null; /** * @var array environmental files configuration cache */ protected static $envConfig = []; /** * @var string Directory containing main configuration file. * @see self::projectDir() */ protected static $dir = null; /** * @var string Current project logs directory. */ protected static $outputDir = null; /** * @var string Current project data directory. This directory is used to hold * sql dumps and other things needed for current project tests. */ protected static $dataDir = null; /** * @var string Directory with test support files like Actors, Helpers, PageObjects, etc */ protected static $supportDir = null; /** * @var string Directory containing environment configuration files. */ protected static $envsDir = null; /** * @var string Directory containing tests and suites of the current project. */ protected static $testsDir = null; public static $lock = false; protected static $di; /** * @var array Default config */ public static $defaultConfig = [ 'actor_suffix'=> 'Tester', 'namespace' => '', 'include' => [], 'paths' => [], 'suites' => [], 'modules' => [], 'extensions' => [ 'enabled' => [], 'config' => [], 'commands' => [], ], 'reporters' => [ 'xml' => 'Codeception\PHPUnit\Log\JUnit', 'html' => 'Codeception\PHPUnit\ResultPrinter\HTML', 'tap' => 'PHPUnit_Util_Log_TAP', 'json' => 'PHPUnit_Util_Log_JSON', 'report' => 'Codeception\PHPUnit\ResultPrinter\Report', ], 'groups' => [], 'settings' => [ 'colors' => true, 'bootstrap' => false, 'strict_xml' => false, 'lint' => true, 'backup_globals' => true, 'log_incomplete_skipped' => false, 'report_useless_tests' => false, 'disallow_test_output' => false, 'be_strict_about_changes_to_global_state' => false ], 'coverage' => [], 'params' => [], 'gherkin' => [] ]; public static $defaultSuiteSettings = [ 'actor' => null, 'class_name' => null, // Codeception <2.3 compatibility 'modules' => [ 'enabled' => [], 'config' => [], 'depends' => [] ], 'path' => null, 'namespace' => null, 'groups' => [], 'shuffle' => false, 'extensions' => [ // suite extensions 'enabled' => [], 'config' => [], ], 'error_level' => 'E_ALL & ~E_STRICT & ~E_DEPRECATED', ]; protected static $params; /** * Loads global config file which is `codeception.yml` by default. * When config is already loaded - returns it. * * @param null $configFile * @return array * @throws Exception\ConfigurationException */ public static function config($configFile = null) { if (!$configFile && self::$config) { return self::$config; } if (self::$config && self::$lock) { return self::$config; } if ($configFile === null) { $configFile = getcwd() . DIRECTORY_SEPARATOR . 'codeception.yml'; } if (is_dir($configFile)) { $configFile = $configFile . DIRECTORY_SEPARATOR . 'codeception.yml'; } $dir = realpath(dirname($configFile)); self::$dir = $dir; $configDistFile = $dir . DIRECTORY_SEPARATOR . 'codeception.dist.yml'; if (!(file_exists($configDistFile) || file_exists($configFile))) { throw new ConfigurationException("Configuration file could not be found.\nRun `bootstrap` to initialize Codeception.", 404); } // Preload config to retrieve params such that they are applied to codeception config file below $tempConfig = self::$defaultConfig; $distConfigContents = ""; if (file_exists($configDistFile)) { $distConfigContents = file_get_contents($configDistFile); $tempConfig = self::mergeConfigs($tempConfig, self::getConfFromContents($distConfigContents, $configDistFile)); } $configContents = ""; if (file_exists($configFile)) { $configContents = file_get_contents($configFile); $tempConfig = self::mergeConfigs($tempConfig, self::getConfFromContents($configContents, $configFile)); } self::prepareParams($tempConfig); // load config using params $config = self::mergeConfigs(self::$defaultConfig, self::getConfFromContents($distConfigContents, $configDistFile)); $config = self::mergeConfigs($config, self::getConfFromContents($configContents, $configFile)); if ($config == self::$defaultConfig) { throw new ConfigurationException("Configuration file is invalid"); } self::$config = $config; // compatibility with 1.x, 2.0 if (!isset($config['paths']['output']) and isset($config['paths']['log'])) { $config['paths']['output'] = $config['paths']['log']; } if (isset(self::$config['actor'])) { self::$config['actor_suffix'] = self::$config['actor']; // old compatibility } if (!isset($config['paths']['support']) and isset($config['paths']['helpers'])) { $config['paths']['support'] = $config['paths']['helpers']; } if (!isset($config['paths']['output'])) { throw new ConfigurationException('Output path is not defined by key "paths: output"'); } self::$outputDir = $config['paths']['output']; // fill up includes with wildcard expansions $config['include'] = self::expandWildcardedIncludes($config['include']); // config without tests, for inclusion of other configs if (count($config['include'])) { self::$config = $config; if (!isset($config['paths']['tests'])) { return $config; } } if (!isset($config['paths']['tests'])) { throw new ConfigurationException( 'Tests directory is not defined in Codeception config by key "paths: tests:"' ); } if (!isset($config['paths']['data'])) { throw new ConfigurationException('Data path is not defined Codeception config by key "paths: data"'); } if (!isset($config['paths']['support'])) { throw new ConfigurationException('Helpers path is not defined by key "paths: support"'); } self::$dataDir = $config['paths']['data']; self::$supportDir = $config['paths']['support']; self::$testsDir = $config['paths']['tests']; if (isset($config['paths']['envs'])) { self::$envsDir = $config['paths']['envs']; } Autoload::addNamespace(self::$config['namespace'], self::supportDir()); self::loadBootstrap($config['settings']['bootstrap']); self::loadSuites(); return $config; } protected static function loadBootstrap($bootstrap) { if (!$bootstrap) { return; } $bootstrap = self::$dir . DIRECTORY_SEPARATOR . self::$testsDir . DIRECTORY_SEPARATOR . $bootstrap; if (file_exists($bootstrap)) { include_once $bootstrap; } } protected static function loadSuites() { $suites = Finder::create() ->files() ->name('*.{suite,suite.dist}.yml') ->in(self::$dir . DIRECTORY_SEPARATOR . self::$testsDir) ->depth('< 1') ->sortByName(); self::$suites = []; foreach (array_keys(self::$config['suites']) as $suite) { self::$suites[$suite] = $suite; } /** @var SplFileInfo $suite */ foreach ($suites as $suite) { preg_match('~(.*?)(\.suite|\.suite\.dist)\.yml~', $suite->getFilename(), $matches); self::$suites[$matches[1]] = $matches[1]; } } /** * Returns suite configuration. Requires suite name and global config used (Configuration::config) * * @param string $suite * @param array $config * @return array * @throws \Exception */ public static function suiteSettings($suite, $config) { // cut namespace name from suite name if ($suite != $config['namespace'] && substr($suite, 0, strlen($config['namespace'])) == $config['namespace']) { $suite = substr($suite, strlen($config['namespace'])); } if (!in_array($suite, self::$suites)) { throw new ConfigurationException("Suite $suite was not loaded"); } // load global config $globalConf = $config['settings']; foreach (['modules', 'coverage', 'namespace', 'groups', 'env', 'gherkin', 'extensions'] as $key) { if (isset($config[$key])) { $globalConf[$key] = $config[$key]; } } $settings = self::mergeConfigs(self::$defaultSuiteSettings, $globalConf); // load suite config $settings = self::loadSuiteConfig($suite, $config['paths']['tests'], $settings); // load from environment configs if (isset($config['paths']['envs'])) { $envConf = self::loadEnvConfigs(self::$dir . DIRECTORY_SEPARATOR . $config['paths']['envs']); $settings = self::mergeConfigs($settings, $envConf); } if (!$settings['actor']) { // Codeception 2.2 compatibility $settings['actor'] = $settings['class_name']; } if (!$settings['path']) { // take a suite path from its name $settings['path'] = $suite; } $settings['path'] = self::$dir . DIRECTORY_SEPARATOR . $config['paths']['tests'] . DIRECTORY_SEPARATOR . $settings['path'] . DIRECTORY_SEPARATOR; return $settings; } /** * Loads environments configuration from set directory * * @param string $path path to the directory * @return array */ protected static function loadEnvConfigs($path) { if (isset(self::$envConfig[$path])) { return self::$envConfig[$path]; } if (!is_dir($path)) { self::$envConfig[$path] = []; return self::$envConfig[$path]; } $envFiles = Finder::create() ->files() ->name('*.yml') ->in($path) ->depth('< 2'); $envConfig = []; /** @var SplFileInfo $envFile */ foreach ($envFiles as $envFile) { $env = str_replace(['.dist.yml', '.yml'], '', $envFile->getFilename()); $envConfig[$env] = []; $envPath = $path; if ($envFile->getRelativePath()) { $envPath .= DIRECTORY_SEPARATOR . $envFile->getRelativePath(); } foreach (['.dist.yml', '.yml'] as $suffix) { $envConf = self::getConfFromFile($envPath . DIRECTORY_SEPARATOR . $env . $suffix, null); if ($envConf === null) { continue; } $envConfig[$env] = self::mergeConfigs($envConfig[$env], $envConf); } } self::$envConfig[$path] = ['env' => $envConfig]; return self::$envConfig[$path]; } /** * Loads configuration from Yaml data * * @param string $contents Yaml config file contents * @param string $filename which is supposed to be loaded * @return array * @throws ConfigurationException */ protected static function getConfFromContents($contents, $filename = '(.yml)') { if (self::$params) { $template = new Template($contents, '%', '%'); $template->setVars(self::$params); $contents = $template->produce(); } try { return Yaml::parse($contents); } catch (ParseException $exception) { throw new ConfigurationException( sprintf( "Error loading Yaml config from `%s`\n \n%s\nRead more about Yaml format https://goo.gl/9UPuEC", $filename, $exception->getMessage() ) ); } } /** * Loads configuration from Yaml file or returns given value if the file doesn't exist * * @param string $filename filename * @param mixed $nonExistentValue value used if filename is not found * @return array */ protected static function getConfFromFile($filename, $nonExistentValue = []) { if (file_exists($filename)) { $yaml = file_get_contents($filename); return self::getConfFromContents($yaml, $filename); } return $nonExistentValue; } /** * Returns all possible suite configurations according environment rules. * Suite configurations will contain `current_environment` key which specifies what environment used. * * @param $suite * @return array */ public static function suiteEnvironments($suite) { $settings = self::suiteSettings($suite, self::config()); if (!isset($settings['env']) || !is_array($settings['env'])) { return []; } $environments = []; foreach ($settings['env'] as $env => $envConfig) { $environments[$env] = $envConfig ? self::mergeConfigs($settings, $envConfig) : $settings; $environments[$env]['current_environment'] = $env; } return $environments; } public static function suites() { return self::$suites; } /** * Return list of enabled modules according suite config. * * @param array $settings suite settings * @return array */ public static function modules($settings) { return array_filter( array_map( function ($m) { return is_array($m) ? key($m) : $m; }, $settings['modules']['enabled'], array_keys($settings['modules']['enabled']) ), function ($m) use ($settings) { if (!isset($settings['modules']['disabled'])) { return true; } return !in_array($m, $settings['modules']['disabled']); } ); } public static function isExtensionEnabled($extensionName) { return isset(self::$config['extensions']) && isset(self::$config['extensions']['enabled']) && in_array($extensionName, self::$config['extensions']['enabled']); } /** * Returns current path to `_data` dir. * Use it to store database fixtures, sql dumps, or other files required by your tests. * * @return string */ public static function dataDir() { return self::$dir . DIRECTORY_SEPARATOR . self::$dataDir . DIRECTORY_SEPARATOR; } /** * Return current path to `_helpers` dir. * Helpers are custom modules. * * @return string */ public static function supportDir() { return self::$dir . DIRECTORY_SEPARATOR . self::$supportDir . DIRECTORY_SEPARATOR; } /** * Returns actual path to current `_output` dir. * Use it in Helpers or Groups to save result or temporary files. * * @return string * @throws Exception\ConfigurationException */ public static function outputDir() { if (!self::$outputDir) { throw new ConfigurationException("Path for output not specified. Please, set output path in global config"); } $dir = self::$outputDir . DIRECTORY_SEPARATOR; if (strcmp(self::$outputDir[0], "/") !== 0) { $dir = self::$dir . DIRECTORY_SEPARATOR . $dir; } if (!is_writable($dir)) { @mkdir($dir); @chmod($dir, 0777); } if (!is_writable($dir)) { throw new ConfigurationException( "Path for output is not writable. Please, set appropriate access mode for output path." ); } return $dir; } /** * Compatibility alias to `Configuration::logDir()` * @return string */ public static function logDir() { return self::outputDir(); } /** * Returns path to the root of your project. * Basically returns path to current `codeception.yml` loaded. * Use this method instead of `__DIR__`, `getcwd()` or anything else. * @return string */ public static function projectDir() { return self::$dir . DIRECTORY_SEPARATOR; } /** * Returns path to tests directory * * @return string */ public static function testsDir() { return self::$dir . DIRECTORY_SEPARATOR . self::$testsDir . DIRECTORY_SEPARATOR; } /** * Return current path to `_envs` dir. * Use it to store environment specific configuration. * * @return string */ public static function envsDir() { if (!self::$envsDir) { return null; } return self::$dir . DIRECTORY_SEPARATOR . self::$envsDir . DIRECTORY_SEPARATOR; } /** * Is this a meta-configuration file that just points to other `codeception.yml`? * If so, it may have no tests by itself. * * @return bool */ public static function isEmpty() { return !(bool)self::$testsDir; } /** * Adds parameters to config * * @param array $config * @return array */ public static function append(array $config = []) { self::$config = self::mergeConfigs(self::$config, $config); if (isset(self::$config['paths']['output'])) { self::$outputDir = self::$config['paths']['output']; } if (isset(self::$config['paths']['data'])) { self::$dataDir = self::$config['paths']['data']; } if (isset(self::$config['paths']['support'])) { self::$supportDir = self::$config['paths']['support']; } if (isset(self::$config['paths']['tests'])) { self::$testsDir = self::$config['paths']['tests']; } return self::$config; } public static function mergeConfigs($a1, $a2) { if (!is_array($a1)) { return $a2; } if (!is_array($a2)) { return $a1; } $res = []; // for sequential arrays if (isset($a1[0]) && isset($a2[0])) { return array_merge_recursive($a2, $a1); } // for associative arrays foreach ($a2 as $k2 => $v2) { if (!isset($a1[$k2])) { // if no such key $res[$k2] = $v2; unset($a1[$k2]); continue; } $res[$k2] = self::mergeConfigs($a1[$k2], $v2); unset($a1[$k2]); } foreach ($a1 as $k1 => $v1) { // only single elements here left $res[$k1] = $v1; } return $res; } /** * Loads config from *.dist.suite.yml and *.suite.yml * * @param $suite * @param $path * @param $settings * @return array */ protected static function loadSuiteConfig($suite, $path, $settings) { if (isset(self::$config['suites'][$suite])) { // bundled config return self::mergeConfigs($settings, self::$config['suites'][$suite]); } $suiteDistConf = self::getConfFromFile( self::$dir . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$suite.suite.dist.yml" ); $suiteConf = self::getConfFromFile( self::$dir . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . "$suite.suite.yml" ); $settings = self::mergeConfigs($settings, $suiteDistConf); $settings = self::mergeConfigs($settings, $suiteConf); return $settings; } /** * Replaces wildcarded items in include array with real paths. * * @param $includes * @return array */ protected static function expandWildcardedIncludes(array $includes) { if (empty($includes)) { return $includes; } $expandedIncludes = []; foreach ($includes as $include) { $expandedIncludes = array_merge($expandedIncludes, self::expandWildcardsFor($include)); } return $expandedIncludes; } /** * Finds config files in given wildcarded include path. * Returns the expanded paths or the original if not a wildcard. * * @param $include * @return array * @throws ConfigurationException */ protected static function expandWildcardsFor($include) { if (1 !== preg_match('/[\?\.\*]/', $include)) { return [$include,]; } try { $configFiles = Finder::create()->files() ->name('/codeception(\.dist\.yml|\.yml)/') ->in(self::$dir . DIRECTORY_SEPARATOR . $include); } catch (\InvalidArgumentException $e) { throw new ConfigurationException( "Configuration file(s) could not be found in \"$include\"." ); } $paths = []; foreach ($configFiles as $file) { $paths[] = codecept_relative_path($file->getPath()); } return $paths; } private static function prepareParams($settings) { self::$params = []; $paramsLoader = new ParamsLoader(); foreach ($settings['params'] as $paramStorage) { static::$params = array_merge(self::$params, $paramsLoader->load($paramStorage)); } } } <?php namespace Codeception\Exception; class InjectionException extends \Exception { } <?php namespace Codeception\Exception; class RemoteException extends \Exception { public function __construct($message) { parent::__construct($message); $this->message = "Remote Application Error:\n" . $this->message; } } <?php namespace Codeception\Exception; class Skip extends \PHPUnit_Framework_SkippedTestError { } <?php namespace Codeception\Exception; class MalformedLocatorException extends TestRuntimeException { public function __construct($locator, $type = "CSS or XPath") { parent::__construct(ucfirst($type) . " locator is malformed: $locator"); } } <?php namespace Codeception\Exception; class TestRuntimeException extends \RuntimeException { } <?php namespace Codeception\Exception; class ConfigurationException extends \Exception { } <?php namespace Codeception\Exception; class ModuleConflictException extends \Exception { public function __construct($module, $conflicted, $additional = '') { if (is_object($module)) { $module = get_class($module); } if (is_object($conflicted)) { $conflicted = get_class($conflicted); } $module = ltrim(str_replace('Codeception\Module\\', '', $module), '\\'); $conflicted = ltrim(str_replace('Codeception\Module\\', '', $conflicted), '\\'); $this->message = "$module module conflicts with $conflicted\n\n--\n" . "This usually happens when you enable two modules with the same actions but with different backends.\n" . "For instance, you can't run PhpBrowser, WebDriver, Laravel5 modules in one suite,\n" . "as they implement similar methods but use different drivers to execute them.\n" . "You can load a part of module (like: ORM) to avoid conflict.\n" . $additional; } } <?php namespace Codeception\Exception; class ModuleConfigException extends \Exception { public function __construct($module, $message, \Exception $previous = null) { if (is_object($module)) { $module = get_class($module); } $module = str_replace('Codeception\Module\\', '', ltrim($module, '\\')); parent::__construct($message, 0, $previous); $this->message = $module . " module is not configured!\n \n" . $this->message; } } <?php namespace Codeception\Exception; use Codeception\Util\Locator; class ElementNotFound extends \PHPUnit_Framework_AssertionFailedError { public function __construct($selector, $message = null) { $selector = Locator::humanReadableString($selector); parent::__construct($message . " element with $selector was not found."); } } <?php namespace Codeception\Exception; class ContentNotFound extends \PHPUnit_Framework_AssertionFailedError { } <?php namespace Codeception\Exception; class ConditionalAssertionFailed extends \PHPUnit_Framework_AssertionFailedError { } <?php namespace Codeception\Exception; class ParseException extends \Exception { } <?php namespace Codeception\Exception; class TestParseException extends \Exception { public function __construct($fileName, $errors = null) { $this->message = "Couldn't parse test '$fileName'"; if ($errors) { $this->message .= "\n$errors"; } } } <?php namespace Codeception\Exception; class Fail extends \PHPUnit_Framework_AssertionFailedError { } <?php namespace Codeception\Exception; class ModuleException extends \Exception { protected $module; public function __construct($module, $message) { if (is_object($module)) { $module = get_class($module); } $module = ltrim(str_replace('Codeception\Module\\', '', $module), '\\'); $this->module = $module; parent::__construct($message); $this->message = "$module: {$this->message}"; } } <?php namespace Codeception\Exception; class ExtensionException extends \Exception { public function __construct($extension, $message, \Exception $previous = null) { parent::__construct($message, $previous); if (is_object($extension)) { $extension = get_class($extension); } $this->message = $extension . "\n\n" . $this->message; } } <?php namespace Codeception\Exception; class ConnectionException extends \RuntimeException { } <?php namespace Codeception\Exception; class Incomplete extends \PHPUnit_Framework_IncompleteTestError { } <?php namespace Codeception\Exception; class ModuleRequireException extends \Exception { public function __construct($module, $message) { if (is_object($module)) { $module = get_class($module); } $module = str_replace('Codeception\\Module\\', '', ltrim($module, '\\')); parent::__construct($message); $this->message = "[$module] module requirements not met --\n \n" . $this->message; } } <?php namespace Codeception\Exception; class ExternalUrlException extends \Exception { } <?php namespace Codeception; interface CustomCommandInterface { /** * returns the name of the command * * @return string */ public static function getCommandName(); } <?php namespace Codeception; use Codeception\Event\StepEvent; use Codeception\Exception\ConditionalAssertionFailed; use Codeception\Test\Metadata; class Scenario { /** * @var TestInterface */ protected $test; /** * @var Metadata */ protected $metadata; /** * @var array */ protected $steps = []; /** * @var string */ protected $feature; protected $metaStep; /** * Constructor * * @param TestInterface $test */ public function __construct(TestInterface $test) { $this->metadata = $test->getMetadata(); $this->test = $test; } public function setFeature($feature) { $this->metadata->setFeature($feature); } public function getFeature() { return $this->metadata->getFeature(); } public function getGroups() { return $this->metadata->getGroups(); } public function current($key) { return $this->metadata->getCurrent($key); } public function runStep(Step $step) { $step->saveTrace(); if ($this->metaStep instanceof Step\Meta) { $step->setMetaStep($this->metaStep); } $this->steps[] = $step; $result = null; $this->metadata->getService('dispatcher')->dispatch(Events::STEP_BEFORE, new StepEvent($this->test, $step)); try { $result = $step->run($this->metadata->getService('modules')); } catch (ConditionalAssertionFailed $f) { $result = $this->test->getTestResultObject(); if (is_null($result)) { $this->metadata->getService('dispatcher')->dispatch(Events::STEP_AFTER, new StepEvent($this->test, $step)); throw $f; } else { $result->addFailure(clone($this->test), $f, $result->time()); } } catch (\Exception $e) { $this->metadata->getService('dispatcher')->dispatch(Events::STEP_AFTER, new StepEvent($this->test, $step)); throw $e; } $this->metadata->getService('dispatcher')->dispatch(Events::STEP_AFTER, new StepEvent($this->test, $step)); $step->executed = true; return $result; } public function addStep(Step $step) { $this->steps[] = $step; } /** * Returns the steps of this scenario. * * @return array */ public function getSteps() { return $this->steps; } public function getHtml() { $text = ''; foreach ($this->getSteps() as $step) { /** @var Step $step */ if ($step->getName() !== 'Comment') { $text .= $step->getHtml() . '<br/>'; } else { $text .= trim($step->getHumanizedArguments(), '"') . '<br/>'; } } $text = str_replace(['"\'', '\'"'], ["'", "'"], $text); $text = "<h3>" . mb_strtoupper('I want to ' . $this->getFeature(), 'utf-8') . "</h3>" . $text; return $text; } public function getText() { $text = ''; foreach ($this->getSteps() as $step) { $text .= $step->getPrefix() . "$step \r\n"; } $text = trim(str_replace(['"\'', '\'"'], ["'", "'"], $text)); $text = mb_strtoupper('I want to ' . $this->getFeature(), 'utf-8') . "\r\n\r\n" . $text . "\r\n\r\n"; return $text; } public function comment($comment) { $this->runStep(new \Codeception\Step\Comment($comment, [])); } public function skip($message = '') { throw new \PHPUnit_Framework_SkippedTestError($message); } public function incomplete($message = '') { throw new \PHPUnit_Framework_IncompleteTestError($message); } public function __call($method, $args) { // all methods were deprecated and removed from here trigger_error("Codeception: \$scenario->$method() has been deprecated and removed. Use annotations to pass scenario params", E_USER_DEPRECATED); } /** * @param Step\Meta $metaStep */ public function setMetaStep($metaStep) { $this->metaStep = $metaStep; } /** * @return Step\Meta */ public function getMetaStep() { return $this->metaStep; } } <?php namespace Codeception\Event; use Codeception\Suite; use Symfony\Component\EventDispatcher\Event; class SuiteEvent extends Event { /** * @var \PHPUnit_Framework_TestSuite */ protected $suite; /** * @var \PHPUnit_Framework_TestResult */ protected $result; /** * @var array */ protected $settings; public function __construct( \PHPUnit_Framework_TestSuite $suite, \PHPUnit_Framework_TestResult $result = null, $settings = [] ) { $this->suite = $suite; $this->result = $result; $this->settings = $settings; } /** * @return Suite */ public function getSuite() { return $this->suite; } /** * @return \PHPUnit_Framework_TestResult */ public function getResult() { return $this->result; } public function getSettings() { return $this->settings; } } <?php namespace Codeception\Event; use Symfony\Component\EventDispatcher\Event; class PrintResultEvent extends Event { /** * @var \PHPUnit_Framework_TestResult */ protected $result; /** * @var \PHPUnit_Util_Printer */ protected $printer; public function __construct(\PHPUnit_Framework_TestResult $result, \PHPUnit_Util_Printer $printer) { $this->result = $result; $this->printer = $printer; } /** * @return \PHPUnit_Util_Printer */ public function getPrinter() { return $this->printer; } /** * @return \PHPUnit_Framework_TestResult */ public function getResult() { return $this->result; } } <?php namespace Codeception\Event; use Symfony\Component\EventDispatcher\Event; class TestEvent extends Event { /** * @var \PHPUnit_Framework_Test */ protected $test; /** * @var float Time taken */ protected $time; public function __construct(\PHPUnit_Framework_Test $test, $time = 0) { $this->test = $test; $this->time = $time; } /** * @return float */ public function getTime() { return $this->time; } /** * @return \Codeception\TestInterface */ public function getTest() { return $this->test; } } <?php namespace Codeception\Event; use Codeception\Step; use Codeception\TestInterface; use Symfony\Component\EventDispatcher\Event; class StepEvent extends Event { /** * @var Step */ protected $step; /** * @var TestInterface */ protected $test; public function __construct(TestInterface $test, Step $step) { $this->test = $test; $this->step = $step; } public function getStep() { return $this->step; } /** * @return TestInterface */ public function getTest() { return $this->test; } } <?php namespace Codeception\Event; class FailEvent extends TestEvent { /** * @var \Exception */ protected $fail; /** * @var int */ protected $count; public function __construct(\PHPUnit_Framework_Test $test, $time, \Exception $e, $count = 0) { parent::__construct($test, $time); $this->fail = $e; $this->count = $count; } public function getCount() { return $this->count; } public function getFail() { return $this->fail; } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Event\SuiteEvent; use Codeception\Event\TestEvent; use Codeception\Events; use Codeception\Subscriber\Bootstrap as BootstrapLoader; use Codeception\Subscriber\Console as ConsolePrinter; use Codeception\SuiteManager; use Codeception\Test\Interfaces\ScenarioDriven; use Codeception\Test\Test; use Codeception\Util\Maybe; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventDispatcher; /** * Shows step by step execution process for scenario driven tests without actually running them. * * * `codecept dry-run acceptance` * * `codecept dry-run acceptance MyCest` * * `codecept dry-run acceptance checkout.feature` * * `codecept dry-run tests/acceptance/MyCest.php` * */ class DryRun extends Command { use Shared\Config; use Shared\Style; protected function configure() { $this->setDefinition( [ new InputArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files'), new InputArgument('test', InputArgument::OPTIONAL, 'tests to be loaded'), ] ); parent::configure(); } public function getDescription() { return 'Prints step-by-step scenario-driven test or a feature'; } public function execute(InputInterface $input, OutputInterface $output) { $this->addStyles($output); $suite = $input->getArgument('suite'); $test = $input->getArgument('test'); $config = $this->getGlobalConfig(); if (! Configuration::isEmpty() && ! $test && strpos($suite, $config['paths']['tests']) === 0) { list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']); } $settings = $this->getSuiteConfig($suite); $dispatcher = new EventDispatcher(); $dispatcher->addSubscriber(new ConsolePrinter([ 'colors' => !$input->getOption('no-ansi'), 'steps' => true, 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, ])); $dispatcher->addSubscriber(new BootstrapLoader()); $suiteManager = new SuiteManager($dispatcher, $suite, $settings); $moduleContainer = $suiteManager->getModuleContainer(); foreach (Configuration::modules($settings) as $module) { $moduleContainer->mock($module, new Maybe()); } $suiteManager->loadTests($test); $tests = $suiteManager->getSuite()->tests(); $dispatcher->dispatch(Events::SUITE_INIT, new SuiteEvent($suiteManager->getSuite(), null, $settings)); $dispatcher->dispatch(Events::SUITE_BEFORE, new SuiteEvent($suiteManager->getSuite(), null, $settings)); foreach ($tests as $test) { if ($test instanceof \PHPUnit_Framework_TestSuite_DataProvider) { foreach ($test as $t) { if ($t instanceof Test) { $this->dryRunTest($output, $dispatcher, $t); } } } if ($test instanceof Test and $test instanceof ScenarioDriven) { $this->dryRunTest($output, $dispatcher, $test); } } $dispatcher->dispatch(Events::SUITE_AFTER, new SuiteEvent($suiteManager->getSuite())); } protected function matchTestFromFilename($filename, $tests_path) { $filename = str_replace(['//', '\/', '\\'], '/', $filename); $res = preg_match("~^$tests_path/(.*?)/(.*)$~", $filename, $matches); if (!$res) { throw new \InvalidArgumentException("Test file can't be matched"); } return $matches; } /** * @param OutputInterface $output * @param $dispatcher * @param $test */ protected function dryRunTest(OutputInterface $output, EventDispatcher $dispatcher, Test $test) { $dispatcher->dispatch(Events::TEST_START, new TestEvent($test)); $dispatcher->dispatch(Events::TEST_BEFORE, new TestEvent($test)); try { $test->test(); } catch (\Exception $e) { } $dispatcher->dispatch(Events::TEST_AFTER, new TestEvent($test)); $dispatcher->dispatch(Events::TEST_END, new TestEvent($test)); if ($test->getMetadata()->isBlocked()) { $output->writeln(''); if ($skip = $test->getMetadata()->getSkip()) { $output->writeln("<warning> SKIPPED </warning>" . $skip); } if ($incomplete = $test->getMetadata()->getIncomplete()) { $output->writeln("<warning> INCOMPLETE </warning>" . $incomplete); } } $output->writeln(''); } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Lib\Generator\Helper; use Codeception\Util\Template; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Codeception\Lib\Generator\Actor as ActorGenerator; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Yaml\Yaml; /** * Create new test suite. Requires suite name and actor name * * * `` * * `codecept g:suite api` -> api + ApiTester * * `codecept g:suite integration Code` -> integration + CodeTester * * `codecept g:suite frontend Front` -> frontend + FrontTester * */ class GenerateSuite extends Command { use Shared\FileSystem; use Shared\Config; use Shared\Style; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'suite to be generated'), new InputArgument('actor', InputArgument::OPTIONAL, 'name of new actor class'), ]); } public function getDescription() { return 'Generates new test suite'; } public function execute(InputInterface $input, OutputInterface $output) { $this->addStyles($output); $suite = $input->getArgument('suite'); $actor = $input->getArgument('actor'); if ($this->containsInvalidCharacters($suite)) { $output->writeln("<error>Suite name '$suite' contains invalid characters. ([A-Za-z0-9_]).</error>"); return; } $config = $this->getGlobalConfig(); if (!$actor) { $actor = ucfirst($suite) . $config['actor_suffix']; } $dir = Configuration::testsDir(); if (file_exists($dir . $suite . '.suite.yml')) { throw new \Exception("Suite configuration file '$suite.suite.yml' already exists."); } $this->createDirectoryFor($dir . $suite); if ($config['settings']['bootstrap']) { // generate bootstrap file $this->createFile( $dir . $suite . DIRECTORY_SEPARATOR . $config['settings']['bootstrap'], "<?php\n", true ); } $helperName = ucfirst($suite); $file = $this->createDirectoryFor( Configuration::supportDir() . "Helper", "$helperName.php" ) . "$helperName.php"; $gen = new Helper($helperName, $config['namespace']); // generate helper $this->createFile( $file, $gen->produce() ); $output->writeln("Helper <info>" . $gen->getHelperName() . "</info> was created in $file"); $yamlSuiteConfigTemplate = <<<EOF actor: {{actor}} modules: enabled: - {{helper}} EOF; $this->createFile( $dir . $suite . '.suite.yml', $yamlSuiteConfig = (new Template($yamlSuiteConfigTemplate)) ->place('actor', $actor) ->place('helper', $gen->getHelperName()) ->produce() ); Configuration::append(Yaml::parse($yamlSuiteConfig)); $actorGenerator = new ActorGenerator(Configuration::config()); $content = $actorGenerator->produce(); $file = $this->createDirectoryFor( Configuration::supportDir(), $actor ) . $this->getShortClassName($actor); $file .= '.php'; $this->createFile($file, $content); $output->writeln("Actor <info>" . $actor . "</info> was created in $file"); $output->writeln("Suite config <info>$suite.suite.yml</info> was created."); $output->writeln(' '); $output->writeln("Next steps:"); $output->writeln("1. Edit <bold>$suite.suite.yml</bold> to enable modules for this suite"); $output->writeln("2. Create first test with <bold>generate:cest testName</bold> ( or test|cept) command"); $output->writeln("3. Run tests of this suite with <bold>codecept run $suite</bold> command"); $output->writeln("<info>Suite $suite generated</info>"); } private function containsInvalidCharacters($suite) { return preg_match('#[^A-Za-z0-9_]#', $suite) ? true : false; } } <?php namespace Codeception\Command\Shared; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Output\OutputInterface; trait Style { public function addStyles(OutputInterface $output) { $output->getFormatter()->setStyle('notice', new OutputFormatterStyle('white', 'green', ['bold'])); $output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold'])); $output->getFormatter()->setStyle('warning', new OutputFormatterStyle(null, 'yellow', ['bold'])); $output->getFormatter()->setStyle('debug', new OutputFormatterStyle('cyan')); } } <?php namespace Codeception\Command\Shared; use Codeception\Util\Shared\Namespaces; trait FileSystem { use Namespaces; protected function createDirectoryFor($basePath, $className = '') { $basePath = rtrim($basePath, DIRECTORY_SEPARATOR); if ($className) { $className = str_replace(['/', '\\'], [DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $className); $path = $basePath . DIRECTORY_SEPARATOR . $className; $basePath = pathinfo($path, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR; } if (!file_exists($basePath)) { // Second argument should be mode. Well, umask() doesn't seem to return any if not set. Config may fix this. mkdir($basePath, 0775, true); // Third parameter commands to create directories recursively } return $basePath; } protected function completeSuffix($filename, $suffix) { if (strpos(strrev($filename), strrev($suffix)) === 0) { $filename .= '.php'; } if (strpos(strrev($filename), strrev($suffix . '.php')) !== 0) { $filename .= $suffix . '.php'; } if (strpos(strrev($filename), strrev('.php')) !== 0) { $filename .= '.php'; } return $filename; } protected function removeSuffix($classname, $suffix) { $classname = preg_replace('~\.php$~', '', $classname); return preg_replace("~$suffix$~", '', $classname); } protected function createFile($filename, $contents, $force = false, $flags = null) { if (file_exists($filename) && !$force) { return false; } file_put_contents($filename, $contents, $flags); return true; } } <?php namespace Codeception\Command\Shared; use Codeception\Configuration; use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; trait Config { protected function getSuiteConfig($suite) { return Configuration::suiteSettings($suite, $this->getGlobalConfig()); } protected function getGlobalConfig($conf = null) { return Configuration::config($conf); } protected function getSuites($conf = null) { return Configuration::suites(); } protected function overrideConfig($configOptions) { $updatedConfig = []; foreach ($configOptions as $option) { $keys = explode(': ', $option); if (count($keys) < 2) { throw new \InvalidArgumentException('--config-option should have config passed as "key:value"'); } $value = array_pop($keys); $yaml = ''; for ($ind = 0; count($keys); $ind += 2) { $yaml .= "\n" . str_repeat(' ', $ind) . array_shift($keys) . ': '; } $yaml .= $value; try { $config = Yaml::parse($yaml); } catch (ParseException $e) { throw new \Codeception\Exception\ParseException("Overridden config can't be parsed: \n$yaml\n" . $e->getParsedLine()); } $updatedConfig = array_merge_recursive($updatedConfig, $config); } return Configuration::append($updatedConfig); } protected function enableExtensions($extensions) { $config = ['extensions' => ['enabled' => []]]; foreach ($extensions as $name) { if (!class_exists($name)) { $className = 'Codeception\\Extension\\' . ucfirst($name); if (!class_exists($className)) { throw new InvalidOptionException("Extension $name can't be loaded (tried by $name and $className)"); } $config['extensions']['enabled'][] = $className; continue; } $config['extensions']['enabled'][] = $name; } return Configuration::append($config); } } <?php namespace Codeception\Command; use Codeception\Lib\Generator\GherkinSnippets as SnippetsGenerator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Generates code snippets for matched feature files in a suite. * Code snuppets are expected to be implemtned in Actor or PageOjects * * Usage: * * * `codecept gherkin:snippets acceptance` - snippets from all feature of acceptance tests * * `codecept gherkin:snippets acceptance/feature/users` - snippets from `feature/users` dir of acceptance tests * * `codecept gherkin:snippets acceptance user_account.feature` - snippets from a single feature file * * `codecept gherkin:snippets acceptance/feature/users/user_accout.feature` - snippets from feature file in a dir */ class GherkinSnippets extends Command { use Shared\Config; use Shared\Style; protected function configure() { $this->setDefinition( [ new InputArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files'), new InputArgument('test', InputArgument::OPTIONAL, 'test to be scanned'), new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'), ] ); parent::configure(); } public function getDescription() { return 'Fetches empty steps from feature files of suite and prints code snippets for them'; } public function execute(InputInterface $input, OutputInterface $output) { $this->addStyles($output); $suite = $input->getArgument('suite'); $test = $input->getArgument('test'); $config = $this->getSuiteConfig($suite); $generator = new SnippetsGenerator($config, $test); $snippets = $generator->getSnippets(); $features = $generator->getFeatures(); if (empty($snippets)) { $output->writeln("<notice> All Gherkin steps are defined. Exiting... </notice>"); return; } $output->writeln("<comment> Snippets found in: </comment>"); foreach ($features as $feature) { $output->writeln("<info> - {$feature} </info>"); } $output->writeln("<comment> Generated Snippets: </comment>"); $output->writeln("<info> ----------------------------------------- </info>"); foreach ($snippets as $snippet) { $output->writeln($snippet); } $output->writeln("<info> ----------------------------------------- </info>"); $output->writeln(sprintf(' <bold>%d</bold> snippets proposed', count($snippets))); $output->writeln("<notice> Copy generated snippets to {$config['actor']} or a specific Gherkin context </notice>"); } } <?php namespace Codeception\Command; use Codeception\InitTemplate; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Init extends Command { protected function configure() { $this->setDefinition( [ new InputArgument('template', InputArgument::REQUIRED, 'Init template for the setup'), new InputOption('path', null, InputOption::VALUE_REQUIRED, 'Change current directory', null), new InputOption('namespace', null, InputOption::VALUE_OPTIONAL, 'Namespace to add for actor classes and helpers\'', null), ] ); } public function getDescription() { return "Creates test suites by a template"; } public function execute(InputInterface $input, OutputInterface $output) { $template = $input->getArgument('template'); if (class_exists($template)) { $className = $template; } else { $className = 'Codeception\Template\\' . ucfirst($template); if (!class_exists($className)) { throw new \Exception("Template from a $className can't be loaded; Init can't be executed"); } } $initProcess = new $className($input, $output); if (!$initProcess instanceof InitTemplate) { throw new \Exception("$className is not a valid template"); } if ($input->getOption('path')) { $initProcess->initDir($input->getOption('path')); } $initProcess->setup(); } } <?php namespace Codeception\Command; use Codeception\Codecept; use Codeception\Configuration; use Codeception\Event\SuiteEvent; use Codeception\Event\TestEvent; use Codeception\Events; use Codeception\Exception\ConfigurationException; use Codeception\Lib\Console\Output; use Codeception\Scenario; use Codeception\SuiteManager; use Codeception\Test\Cept; use Codeception\Util\Debug; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; /** * Try to execute test commands in run-time. You may try commands before writing the test. * * * `codecept console acceptance` - starts acceptance suite environment. If you use WebDriver you can manipulate browser with Codeception commands. */ class Console extends Command { protected $test; protected $codecept; protected $suite; protected $output; protected $actions = []; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'suite to be executed'), new InputOption('colors', '', InputOption::VALUE_NONE, 'Use colors in output'), ]); parent::configure(); } public function getDescription() { return 'Launches interactive test console'; } public function execute(InputInterface $input, OutputInterface $output) { $suiteName = $input->getArgument('suite'); $this->output = $output; $config = Configuration::config(); $settings = Configuration::suiteSettings($suiteName, $config); $options = $input->getOptions(); $options['debug'] = true; $options['silent'] = true; $options['interactive'] = false; $options['colors'] = true; Debug::setOutput(new Output($options)); $this->codecept = new Codecept($options); $dispatcher = $this->codecept->getDispatcher(); $suiteManager = new SuiteManager($dispatcher, $suiteName, $settings); $suiteManager->initialize(); $this->suite = $suiteManager->getSuite(); $moduleContainer = $suiteManager->getModuleContainer(); $this->actions = array_keys($moduleContainer->getActions()); $this->test = new Cept(null, null); $this->test->getMetadata()->setServices([ 'dispatcher' => $dispatcher, 'modules' => $moduleContainer ]); $scenario = new Scenario($this->test); if (!$settings['actor']) { throw new ConfigurationException("Interactive shell can't be started without an actor"); } if (isset($config["namespace"])) { $settings['actor'] = $config["namespace"] .'\\' . $settings['actor']; } $actor = $settings['actor']; $I = new $actor($scenario); $this->listenToSignals(); $output->writeln("<info>Interactive console started for suite $suiteName</info>"); $output->writeln("<info>Try Codeception commands without writing a test</info>"); $output->writeln("<info>type 'exit' to leave console</info>"); $output->writeln("<info>type 'actions' to see all available actions for this suite</info>"); $suiteEvent = new SuiteEvent($this->suite, $this->codecept->getResult(), $settings); $dispatcher->dispatch(Events::SUITE_BEFORE, $suiteEvent); $dispatcher->dispatch(Events::TEST_PARSED, new TestEvent($this->test)); $dispatcher->dispatch(Events::TEST_BEFORE, new TestEvent($this->test)); $output->writeln("\n\n<comment>\$I</comment> = new {$settings['actor']}(\$scenario);"); $this->executeCommands($input, $output, $I, $settings['bootstrap']); $dispatcher->dispatch(Events::TEST_AFTER, new TestEvent($this->test)); $dispatcher->dispatch(Events::SUITE_AFTER, new SuiteEvent($this->suite)); $output->writeln("<info>Bye-bye!</info>"); } protected function executeCommands(InputInterface $input, OutputInterface $output, $I, $bootstrap) { $dialog = new QuestionHelper(); if (file_exists($bootstrap)) { require $bootstrap; } do { $question = new Question("<comment>\$I-></comment>"); $question->setAutocompleterValues($this->actions); $command = $dialog->ask($input, $output, $question); if ($command == 'actions') { $output->writeln("<info>" . implode(' ', $this->actions)); continue; } if ($command == 'exit') { return; } if ($command == '') { continue; } try { $value = eval("return \$I->$command;"); if ($value && !is_object($value)) { codecept_debug($value); } } catch (\PHPUnit_Framework_AssertionFailedError $fail) { $output->writeln("<error>fail</error> " . $fail->getMessage()); } catch (\Exception $e) { $output->writeln("<error>error</error> " . $e->getMessage()); } } while (true); } protected function listenToSignals() { if (function_exists('pcntl_signal')) { declare (ticks = 1); pcntl_signal(SIGINT, SIG_IGN); pcntl_signal(SIGTERM, SIG_IGN); } } } <?php namespace Codeception\Command; use Codeception\Configuration; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Validates and prints Codeception config. * Use it do debug Yaml configs * * Check config: * * * `codecept config`: check global config * * `codecept config unit`: check suite config * * Load config: * * * `codecept config:validate -c path/to/another/config`: from another dir * * `codecept config:validate -c another_config.yml`: from another config file * * Check overriding config values (like in `run` command) * * * `codecept config:validate -o "settings: shuffle: true"`: enable shuffle * * `codecept config:validate -o "settings: lint: false"`: disable linting * * `codecept config:validate -o "reporters: report: \Custom\Reporter" --report`: use custom reporter * */ class ConfigValidate extends Command { use Shared\Config; use Shared\Style; protected function configure() { $this->setDefinition( [ new InputArgument('suite', InputArgument::OPTIONAL, 'to show suite configuration'), new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'), new InputOption('override', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Override config values'), ] ); parent::configure(); } public function getDescription() { return 'Validates and prints config to screen'; } public function execute(InputInterface $input, OutputInterface $output) { $this->addStyles($output); if ($suite = $input->getArgument('suite')) { $output->write("Validating <bold>$suite</bold> config... "); $config = $this->getSuiteConfig($suite, $input->getOption('config')); $output->writeln("Ok"); $output->writeln("------------------------------\n"); $output->writeln("<info>$suite Suite Config</info>:\n"); $output->writeln($this->formatOutput($config)); return; } $output->write("Validating global config... "); $config = $this->getGlobalConfig(); $output->writeln($input->getOption('override')); if (count($input->getOption('override'))) { $config = $this->overrideConfig($input->getOption('override')); } $suites = Configuration::suites(); $output->writeln("Ok"); $output->writeln("------------------------------\n"); $output->writeln("<info>Codeception Config</info>:\n"); $output->writeln($this->formatOutput($config)); $output->writeln('<info>Directories</info>:'); $output->writeln("<comment>codecept_root_dir()</comment> " . codecept_root_dir()); $output->writeln("<comment>codecept_output_dir()</comment> " . codecept_output_dir()); $output->writeln("<comment>codecept_data_dir()</comment> " . codecept_data_dir()); $output->writeln(''); $output->writeln("<info>Available suites</info>: " . implode(', ', $suites)); foreach ($suites as $suite) { $output->write("Validating suite <bold>$suite</bold>... "); $this->getSuiteConfig($suite); $output->writeln('Ok'); } $output->writeln("Execute <info>codecept config:validate [<suite>]</info> to see config for a suite"); } protected function formatOutput($config) { $output = print_r($config, true); return preg_replace('~\[(.*?)\] =>~', "<fg=yellow>$1</fg=yellow> =>", $output); } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Lib\Generator\Group as GroupGenerator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Creates empty GroupObject - extension which handles all group events. * * * `codecept g:group Admin` */ class GenerateGroup extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('group', InputArgument::REQUIRED, 'Group class name'), ]); } public function getDescription() { return 'Generates Group subscriber'; } public function execute(InputInterface $input, OutputInterface $output) { $config = $this->getGlobalConfig(); $group = $input->getArgument('group'); $class = ucfirst($group); $path = $this->createDirectoryFor(Configuration::supportDir() . 'Group' . DIRECTORY_SEPARATOR, $class); $filename = $path . $class . '.php'; $gen = new GroupGenerator($config, $group); $res = $this->createFile($filename, $gen->produce()); if (!$res) { $output->writeln("<error>Group $filename already exists</error>"); return; } $output->writeln("<info>Group extension was created in $filename</info>"); $output->writeln( 'To use this group extension, include it to "extensions" option of global Codeception config.' ); } } <?php namespace Codeception\Command; use Codeception\Lib\Generator\Cept; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Generates Cept (scenario-driven test) file: * * * `codecept generate:cept suite Login` * * `codecept g:cept suite subdir/subdir/testnameCept.php` * * `codecept g:cept suite LoginCept -c path/to/project` * */ class GenerateCept extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'suite to be tested'), new InputArgument('test', InputArgument::REQUIRED, 'test to be run'), ]); } public function getDescription() { return 'Generates empty Cept file in suite'; } public function execute(InputInterface $input, OutputInterface $output) { $suite = $input->getArgument('suite'); $filename = $input->getArgument('test'); $config = $this->getSuiteConfig($suite); $this->createDirectoryFor($config['path'], $filename); $filename = $this->completeSuffix($filename, 'Cept'); $gen = new Cept($config); $full_path = rtrim($config['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; $res = $this->createFile($full_path, $gen->produce()); if (!$res) { $output->writeln("<error>Test $filename already exists</error>"); return; } $output->writeln("<info>Test was created in $full_path</info>"); } } <?php namespace Codeception\Command; use Codeception\Codecept; use Codeception\Configuration; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Executes tests. * * Usage: * * * `codecept run acceptance`: run all acceptance tests * * `codecept run tests/acceptance/MyCept.php`: run only MyCept * * `codecept run acceptance MyCept`: same as above * * `codecept run acceptance MyCest:myTestInIt`: run one test from a Cest * * `codecept run acceptance checkout.feature`: run feature-file * * `codecept run acceptance -g slow`: run tests from *slow* group * * `codecept run unit,functional`: run only unit and functional suites * * Verbosity modes: * * * `codecept run -v`: * * `codecept run --steps`: print step-by-step execution * * `codecept run -vv`: * * `codecept run --debug`: print steps and debug information * * `codecept run -vvv`: print internal debug information * * Load config: * * * `codecept run -c path/to/another/config`: from another dir * * `codecept run -c another_config.yml`: from another config file * * Override config values: * * * `codecept run -o "settings: shuffle: true"`: enable shuffle * * `codecept run -o "settings: lint: false"`: disable linting * * `codecept run -o "reporters: report: \Custom\Reporter" --report`: use custom reporter * * Run with specific extension * * * `codecept run --ext Recorder` run with Recorder extension enabled * * `codecept run --ext DotReporter` run with DotReporter printer * * `codecept run --ext "My\Custom\Extension"` run with an extension loaded by class name * * Full reference: * ``` * Arguments: * suite suite to be tested * test test to be run * * Options: * -o, --override=OVERRIDE Override config values (multiple values allowed) * --config (-c) Use custom path for config * --report Show output in compact style * --html Generate html with results (default: "report.html") * --xml Generate JUnit XML Log (default: "report.xml") * --tap Generate Tap Log (default: "report.tap.log") * --json Generate Json Log (default: "report.json") * --colors Use colors in output * --no-colors Force no colors in output (useful to override config file) * --silent Only outputs suite names and final results * --steps Show steps in output * --debug (-d) Show debug and scenario output * --coverage Run with code coverage (default: "coverage.serialized") * --coverage-html Generate CodeCoverage HTML report in path (default: "coverage") * --coverage-xml Generate CodeCoverage XML report in file (default: "coverage.xml") * --coverage-text Generate CodeCoverage text report in file (default: "coverage.txt") * --no-exit Don't finish with exit code * --group (-g) Groups of tests to be executed (multiple values allowed) * --skip (-s) Skip selected suites (multiple values allowed) * --skip-group (-x) Skip selected groups (multiple values allowed) * --env Run tests in selected environments. (multiple values allowed, environments can be merged with ',') * --fail-fast (-f) Stop after first failure * --help (-h) Display this help message. * --quiet (-q) Do not output any message. * --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug * --version (-V) Display this application version. * --ansi Force ANSI output. * --no-ansi Disable ANSI output. * --no-interaction (-n) Do not ask any interactive question. * ``` * */ class Run extends Command { use Shared\Config; /** * @var Codecept */ protected $codecept; /** * @var integer of executed suites */ protected $executed = 0; /** * @var array of options (command run) */ protected $options = []; /** * @var OutputInterface */ protected $output; /** * Sets Run arguments * @throws \Symfony\Component\Console\Exception\InvalidArgumentException */ protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::OPTIONAL, 'suite to be tested'), new InputArgument('test', InputArgument::OPTIONAL, 'test to be run'), new InputOption('override', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Override config values'), new InputOption('ext', 'e', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Run with extension enabled'), new InputOption('report', '', InputOption::VALUE_NONE, 'Show output in compact style'), new InputOption('html', '', InputOption::VALUE_OPTIONAL, 'Generate html with results', 'report.html'), new InputOption('xml', '', InputOption::VALUE_OPTIONAL, 'Generate JUnit XML Log', 'report.xml'), new InputOption('tap', '', InputOption::VALUE_OPTIONAL, 'Generate Tap Log', 'report.tap.log'), new InputOption('json', '', InputOption::VALUE_OPTIONAL, 'Generate Json Log', 'report.json'), new InputOption('colors', '', InputOption::VALUE_NONE, 'Use colors in output'), new InputOption( 'no-colors', '', InputOption::VALUE_NONE, 'Force no colors in output (useful to override config file)' ), new InputOption('silent', '', InputOption::VALUE_NONE, 'Only outputs suite names and final results'), new InputOption('steps', '', InputOption::VALUE_NONE, 'Show steps in output'), new InputOption('debug', 'd', InputOption::VALUE_NONE, 'Show debug and scenario output'), new InputOption( 'coverage', '', InputOption::VALUE_OPTIONAL, 'Run with code coverage', 'coverage.serialized' ), new InputOption( 'coverage-html', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage HTML report in path', 'coverage' ), new InputOption( 'coverage-xml', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage XML report in file', 'coverage.xml' ), new InputOption( 'coverage-text', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage text report in file', 'coverage.txt' ), new InputOption( 'coverage-crap4j', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage report in Crap4J XML format', 'crap4j.xml' ), new InputOption('no-exit', '', InputOption::VALUE_NONE, 'Don\'t finish with exit code'), new InputOption( 'group', 'g', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Groups of tests to be executed' ), new InputOption( 'skip', 's', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Skip selected suites' ), new InputOption( 'skip-group', 'x', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Skip selected groups' ), new InputOption( 'env', '', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Run tests in selected environments.' ), new InputOption('fail-fast', 'f', InputOption::VALUE_NONE, 'Stop after first failure'), new InputOption('no-rebuild', '', InputOption::VALUE_NONE, 'Do not rebuild actor classes on start'), ]); parent::configure(); } public function getDescription() { return 'Runs the test suites'; } /** * Executes Run * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output * @return int|null|void * @throws \RuntimeException */ public function execute(InputInterface $input, OutputInterface $output) { $this->ensureCurlIsAvailable(); $this->options = $input->getOptions(); $this->output = $output; // load config $config = $this->getGlobalConfig(); // update config from options if (count($this->options['override'])) { $config = $this->overrideConfig($this->options['override']); } if ($this->options['ext']) { $config = $this->enableExtensions($this->options['ext']); } if (!$this->options['colors']) { $this->options['colors'] = $config['settings']['colors']; } if (!$this->options['silent']) { $this->output->writeln( Codecept::versionString() . "\nPowered by " . \PHPUnit_Runner_Version::getVersionString() ); } if ($this->options['debug']) { $this->output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); } $userOptions = array_intersect_key($this->options, array_flip($this->passedOptionKeys($input))); $userOptions = array_merge( $userOptions, $this->booleanOptions($input, ['xml', 'html', 'json', 'tap', 'coverage', 'coverage-xml', 'coverage-html', 'coverage-crap4j']) ); $userOptions['verbosity'] = $this->output->getVerbosity(); $userOptions['interactive'] = !$input->hasParameterOption(['--no-interaction', '-n']); $userOptions['ansi'] = (!$input->hasParameterOption('--no-ansi') xor $input->hasParameterOption('ansi')); if ($this->options['no-colors'] || !$userOptions['ansi']) { $userOptions['colors'] = false; } if ($this->options['group']) { $userOptions['groups'] = $this->options['group']; } if ($this->options['skip-group']) { $userOptions['excludeGroups'] = $this->options['skip-group']; } if ($this->options['report']) { $userOptions['silent'] = true; } if ($this->options['coverage-xml'] or $this->options['coverage-html'] or $this->options['coverage-text'] or $this->options['coverage-crap4j']) { $this->options['coverage'] = true; } if (!$userOptions['ansi'] && $input->getOption('colors')) { $userOptions['colors'] = true; // turn on colors even in non-ansi mode if strictly passed } $suite = $input->getArgument('suite'); $test = $input->getArgument('test'); if (! Configuration::isEmpty() && ! $test && strpos($suite, $config['paths']['tests']) === 0) { list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']); } if ($this->options['group']) { $this->output->writeln(sprintf("[Groups] <info>%s</info> ", implode(', ', $this->options['group']))); } if ($input->getArgument('test')) { $this->options['steps'] = true; } if ($test) { $filter = $this->matchFilteredTestName($test); $userOptions['filter'] = $filter; } $this->codecept = new Codecept($userOptions); if ($suite and $test) { $this->codecept->run($suite, $test); } if (!$test) { $suites = $suite ? explode(',', $suite) : Configuration::suites(); $this->executed = $this->runSuites($suites, $this->options['skip']); if (!empty($config['include']) and !$suite) { $current_dir = Configuration::projectDir(); $suites += $config['include']; $this->runIncludedSuites($config['include'], $current_dir); } if ($this->executed === 0) { throw new \RuntimeException( sprintf("Suite '%s' could not be found", implode(', ', $suites)) ); } } $this->codecept->printResult(); if (!$input->getOption('no-exit')) { if (!$this->codecept->getResult()->wasSuccessful()) { exit(1); } } } /** * Runs included suites recursively * * @param array $suites * @param string $parent_dir */ protected function runIncludedSuites($suites, $parent_dir) { foreach ($suites as $relativePath) { $current_dir = rtrim($parent_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $relativePath; $config = Configuration::config($current_dir); $suites = Configuration::suites(); $namespace = $this->currentNamespace(); $this->output->writeln( "\n<fg=white;bg=magenta>\n[$namespace]: tests from $current_dir\n</fg=white;bg=magenta>" ); $this->executed += $this->runSuites($suites, $this->options['skip']); if (!empty($config['include'])) { $this->runIncludedSuites($config['include'], $current_dir); } } } protected function currentNamespace() { $config = Configuration::config(); if (!$config['namespace']) { throw new \RuntimeException( "Can't include into runner suite without a namespace;\n" . "Please add `namespace` section into included codeception.yml file" ); } return $config['namespace']; } protected function runSuites($suites, $skippedSuites = []) { $executed = 0; foreach ($suites as $suite) { if (in_array($suite, $skippedSuites)) { continue; } if (!in_array($suite, Configuration::suites())) { continue; } $this->codecept->run($suite); $executed++; } return $executed; } protected function matchTestFromFilename($filename, $tests_path) { $filename = str_replace(['//', '\/', '\\'], '/', $filename); $res = preg_match("~^$tests_path/(.*?)(?>/(.*))?$~", $filename, $matches); if (!$res) { throw new \InvalidArgumentException("Test file can't be matched"); } if (!isset($matches[2])) { $matches[2] = null; } return $matches; } private function matchFilteredTestName(&$path) { $test_parts = explode(':', $path, 2); if (count($test_parts) > 1) { list($path, $filter) = $test_parts; // use carat to signify start of string like in normal regex // phpunit --filter matches against the fully qualified method name, so tests actually begin with : $carat_pos = strpos($filter, '^'); if ($carat_pos !== false) { $filter = substr_replace($filter, ':', $carat_pos, 1); } return $filter; } return null; } protected function passedOptionKeys(InputInterface $input) { $options = []; $request = (string)$input; $tokens = explode(' ', $request); foreach ($tokens as $token) { $token = preg_replace('~=.*~', '', $token); // strip = from options if (empty($token)) { continue; } if ($token == '--') { break; // there should be no options after ' -- ', only arguments } if (substr($token, 0, 2) === '--') { $options[] = substr($token, 2); } elseif ($token[0] === '-') { $shortOption = substr($token, 1); $options[] = $this->getDefinition()->getOptionForShortcut($shortOption)->getName(); } } return $options; } protected function booleanOptions(InputInterface $input, $options = []) { $values = []; $request = (string)$input; foreach ($options as $option) { if (strpos($request, "--$option")) { $values[$option] = $input->hasParameterOption($option) ? $input->getParameterOption($option) : $input->getOption($option); } else { $values[$option] = false; } } return $values; } private function ensureCurlIsAvailable() { if (!extension_loaded('curl')) { throw new \Exception( "Codeception requires CURL extension installed to make tests run\n" . "If you are not sure, how to install CURL, please refer to StackOverflow\n\n" . "Notice: PHP for Apache/Nginx and CLI can have different php.ini files.\n" . "Please make sure that your PHP you run from console has CURL enabled." ); } } } <?php namespace Codeception\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; use Codeception\Codecept; /** * Auto-updates phar archive from official site: 'http://codeception.com/codecept.phar' . * * * `php codecept.phar self-update` * * @author Franck Cassedanne <franck@cassedanne.com> */ class SelfUpdate extends Command { /** * Class constants */ const NAME = 'Codeception'; const GITHUB_REPO = 'Codeception/Codeception'; const PHAR_URL = 'http://codeception.com/releases/%s/codecept.phar'; const PHAR_URL_PHP54 = 'http://codeception.com/releases/%s/php54/codecept.phar'; /** * Holds the current script filename. * @var string */ protected $filename; /** * Holds the live version string. * @var string */ protected $liveVersion; /** * {@inheritdoc} */ protected function configure() { $this->filename = $_SERVER['argv'][0]; $this // ->setAliases(array('selfupdate')) ->setDescription( sprintf( 'Upgrade <comment>%s</comment> to the latest version', $this->filename ) ); parent::configure(); } /** * @return string */ protected function getCurrentVersion() { return Codecept::VERSION; } /** * {@inheritdoc} */ public function execute(InputInterface $input, OutputInterface $output) { $version = $this->getCurrentVersion(); $output->writeln( sprintf( '<info>%s</info> version <comment>%s</comment>', self::NAME, $version ) ); $output->writeln("\n<info>Checking for a new version...</info>\n"); try { $latestVersion = $this->getLatestStableVersion(); if ($this->isOutOfDate($version, $latestVersion)) { $output->writeln( sprintf( 'A newer version is available: <comment>%s</comment>', $latestVersion ) ); if (!$input->getOption('no-interaction')) { $dialog = $this->getHelperSet()->get('question'); $question = new ConfirmationQuestion("\n<question>Do you want to update?</question> ", false); if (!$dialog->ask($input, $output, $question)) { $output->writeln("\n<info>Bye-bye!</info>\n"); return; } } $output->writeln("\n<info>Updating...</info>"); $this->retrievePharFile($latestVersion, $output); } else { $output->writeln('You are already using the latest version.'); } } catch (\Exception $e) { $output->writeln( sprintf( "<error>\n%s\n</error>", $e->getMessage() ) ); } } /** * Checks whether the provided version is current. * * @param string $version The version number to check. * @param string $latestVersion Latest stable version * @return boolean Returns True if a new version is available. */ private function isOutOfDate($version, $latestVersion) { return -1 != version_compare($version, $latestVersion, '>='); } /** * @return string */ private function getLatestStableVersion() { $stableVersions = $this->filterStableVersions( $this->getGithubTags(self::GITHUB_REPO) ); return array_reduce( $stableVersions, function ($a, $b) { return version_compare($a, $b, '>') ? $a : $b; } ); } /** * @param array $tags * @return array */ private function filterStableVersions($tags) { return array_filter($tags, function ($tag) { return preg_match('/^[0-9]+\.[0-9]+\.[0-9]+$/', $tag); }); } /** * Returns an array of tags from a github repo. * * @param string $repo The repository name to check upon. * @return array */ protected function getGithubTags($repo) { $jsonTags = $this->retrieveContentFromUrl( 'https://api.github.com/repos/' . $repo . '/tags' ); return array_map( function ($tag) { return $tag['name']; }, json_decode($jsonTags, true) ); } /** * Retrieves the body-content from the provided URL. * * @param string $url * @return string * @throws \Exception if status code is above 300 */ private function retrieveContentFromUrl($url) { $ctx = $this->prepareContext($url); $body = file_get_contents($url, 0, $ctx); if (isset($http_response_header)) { $code = substr($http_response_header[0], 9, 3); if (floor($code / 100) > 3) { throw new \Exception($http_response_header[0]); } } else { throw new \Exception('Request failed.'); } return $body; } /** * Add proxy support to context if environment variable was set up * * @param array $opt context options * @param string $url */ private function prepareProxy(&$opt, $url) { $scheme = parse_url($url)['scheme']; if ($scheme === 'http' && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { $proxy = !empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']; } if ($scheme === 'https' && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) { $proxy = !empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']; } if (!empty($proxy)) { $proxy = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $proxy); $opt['http']['proxy'] = $proxy; } } /** * Preparing context for request * @param $url * * @return resource */ private function prepareContext($url) { $opts = [ 'http' => [ 'follow_location' => 1, 'max_redirects' => 20, 'timeout' => 10, 'user_agent' => self::NAME ] ]; $this->prepareProxy($opts, $url); return stream_context_create($opts); } /** * Retrieves the latest phar file. * * @param string $version * @param OutputInterface $output * @throws \Exception */ protected function retrievePharFile($version, OutputInterface $output) { $temp = basename($this->filename, '.phar') . '-temp.phar'; try { $sourceUrl = $this->getPharUrl($version); if (@copy($sourceUrl, $temp)) { chmod($temp, 0777 & ~umask()); // test the phar validity $phar = new \Phar($temp); // free the variable to unlock the file unset($phar); rename($temp, $this->filename); } else { throw new \Exception('Request failed.'); } } catch (\Exception $e) { if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException ) { throw $e; } unlink($temp); $output->writeln( sprintf( "<error>\nSomething went wrong (%s).\nPlease re-run this again.</error>\n", $e->getMessage() ) ); } $output->writeln( sprintf( "\n<comment>%s</comment> has been updated.\n", $this->filename ) ); } /** * Returns Phar file URL for specified version * * @param string $version * @return string */ protected function getPharUrl($version) { $sourceUrl = self::PHAR_URL; if (version_compare(PHP_VERSION, '5.6.0', '<')) { $sourceUrl = self::PHAR_URL_PHP54; } return sprintf($sourceUrl, $version); } } <?php namespace Codeception\Command; use Codeception\Lib\Generator\Test as TestGenerator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Generates skeleton for Unit Test that extends `Codeception\TestCase\Test`. * * * `codecept g:test unit User` * * `codecept g:test unit "App\User"` */ class GenerateTest extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition( [ new InputArgument('suite', InputArgument::REQUIRED, 'suite where tests will be put'), new InputArgument('class', InputArgument::REQUIRED, 'class name'), ] ); parent::configure(); } public function getDescription() { return 'Generates empty unit test file in suite'; } public function execute(InputInterface $input, OutputInterface $output) { $suite = $input->getArgument('suite'); $class = $input->getArgument('class'); $config = $this->getSuiteConfig($suite); $className = $this->getShortClassName($class); $path = $this->createDirectoryFor($config['path'], $class); $filename = $this->completeSuffix($className, 'Test'); $filename = $path . $filename; $gen = new TestGenerator($config, $class); $res = $this->createFile($filename, $gen->produce()); if (!$res) { $output->writeln("<error>Test $filename already exists</error>"); return; } $output->writeln("<info>Test was created in $filename</info>"); } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Lib\Generator\Actions as ActionsGenerator; use Codeception\Lib\Generator\Actor as ActorGenerator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Generates Actor classes (initially Guy classes) from suite configs. * Starting from Codeception 2.0 actor classes are auto-generated. Use this command to generate them manually. * * * `codecept build` * * `codecept build path/to/project` * */ class Build extends Command { use Shared\Config; use Shared\FileSystem; protected $inheritedMethodTemplate = ' * @method void %s(%s)'; /** * @var OutputInterface */ protected $output; public function getDescription() { return 'Generates base classes for all suites'; } protected function execute(InputInterface $input, OutputInterface $output) { $this->output = $output; $this->buildActorsForConfig(); } private function buildActor(array $settings) { $actorGenerator = new ActorGenerator($settings); $this->output->writeln( '<info>' . Configuration::config()['namespace'] . '\\' . $actorGenerator->getActorName() . "</info> includes modules: " . implode(', ', $actorGenerator->getModules()) ); $content = $actorGenerator->produce(); $file = $this->createDirectoryFor( Configuration::supportDir(), $settings['actor'] ) . $this->getShortClassName($settings['actor']); $file .= '.php'; return $this->createFile($file, $content); } private function buildActions(array $settings) { $actionsGenerator = new ActionsGenerator($settings); $this->output->writeln( " -> {$settings['actor']}Actions.php generated successfully. " . $actionsGenerator->getNumMethods() . " methods added" ); $content = $actionsGenerator->produce(); $file = $this->createDirectoryFor(Configuration::supportDir() . '_generated', $settings['actor']); $file .= $this->getShortClassName($settings['actor']) . 'Actions.php'; return $this->createFile($file, $content, true); } private function buildSuiteActors() { $suites = $this->getSuites(); if (!empty($suites)) { $this->output->writeln("<info>Building Actor classes for suites: " . implode(', ', $suites) . '</info>'); } foreach ($suites as $suite) { $settings = $this->getSuiteConfig($suite); if (!$settings['actor']) { continue; // no actor } $this->buildActions($settings); $actorBuilt = $this->buildActor($settings); if ($actorBuilt) { $this->output->writeln("{$settings['actor']}.php created."); } } } protected function buildActorsForConfig($configFile = null) { $config = $this->getGlobalConfig($configFile); $dir = Configuration::projectDir(); $this->buildSuiteActors(); foreach ($config['include'] as $subConfig) { $this->output->writeln("\n<comment>Included Configuration: $subConfig</comment>"); $this->buildActorsForConfig($dir . DIRECTORY_SEPARATOR . $subConfig); } } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Lib\Generator\Helper; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Creates empty Helper class. * * * `codecept g:helper MyHelper` * * `codecept g:helper "My\Helper"` * */ class GenerateHelper extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('name', InputArgument::REQUIRED, 'helper name'), ]); } public function getDescription() { return 'Generates new helper'; } public function execute(InputInterface $input, OutputInterface $output) { $name = ucfirst($input->getArgument('name')); $config = $this->getGlobalConfig(); $path = $this->createDirectoryFor(Configuration::supportDir() . 'Helper', $name); $filename = $path . $this->getShortClassName($name) . '.php'; $res = $this->createFile($filename, (new Helper($name, $config['namespace']))->produce()); if ($res) { $output->writeln("<info>Helper $filename created</info>"); } else { $output->writeln("<error>Error creating helper $filename</error>"); } } } <?php namespace Codeception\Command; use Codeception\Lib\Generator\Helper; use Codeception\Template\Bootstrap as BootstrapTemplate; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Yaml\Yaml; /** * Creates default config, tests directory and sample suites for current project. * Use this command to start building a test suite. * * By default it will create 3 suites **acceptance**, **functional**, and **unit**. * * * `codecept bootstrap` - creates `tests` dir and `codeception.yml` in current dir. * * `codecept bootstrap --empty` - creates `tests` dir without suites * * `codecept bootstrap --namespace Frontend` - creates tests, and use `Frontend` namespace for actor classes and helpers. * * `codecept bootstrap --actor Wizard` - sets actor as Wizard, to have `TestWizard` actor in tests. * * `codecept bootstrap path/to/the/project` - provide different path to a project, where tests should be placed * */ class Bootstrap extends Command { protected function configure() { $this->setDefinition( [ new InputArgument('path', InputArgument::OPTIONAL, 'custom installation dir', null), new InputOption( 'namespace', 'ns', InputOption::VALUE_OPTIONAL, 'Namespace to add for actor classes and helpers' ), new InputOption('actor', 'a', InputOption::VALUE_OPTIONAL, 'Custom actor instead of Tester'), new InputOption('empty', 'e', InputOption::VALUE_NONE, 'Don\'t create standard suites') ] ); } public function getDescription() { return "Creates default test suites and generates all required files"; } public function execute(InputInterface $input, OutputInterface $output) { $bootstrap = new BootstrapTemplate($input, $output); if ($input->getArgument('path')) { $bootstrap->initDir($input->getArgument('path')); } $bootstrap->setup(); } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Exception\ConfigurationException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Generates empty environment configuration file into envs dir: * * * `codecept g:env firefox` * * Required to have `envs` path to be specifed in `codeception.yml` */ class GenerateEnvironment extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('env', InputArgument::REQUIRED, 'Environment name'), ]); } public function getDescription() { return 'Generates empty environment config'; } public function execute(InputInterface $input, OutputInterface $output) { $conf = $this->getGlobalConfig(); if (!Configuration::envsDir()) { throw new ConfigurationException( "Path for environments configuration is not set.\n" . "Please specify envs path in your `codeception.yml`\n \n" . "envs: tests/_envs" ); } $relativePath = $conf['paths']['envs']; $env = $input->getArgument('env'); $file = "$env.yml"; $path = $this->createDirectoryFor($relativePath, $file); $saved = $this->createFile($path . $file, "# `$env` environment config goes here"); if ($saved) { $output->writeln("<info>$env config was created in $relativePath/$file</info>"); } else { $output->writeln("<error>File $relativePath/$file already exists</error>"); } } } <?php namespace Codeception\Command; use Codeception\Lib\Generator\Feature; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Generates Feature file (in Gherkin): * * * `codecept generate:feature suite Login` * * `codecept g:feature suite subdir/subdir/login.feature` * * `codecept g:feature suite login.feature -c path/to/project` * */ class GenerateFeature extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'suite to be tested'), new InputArgument('feature', InputArgument::REQUIRED, 'feature to be generated'), new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'), ]); } public function getDescription() { return 'Generates empty feature file in suite'; } public function execute(InputInterface $input, OutputInterface $output) { $suite = $input->getArgument('suite'); $filename = $input->getArgument('feature'); $config = $this->getSuiteConfig($suite); $this->createDirectoryFor($config['path'], $filename); $gen = new Feature(basename($filename)); if (!preg_match('~\.feature$~', $filename)) { $filename .= '.feature'; } $full_path = rtrim($config['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; $res = $this->createFile($full_path, $gen->produce()); if (!$res) { $output->writeln("<error>Feature $filename already exists</error>"); return; } $output->writeln("<info>Feature was created in $full_path</info>"); } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Exception\ConfigurationException as ConfigurationException; use Codeception\Test\Cest; use Codeception\Test\Interfaces\ScenarioDriven; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventDispatcher; /** * Generates user-friendly text scenarios from scenario-driven tests (Cest, Cept). * * * `codecept g:scenarios acceptance` - for all acceptance tests * * `codecept g:scenarios acceptance --format html` - in html format * * `codecept g:scenarios acceptance --path doc` - generate scenarios to `doc` dir */ class GenerateScenarios extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'suite from which texts should be generated'), new InputOption('path', 'p', InputOption::VALUE_REQUIRED, 'Use specified path as destination instead of default'), new InputOption('single-file', '', InputOption::VALUE_NONE, 'Render all scenarios to only one file'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Specify output format: html or text (default)', 'text'), ]); parent::configure(); } public function getDescription() { return 'Generates text representation for all scenarios'; } protected function execute(InputInterface $input, OutputInterface $output) { $suite = $input->getArgument('suite'); $suiteConf = $this->getSuiteConfig($suite); $path = $input->getOption('path') ? $input->getOption('path') : Configuration::dataDir() . 'scenarios'; $format = $input->getOption('format'); @mkdir($path); if (!is_writable($path)) { throw new ConfigurationException( "Path $path is not writable. Please, set valid permissions for folder to store scenarios." ); } $path = $path . DIRECTORY_SEPARATOR . $suite; if (!$input->getOption('single-file')) { @mkdir($path); } $suiteManager = new \Codeception\SuiteManager(new EventDispatcher(), $suite, $suiteConf); if ($suiteConf['bootstrap']) { if (file_exists($suiteConf['path'] . $suiteConf['bootstrap'])) { require_once $suiteConf['path'] . $suiteConf['bootstrap']; } } $tests = $this->getTests($suiteManager); $scenarios = ""; foreach ($tests as $test) { if (!($test instanceof ScenarioDriven)) { continue; } $feature = $test->getScenarioText($format); $name = $this->underscore(basename($test->getFileName(), '.php')); // create separate file for each test in Cest if ($test instanceof Cest && !$input->getOption('single-file')) { $name .= '.' . $this->underscore($test->getTestMethod()); } if ($input->getOption('single-file')) { $scenarios .= $feature; $output->writeln("* $name rendered"); } else { $feature = $this->decorate($feature, $format); $this->createFile($path . DIRECTORY_SEPARATOR . $name . $this->formatExtension($format), $feature, true); $output->writeln("* $name generated"); } } if ($input->getOption('single-file')) { $this->createFile($path . $this->formatExtension($format), $this->decorate($scenarios, $format), true); } } protected function decorate($text, $format) { switch ($format) { case 'text': return $text; case 'html': return "<html><body>$text</body></html>"; } } protected function getTests($suiteManager) { $suiteManager->loadTests(); return $suiteManager->getSuite()->tests(); } protected function formatExtension($format) { switch ($format) { case 'text': return '.txt'; case 'html': return '.html'; } } private function underscore($name) { $name = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\\1_\\2', $name); $name = preg_replace('/([a-z\d])([A-Z])/', '\\1_\\2', $name); $name = str_replace(['/', '\\'], ['.', '.'], $name); $name = preg_replace('/_Cept$/', '', $name); $name = preg_replace('/_Cest$/', '', $name); return $name; } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Lib\Generator\StepObject as StepObjectGenerator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; /** * Generates StepObject class. You will be asked for steps you want to implement. * * * `codecept g:step acceptance AdminSteps` * * `codecept g:step acceptance UserSteps --silent` - skip action questions * */ class GenerateStepObject extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'Suite for StepObject'), new InputArgument('step', InputArgument::REQUIRED, 'StepObject name'), new InputOption('silent', '', InputOption::VALUE_NONE, 'skip verification question'), ]); } public function getDescription() { return 'Generates empty StepObject class'; } public function execute(InputInterface $input, OutputInterface $output) { $suite = $input->getArgument('suite'); $step = $input->getArgument('step'); $config = $this->getSuiteConfig($suite); $class = $this->getShortClassName($step); $path = $this->createDirectoryFor(Configuration::supportDir() . 'Step' . DIRECTORY_SEPARATOR . ucfirst($suite), $step); $dialog = $this->getHelperSet()->get('question'); $filename = $path . $class . '.php'; $helper = $this->getHelper('question'); $question = new Question("Add action to StepObject class (ENTER to exit): "); $gen = new StepObjectGenerator($config, ucfirst($suite) . '\\' . $step); if (!$input->getOption('silent')) { do { $question = new Question('Add action to StepObject class (ENTER to exit): ', null); $action = $dialog->ask($input, $output, $question); if ($action) { $gen->createAction($action); } } while ($action); } $res = $this->createFile($filename, $gen->produce()); if (!$res) { $output->writeln("<error>StepObject $filename already exists</error>"); exit; } $output->writeln("<info>StepObject was created in $filename</info>"); } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Util\FileSystem; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Cleans `output` directory * * * `codecept clean` * * `codecept clean -c path/to/project` * */ class Clean extends Command { use Shared\Config; public function getDescription() { return 'Cleans or creates _output directory'; } protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln("<info>Cleaning up " . Configuration::outputDir() . "...</info>"); FileSystem::doEmptyDir(Configuration::outputDir()); $output->writeln("Done"); } } <?php namespace Codeception\Command; use Codeception\Test\Loader\Gherkin; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Prints all steps from all Gherkin contexts for a specific suite * * ``` * codecept gherkin:steps acceptance * ``` * */ class GherkinSteps extends Command { use Shared\Config; use Shared\Style; protected function configure() { $this->setDefinition( [ new InputArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files'), new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'), ] ); parent::configure(); } public function getDescription() { return 'Prints all defined feature steps'; } public function execute(InputInterface $input, OutputInterface $output) { $this->addStyles($output); $suite = $input->getArgument('suite'); $config = $this->getSuiteConfig($suite); $config['describe_steps'] = true; $loader = new Gherkin($config); $steps = $loader->getSteps(); foreach ($steps as $name => $context) { /** @var $table Table **/ $table = new Table($output); $table->setHeaders(array('Step', 'Implementation')); $output->writeln("Steps from <bold>$name</bold> context:"); foreach ($context as $step => $callable) { if (count($callable) < 2) { continue; } $method = $callable[0] . '::' . $callable[1]; $table->addRow([$step, $method]); } $table->render(); } if (!isset($table)) { $output->writeln("No steps are defined, start creating them by running <bold>gherkin:snippets</bold>"); } } } <?php namespace Codeception\Command; use Codeception\Configuration; use Stecman\Component\Symfony\Console\BashCompletion\Completion as ConsoleCompletion; use Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand; use Stecman\Component\Symfony\Console\BashCompletion\CompletionHandler; use Stecman\Component\Symfony\Console\BashCompletion\Completion\ShellPathCompletion as ShellPathCompletion; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Completion extends CompletionCommand { protected function configureCompletion(CompletionHandler $handler) { // Can't set for all commands, because it wouldn't work well with generate:suite $suiteCommands = [ 'run', 'config:validate', 'console', 'dry-run', 'generate:cept', 'generate:cest', 'generate:feature', 'generate:phpunit', 'generate:scenarios', 'generate:stepobject', 'generate:test', 'gherkin:snippets', 'gherkin:steps' ]; foreach ($suiteCommands as $suiteCommand) { $handler->addHandler(new ConsoleCompletion( $suiteCommand, 'suite', ConsoleCompletion::TYPE_ARGUMENT, Configuration::suites() )); } $handler->addHandlers([ new ShellPathCompletion( ConsoleCompletion::ALL_COMMANDS, 'path', ConsoleCompletion::TYPE_ARGUMENT ), new ShellPathCompletion( ConsoleCompletion::ALL_COMMANDS, 'test', ConsoleCompletion::TYPE_ARGUMENT ), ]); } protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('generate-hook') && $input->getOption('use-vendor-bin')) { global $argv; $argv[0] = 'vendor/bin/' . basename($argv[0]); } parent::execute($input, $output); } protected function createDefinition() { $definition = parent::createDefinition(); $definition->addOption(new InputOption( 'use-vendor-bin', null, InputOption::VALUE_NONE, 'Use the vendor bin for autocompletion.' )); return $definition; } } <?php namespace Codeception\Command; use Codeception\Lib\Generator\Cest as CestGenerator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Generates Cest (scenario-driven object-oriented test) file: * * * `codecept generate:cest suite Login` * * `codecept g:cest suite subdir/subdir/testnameCest.php` * * `codecept g:cest suite LoginCest -c path/to/project` * * `codecept g:cest "App\Login"` * */ class GenerateCest extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'suite where tests will be put'), new InputArgument('class', InputArgument::REQUIRED, 'test name'), ]); } public function getDescription() { return 'Generates empty Cest file in suite'; } public function execute(InputInterface $input, OutputInterface $output) { $suite = $input->getArgument('suite'); $class = $input->getArgument('class'); $config = $this->getSuiteConfig($suite); $className = $this->getShortClassName($class); $path = $this->createDirectoryFor($config['path'], $class); $filename = $this->completeSuffix($className, 'Cest'); $filename = $path . $filename; if (file_exists($filename)) { $output->writeln("<error>Test $filename already exists</error>"); return; } $gen = new CestGenerator($class, $config); $res = $this->createFile($filename, $gen->produce()); if (!$res) { $output->writeln("<error>Test $filename already exists</error>"); return; } $output->writeln("<info>Test was created in $filename</info>"); } } <?php namespace Codeception\Command; use Codeception\Configuration; use Codeception\Lib\Generator\PageObject as PageObjectGenerator; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Generates PageObject. Can be generated either globally, or just for one suite. * If PageObject is generated globally it will act as UIMap, without any logic in it. * * * `codecept g:page Login` * * `codecept g:page Registration` * * `codecept g:page acceptance Login` */ class GeneratePageObject extends Command { use Shared\FileSystem; use Shared\Config; protected function configure() { $this->setDefinition([ new InputArgument('suite', InputArgument::REQUIRED, 'Either suite name or page object name)'), new InputArgument('page', InputArgument::OPTIONAL, 'Page name of pageobject to represent'), ]); parent::configure(); } public function getDescription() { return 'Generates empty PageObject class'; } public function execute(InputInterface $input, OutputInterface $output) { $suite = $input->getArgument('suite'); $class = $input->getArgument('page'); if (!$class) { $class = $suite; $suite = null; } $conf = $suite ? $this->getSuiteConfig($suite) : $this->getGlobalConfig(); if ($suite) { $suite = DIRECTORY_SEPARATOR . ucfirst($suite); } $path = $this->createDirectoryFor(Configuration::supportDir() . 'Page' . $suite, $class); $filename = $path . $this->getShortClassName($class) . '.php'; $output->writeln($filename); $gen = new PageObjectGenerator($conf, ucfirst($suite) . '\\' . $class); $res = $this->createFile($filename, $gen->produce()); if (!$res) { $output->writeln("<error>PageObject $filename already exists</error>"); exit; } $output->writeln("<info>PageObject was created in $filename</info>"); } protected function pathToPageObject($class, $suite) { } } <?php namespace Codeception; use Codeception\Lib\ModuleContainer; use Codeception\Step\Meta as MetaStep; use Codeception\Util\Locator; abstract class Step { const STACK_POSITION = 3; /** * @var string */ protected $action; /** * @var array */ protected $arguments; protected $debugOutput; public $executed = false; protected $line = null; protected $file = null; protected $prefix = 'I'; /** * @var MetaStep */ protected $metaStep = null; protected $failed = false; public function __construct($action, array $arguments = []) { $this->action = $action; $this->arguments = $arguments; } public function saveTrace() { $stack = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); if (count($stack) <= self::STACK_POSITION) { return; } $traceLine = $stack[self::STACK_POSITION - 1]; if (!isset($traceLine['file'])) { return; } $this->file = $traceLine['file']; $this->line = $traceLine['line']; $this->addMetaStep($traceLine, $stack); } private function isTestFile($file) { return preg_match('~[^\\'.DIRECTORY_SEPARATOR.'](Cest|Cept|Test).php$~', $file); } public function getName() { $class = explode('\\', __CLASS__); return end($class); } public function getAction() { return $this->action; } public function getLine() { if ($this->line && $this->file) { return codecept_relative_path($this->file) . ':' . $this->line; } } public function hasFailed() { return $this->failed; } public function getArguments() { return $this->arguments; } public function getArgumentsAsString($maxLength = 200) { $arguments = $this->arguments; $argumentCount = count($arguments); $totalLength = $argumentCount - 1; // count separators before adding length of individual arguments foreach ($arguments as $key => $argument) { $stringifiedArgument = $this->stringifyArgument($argument); $arguments[$key] = $stringifiedArgument; $totalLength += mb_strlen($stringifiedArgument, 'utf-8'); } if ($totalLength > $maxLength && $maxLength > 0) { //sort arguments from shortest to longest uasort($arguments, function ($arg1, $arg2) { $length1 = mb_strlen($arg1, 'utf-8'); $length2 = mb_strlen($arg2, 'utf-8'); if ($length1 === $length2) { return 0; } return ($length1 < $length2) ? -1 : 1; }); $allowedLength = floor(($maxLength - $argumentCount + 1) / $argumentCount); $lengthRemaining = $maxLength; $argumentsRemaining = $argumentCount; foreach ($arguments as $key => $argument) { $argumentsRemaining--; if (mb_strlen($argument, 'utf-8') > $allowedLength) { $arguments[$key] = mb_substr($argument, 0, $allowedLength - 4, 'utf-8') . '...' . mb_substr($argument, -1, 1, 'utf-8'); $lengthRemaining -= ($allowedLength + 1); } else { $lengthRemaining -= (mb_strlen($arguments[$key], 'utf-8') + 1); //recalculate allowed length because this argument was short if ($argumentsRemaining > 0) { $allowedLength = floor(($lengthRemaining - $argumentsRemaining + 1) / $argumentsRemaining); } } } //restore original order of arguments ksort($arguments); } return implode(',', $arguments); } protected function stringifyArgument($argument) { if (is_string($argument)) { return '"' . strtr($argument, ["\n" => '\n', "\r" => '\r', "\t" => ' ']) . '"'; } elseif (is_resource($argument)) { $argument = (string)$argument; } elseif (is_array($argument)) { foreach ($argument as $key => $value) { if (is_object($value)) { $argument[$key] = $this->getClassName($value); } } } elseif (is_object($argument)) { if (method_exists($argument, '__toString')) { $argument = (string)$argument; } elseif (get_class($argument) == 'Facebook\WebDriver\WebDriverBy') { $argument = Locator::humanReadableString($argument); } else { $argument = $this->getClassName($argument); } } return json_encode($argument, JSON_UNESCAPED_UNICODE); } protected function getClassName($argument) { if ($argument instanceof \Closure) { return 'Closure'; } elseif ((isset($argument->__mocked))) { return $this->formatClassName($argument->__mocked); } else { return $this->formatClassName(get_class($argument)); } } protected function formatClassName($classname) { return trim($classname, "\\"); } public function getPhpCode($maxLength) { $result = "\${$this->prefix}->" . $this->getAction() . '('; $maxLength = $maxLength - mb_strlen($result, 'utf-8') - 1; $result .= $this->getHumanizedArguments($maxLength) .')'; return $result; } /** * @return MetaStep */ public function getMetaStep() { return $this->metaStep; } public function __toString() { $humanizedAction = $this->humanize($this->getAction()); return $humanizedAction . ' ' . $this->getHumanizedArguments(); } public function toString($maxLength) { $humanizedAction = $this->humanize($this->getAction()); $maxLength = $maxLength - mb_strlen($humanizedAction, 'utf-8') - 1; return $humanizedAction . ' ' . $this->getHumanizedArguments($maxLength); } public function getHtml($highlightColor = '#732E81') { if (empty($this->arguments)) { return sprintf('%s %s', ucfirst($this->prefix), $this->humanize($this->getAction())); } return sprintf('%s %s <span style="color: %s">%s</span>', ucfirst($this->prefix), htmlspecialchars($this->humanize($this->getAction())), $highlightColor, htmlspecialchars($this->getHumanizedArguments())); } public function getHumanizedActionWithoutArguments() { return $this->humanize($this->getAction()); } public function getHumanizedArguments($maxLength = 200) { return $this->getArgumentsAsString($maxLength); } protected function clean($text) { return str_replace('\/', '', $text); } protected function humanize($text) { $text = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\\1 \\2', $text); $text = preg_replace('/([a-z\d])([A-Z])/', '\\1 \\2', $text); $text = preg_replace('~\bdont\b~', 'don\'t', $text); return strtolower($text); } public function run(ModuleContainer $container = null) { $this->executed = true; if (!$container) { return null; } $activeModule = $container->moduleForAction($this->action); if (!is_callable([$activeModule, $this->action])) { throw new \RuntimeException("Action '{$this->action}' can't be called"); } try { $res = call_user_func_array([$activeModule, $this->action], $this->arguments); } catch (\Exception $e) { $this->failed = true; if ($this->getMetaStep()) { $this->getMetaStep()->setFailed(true); } throw $e; } return $res; } /** * If steps are combined into one method they can be reproduced as meta-step. * We are using stack trace to analyze if steps were called from test, if not - they were called from meta-step. * * @param $step * @param $stack */ protected function addMetaStep($step, $stack) { if (($this->isTestFile($this->file)) || ($step['class'] == 'Codeception\Scenario')) { return; } $i = count($stack) - self::STACK_POSITION - 1; // get into test file and retrieve its actual call while (isset($stack[$i])) { $step = $stack[$i]; $i--; if (!isset($step['file']) or !isset($step['function']) or !isset($step['class'])) { continue; } if (!$this->isTestFile($step['file'])) { continue; } $this->metaStep = new Step\Meta($step['function'], array_values($step['args'])); $this->metaStep->setTraceInfo($step['file'], $step['line']); // pageobjects or other classes should not be included with "I" if (!in_array('Codeception\Actor', class_parents($step['class']))) { $this->metaStep->setPrefix($step['class'] . ':'); } return; } } /** * @param MetaStep $metaStep */ public function setMetaStep($metaStep) { $this->metaStep = $metaStep; } /** * @return string */ public function getPrefix() { return $this->prefix . ' '; } } <?php namespace Codeception\Module; use Codeception\TestInterface; /** * * Works with SFTP/FTP servers. * * In order to test the contents of a specific file stored on any remote FTP/SFTP system * this module downloads a temporary file to the local system. The temporary directory is * defined by default as ```tests/_data``` to specify a different directory set the tmp config * option to your chosen path. * * Don't forget to create the folder and ensure its writable. * * Supported and tested FTP types are: * * * FTP * * SFTP * * Connection uses php build in FTP client for FTP, * connection to SFTP uses [phpseclib](http://phpseclib.sourceforge.net/) pulled in using composer. * * For SFTP, add [phpseclib](http://phpseclib.sourceforge.net/) to require list. * ``` * "require": { * "phpseclib/phpseclib": "0.3.6" * } * ``` * * ## Status * * * Maintainer: **nathanmac** * * Stability: * - FTP: **stable** * - SFTP: **stable** * * Contact: nathan.macnamara@outlook.com * * ## Config * * * type: ftp - type of connection ftp/sftp (defaults to ftp). * * host *required* - hostname/ip address of the ftp server. * * port: 21 - port number for the ftp server * * timeout: 90 - timeout settings for connecting the ftp server. * * user: anonymous - user to access ftp server, defaults to anonymous authentication. * * password - password, defaults to empty for anonymous. * * key - path to RSA key for sftp. * * tmp - path to local directory for storing tmp files. * * passive: true - Turns on or off passive mode (FTP only) * * cleanup: true - remove tmp files from local directory on completion. * * ### Example * #### Example (FTP) * * modules: * enabled: [FTP] * config: * FTP: * type: ftp * host: '127.0.0.1' * port: 21 * timeout: 120 * user: 'root' * password: 'root' * key: ~/.ssh/id_rsa * tmp: 'tests/_data/ftp' * passive: true * cleanup: false * * #### Example (SFTP) * * modules: * enabled: [FTP] * config: * FTP: * type: sftp * host: '127.0.0.1' * port: 22 * timeout: 120 * user: 'root' * password: 'root' * key: '' * tmp: 'tests/_data/ftp' * cleanup: false * * * This module extends the Filesystem module, file contents methods are inherited from this module. */ class FTP extends Filesystem { /** * FTP/SFTP connection handler */ protected $ftp = null; /** * Configuration options and default settings * * @var array */ protected $config = [ 'type' => 'ftp', 'port' => 21, 'timeout' => 90, 'user' => 'anonymous', 'password' => '', 'key' => '', 'tmp' => 'tests/_data', 'passive' => false, 'cleanup' => true ]; /** * Required configuration fields * * @var array */ protected $requiredFields = ['host']; // ----------- SETUP METHODS BELOW HERE -------------------------// /** * Setup connection and login with config settings * * @param \Codeception\TestInterface $test */ public function _before(TestInterface $test) { // Login using config settings $this->loginAs($this->config['user'], $this->config['password']); } /** * Close the FTP connection & Clear up */ public function _after(TestInterface $test) { $this->_closeConnection(); // Clean up temp files if ($this->config['cleanup']) { if (file_exists($this->config['tmp'] . '/ftp_data_file.tmp')) { unlink($this->config['tmp'] . '/ftp_data_file.tmp'); } } } /** * Change the logged in user mid-way through your test, this closes the * current connection to the server and initialises and new connection. * * On initiation of this modules you are automatically logged into * the server using the specified config options or defaulted * to anonymous user if not provided. * * ``` php * <?php * $I->loginAs('user','password'); * ?> * ``` * * @param String $user * @param String $password */ public function loginAs($user = 'anonymous', $password = '') { $this->_openConnection($user, $password); // Create new connection and login. } /** * Enters a directory on the ftp system - FTP root directory is used by default * * @param $path */ public function amInPath($path) { $this->_changeDirectory($this->path = $this->absolutizePath($path) . ($path == '/' ? '' : DIRECTORY_SEPARATOR)); $this->debug('Moved to ' . $this->path); } /** * Resolve path * * @param $path * @return string */ protected function absolutizePath($path) { if (strpos($path, '/') === 0) { return $path; } return $this->path . $path; } // ----------- SEARCH METHODS BELOW HERE ------------------------// /** * Checks if file exists in path on the remote FTP/SFTP system. * DOES NOT OPEN the file when it's exists * * ``` php * <?php * $I->seeFileFound('UserModel.php','app/models'); * ?> * ``` * * @param $filename * @param string $path */ public function seeFileFound($filename, $path = '') { $files = $this->grabFileList($path); $this->debug("see file: {$filename}"); $this->assertContains($filename, $files, "file {$filename} not found in {$path}"); } /** * Checks if file exists in path on the remote FTP/SFTP system, using regular expression as filename. * DOES NOT OPEN the file when it's exists * * ``` php * <?php * $I->seeFileFoundMatches('/^UserModel_([0-9]{6}).php$/','app/models'); * ?> * ``` * * @param $regex * @param string $path */ public function seeFileFoundMatches($regex, $path = '') { foreach ($this->grabFileList($path) as $filename) { preg_match($regex, $filename, $matches); if (!empty($matches)) { $this->debug("file '{$filename}' matches '{$regex}'"); return; } } $this->fail("no file matches found for '{$regex}'"); } /** * Checks if file does not exist in path on the remote FTP/SFTP system * * @param $filename * @param string $path */ public function dontSeeFileFound($filename, $path = '') { $files = $this->grabFileList($path); $this->debug("don't see file: {$filename}"); $this->assertNotContains($filename, $files); } /** * Checks if file does not exist in path on the remote FTP/SFTP system, using regular expression as filename. * DOES NOT OPEN the file when it's exists * * @param $regex * @param string $path */ public function dontSeeFileFoundMatches($regex, $path = '') { foreach ($this->grabFileList($path) as $filename) { preg_match($regex, $filename, $matches); if (!empty($matches)) { $this->fail("file matches found for {$regex}"); } } $this->assertTrue(true); $this->debug("no files match '{$regex}'"); } // ----------- UTILITY METHODS BELOW HERE -------------------------// /** * Opens a file (downloads from the remote FTP/SFTP system to a tmp directory for processing) * and stores it's content. * * Usage: * * ``` php * <?php * $I->openFile('composer.json'); * $I->seeInThisFile('codeception/codeception'); * ?> * ``` * * @param $filename */ public function openFile($filename) { $this->_openFile($this->absolutizePath($filename)); } /** * Saves contents to tmp file and uploads the FTP/SFTP system. * Overwrites current file on server if exists. * * ``` php * <?php * $I->writeToFile('composer.json', 'some data here'); * ?> * ``` * * @param $filename * @param $contents */ public function writeToFile($filename, $contents) { $this->_writeToFile($this->absolutizePath($filename), $contents); } /** * Create a directory on the server * * ``` php * <?php * $I->makeDir('vendor'); * ?> * ``` * * @param $dirname */ public function makeDir($dirname) { $this->makeDirectory($this->absolutizePath($dirname)); } /** * Currently not supported in this module, overwrite inherited method * * @param $src * @param $dst */ public function copyDir($src, $dst) { $this->fail('copyDir() currently unsupported by FTP module'); } /** * Rename/Move file on the FTP/SFTP server * * ``` php * <?php * $I->renameFile('composer.lock', 'composer_old.lock'); * ?> * ``` * * @param $filename * @param $rename */ public function renameFile($filename, $rename) { $this->renameDirectory($this->absolutizePath($filename), $this->absolutizePath($rename)); } /** * Rename/Move directory on the FTP/SFTP server * * ``` php * <?php * $I->renameDir('vendor', 'vendor_old'); * ?> * ``` * * @param $dirname * @param $rename */ public function renameDir($dirname, $rename) { $this->renameDirectory($this->absolutizePath($dirname), $this->absolutizePath($rename)); } /** * Deletes a file on the remote FTP/SFTP system * * ``` php * <?php * $I->deleteFile('composer.lock'); * ?> * ``` * * @param $filename */ public function deleteFile($filename) { $this->delete($this->absolutizePath($filename)); } /** * Deletes directory with all subdirectories on the remote FTP/SFTP server * * ``` php * <?php * $I->deleteDir('vendor'); * ?> * ``` * * @param $dirname */ public function deleteDir($dirname) { $this->delete($this->absolutizePath($dirname)); } /** * Erases directory contents on the FTP/SFTP server * * ``` php * <?php * $I->cleanDir('logs'); * ?> * ``` * * @param $dirname */ public function cleanDir($dirname) { $this->clearDirectory($this->absolutizePath($dirname)); } // ----------- GRABBER METHODS BELOW HERE -----------------------// /** * Grabber method for returning file/folders listing in an array * * ```php * <?php * $files = $I->grabFileList(); * $count = $I->grabFileList('TEST', false); // Include . .. .thumbs.db * ?> * ``` * * @param string $path * @param bool $ignore - suppress '.', '..' and '.thumbs.db' * @return array */ public function grabFileList($path = '', $ignore = true) { $absolutize_path = $this->absolutizePath($path) . ($path != '' && substr($path, -1) != '/' ? DIRECTORY_SEPARATOR : ''); $files = $this->_listFiles($absolutize_path); $display_files = []; if (is_array($files) && !empty($files)) { $this->debug('File List:'); foreach ($files as &$file) { if (strtolower($file) != '.' && strtolower($file) != '..' && strtolower($file) != 'thumbs.db' ) { // Ignore '.', '..' and 'thumbs.db' // Replace full path from file listings if returned in listing $file = str_replace( $absolutize_path, '', $file ); $display_files[] = $file; $this->debug(' - ' . $file); } } return $ignore ? $display_files : $files; } $this->debug("File List: <empty>"); return []; } /** * Grabber method for returning file/folders count in directory * * ```php * <?php * $count = $I->grabFileCount(); * $count = $I->grabFileCount('TEST', false); // Include . .. .thumbs.db * ?> * ``` * * @param string $path * @param bool $ignore - suppress '.', '..' and '.thumbs.db' * @return int */ public function grabFileCount($path = '', $ignore = true) { $count = count($this->grabFileList($path, $ignore)); $this->debug("File Count: {$count}"); return $count; } /** * Grabber method to return file size * * ```php * <?php * $size = $I->grabFileSize('test.txt'); * ?> * ``` * * @param $filename * @return bool */ public function grabFileSize($filename) { $fileSize = $this->size($filename); $this->debug("{$filename} has a file size of {$fileSize}"); return $fileSize; } /** * Grabber method to return last modified timestamp * * ```php * <?php * $time = $I->grabFileModified('test.txt'); * ?> * ``` * * @param $filename * @return bool */ public function grabFileModified($filename) { $time = $this->modified($filename); $this->debug("{$filename} was last modified at {$time}"); return $time; } /** * Grabber method to return current working directory * * ```php * <?php * $pwd = $I->grabDirectory(); * ?> * ``` * * @return string */ public function grabDirectory() { $pwd = $this->_directory(); $this->debug("PWD: {$pwd}"); return $pwd; } // ----------- SERVER CONNECTION METHODS BELOW HERE -------------// /** * Open a new FTP/SFTP connection and authenticate user. * * @param string $user * @param string $password */ private function _openConnection($user = 'anonymous', $password = '') { $this->_closeConnection(); // Close connection if already open if ($this->isSFTP()) { $this->sftpConnect($user, $password); } else { $this->ftpConnect($user, $password); } $pwd = $this->grabDirectory(); $this->path = $pwd . ($pwd == '/' ? '' : DIRECTORY_SEPARATOR); } /** * Close open FTP/SFTP connection */ private function _closeConnection() { if (!$this->ftp) { return; } if (!$this->isSFTP()) { ftp_close($this->ftp); $this->ftp = null; } } /** * Get the file listing for FTP/SFTP connection * * @param String $path * @return array */ private function _listFiles($path) { if ($this->isSFTP()) { $files = @$this->ftp->nlist($path); } else { $files = @ftp_nlist($this->ftp, $path); } if ($files === false) { $this->fail("couldn't list files"); } return $files; } /** * Get the current directory for the FTP/SFTP connection * * @return string */ private function _directory() { if ($this->isSFTP()) { // == DIRECTORY_SEPARATOR ? '' : $pwd; $pwd = @$this->ftp->pwd(); } else { $pwd = @ftp_pwd($this->ftp); } if (!$pwd) { $this->fail("couldn't get current directory"); } } /** * Change the working directory on the FTP/SFTP server * * @param $path */ private function _changeDirectory($path) { if ($this->isSFTP()) { $changed = @$this->ftp->chdir($path); } else { $changed = @ftp_chdir($this->ftp, $path); } if (!$changed) { $this->fail("couldn't change directory {$path}"); } } /** * Download remote file to local tmp directory and open contents. * * @param $filename */ private function _openFile($filename) { // Check local tmp directory if (!is_dir($this->config['tmp']) || !is_writeable($this->config['tmp'])) { $this->fail('tmp directory not found or is not writable'); } // Download file to local tmp directory $tmp_file = $this->config['tmp'] . "/ftp_data_file.tmp"; if ($this->isSFTP()) { $downloaded = @$this->ftp->get($filename, $tmp_file); } else { $downloaded = @ftp_get($this->ftp, $tmp_file, $filename, FTP_BINARY); } if (!$downloaded) { $this->fail('failed to download file to tmp directory'); } // Open file content to variable if ($this->file = file_get_contents($tmp_file)) { $this->filepath = $filename; } else { $this->fail('failed to open tmp file'); } } /** * Write data to local tmp file and upload to server * * @param $filename * @param $contents */ private function _writeToFile($filename, $contents) { // Check local tmp directory if (!is_dir($this->config['tmp']) || !is_writeable($this->config['tmp'])) { $this->fail('tmp directory not found or is not writable'); } // Build temp file $tmp_file = $this->config['tmp'] . "/ftp_data_file.tmp"; file_put_contents($tmp_file, $contents); // Update variables $this->filepath = $tmp_file; $this->file = $contents; // Upload the file to server if ($this->isSFTP()) { $uploaded = @$this->ftp->put($filename, $tmp_file, NET_SFTP_LOCAL_FILE); } else { $uploaded = ftp_put($this->ftp, $filename, $tmp_file, FTP_BINARY); } if (!$uploaded) { $this->fail('failed to upload file to server'); } } /** * Make new directory on server * * @param $path */ private function makeDirectory($path) { if ($this->isSFTP()) { $created = @$this->ftp->mkdir($path, true); } else { $created = @ftp_mkdir($this->ftp, $path); } if (!$created) { $this->fail("couldn't make directory {$path}"); } $this->debug("Make directory: {$path}"); } /** * Rename/Move directory/file on server * * @param $path * @param $rename */ private function renameDirectory($path, $rename) { if ($this->isSFTP()) { $renamed = @$this->ftp->rename($path, $rename); } else { $renamed = @ftp_rename($this->ftp, $path, $rename); } if (!$renamed) { $this->fail("couldn't rename directory {$path} to {$rename}"); } $this->debug("Renamed directory: {$path} to {$rename}"); } /** * Delete file on server * * @param $filename */ private function delete($filename, $isDir = false) { if ($this->isSFTP()) { $deleted = @$this->ftp->delete($filename, $isDir); } else { $deleted = @$this->ftpDelete($filename); } if (!$deleted) { $this->fail("couldn't delete {$filename}"); } $this->debug("Deleted: {$filename}"); } /** * Function to recursively delete folder, used for PHP FTP build in client. * * @param $directory * @return bool */ private function ftpDelete($directory) { // here we attempt to delete the file/directory if (!(@ftp_rmdir($this->ftp, $directory) || @ftp_delete($this->ftp, $directory))) { // if the attempt to delete fails, get the file listing $filelist = @ftp_nlist($this->ftp, $directory); // loop through the file list and recursively delete the FILE in the list foreach ($filelist as $file) { $this->ftpDelete($file); } // if the file list is empty, delete the DIRECTORY we passed $this->ftpDelete($directory); } return true; } /** * Clear directory on server of all content * * @param $path */ private function clearDirectory($path) { $this->debug("Clear directory: {$path}"); $this->delete($path); $this->makeDirectory($path); } /** * Return the size of a given file * * @param $filename * @return bool */ private function size($filename) { if ($this->isSFTP()) { $size = (int)@$this->ftp->size($filename); } else { $size = @ftp_size($this->ftp, $filename); } if ($size > 0) { return $size; } $this->fail("couldn't get the file size for {$filename}"); } /** * Return the last modified time of a given file * * @param $filename * @return bool */ private function modified($filename) { if ($this->isSFTP()) { $info = @$this->ftp->lstat($filename); if ($info) { return $info['mtime']; } } else { if ($time = @ftp_mdtm($this->ftp, $filename)) { return $time; } } $this->fail("couldn't get the file size for {$filename}"); } /** * @param $user * @param $password */ protected function sftpConnect($user, $password) { $this->ftp = new \Net_SFTP($this->config['host'], $this->config['port'], $this->config['timeout']); if ($this->ftp === false) { $this->ftp = null; $this->fail('failed to connect to ftp server'); } if (isset($this->config['key'])) { $keyFile = file_get_contents($this->config['key']); $password = new \Crypt_RSA(); $password->loadKey($keyFile); } if (!$this->ftp->login($user, $password)) { $this->fail('failed to authenticate user'); } } /** * @param $user * @param $password */ protected function ftpConnect($user, $password) { $this->ftp = ftp_connect($this->config['host'], $this->config['port'], $this->config['timeout']); if ($this->ftp === false) { $this->ftp = null; $this->fail('failed to connect to ftp server'); } // Login using given access details if (!@ftp_login($this->ftp, $user, $password)) { $this->fail('failed to authenticate user'); } // Set passive mode option (ftp only option) if (isset($this->config['passive'])) { ftp_pasv($this->ftp, $this->config['passive']); } } protected function isSFTP() { return strtolower($this->config['type']) == 'sftp'; } } <?php namespace Codeception\Module; use Codeception\Lib\Interfaces\API; use Codeception\Module as CodeceptionModule; use Codeception\Lib\Framework; use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleRequireException; use Codeception\TestInterface; /** * Module for testing XMLRPC WebService. * * This module can be used either with frameworks or PHPBrowser. * It tries to guess the framework is is attached to. * * Whether framework is used it operates via standard framework modules. * Otherwise sends raw HTTP requests to url via PHPBrowser. * * ## Requirements * * * Module requires installed php_xmlrpc extension * * ## Status * * * Maintainer: **tiger-seo** * * Stability: **beta** * * Contact: tiger.seo@gmail.com * * ## Configuration * * * url *optional* - the url of api * * ## Public Properties * * * headers - array of headers going to be sent. * * params - array of sent data * * response - last response (string) * * @since 1.1.5 * @author tiger.seo@gmail.com */ class XMLRPC extends CodeceptionModule implements API { protected $config = ['url' => ""]; /** * @var \Symfony\Component\BrowserKit\Client */ public $client = null; public $is_functional = false; public $headers = []; public $params = []; public $response = ""; public function _initialize() { if (!function_exists('xmlrpc_encode_request')) { throw new ModuleRequireException(__CLASS__, "XMLRPC module requires installed php_xmlrpc extension"); } parent::_initialize(); } public function _before(TestInterface $test) { if (!$this->client) { if (!strpos($this->config['url'], '://')) { // not valid url foreach ($this->getModules() as $module) { if ($module instanceof Framework) { $this->client = $module->client; $this->is_functional = true; break; } } } else { if (!$this->hasModule('PhpBrowser')) { throw new ModuleConfigException( __CLASS__, "For XMLRPC testing via HTTP please enable PhpBrowser module" ); } $this->client = $this->getModule('PhpBrowser')->client; } if (!$this->client) { throw new ModuleConfigException( __CLASS__, "Client for XMLRPC requests not initialized.\n" . "Provide either PhpBrowser module, or a framework module which shares FrameworkInterface" ); } } $this->headers = []; $this->params = []; $this->response = ''; $this->client->setServerParameters([]); } /** * Sets HTTP header * * @param string $name * @param string $value */ public function haveHttpHeader($name, $value) { $this->headers[$name] = $value; } /** * Checks response code. * * @param $num */ public function seeResponseCodeIs($num) { \PHPUnit_Framework_Assert::assertEquals($num, $this->client->getInternalResponse()->getStatus()); } /** * Checks weather last response was valid XMLRPC. * This is done with xmlrpc_decode function. * */ public function seeResponseIsXMLRPC() { $result = xmlrpc_decode($this->response); \PHPUnit_Framework_Assert::assertNotNull($result, 'Invalid response document returned from XmlRpc server'); } /** * Sends a XMLRPC method call to remote XMLRPC-server. * * @param string $methodName * @param array $parameters */ public function sendXMLRPCMethodCall($methodName, $parameters = []) { if (!array_key_exists('Content-Type', $this->headers)) { $this->headers['Content-Type'] = 'text/xml'; } foreach ($this->headers as $header => $val) { $this->client->setServerParameter("HTTP_$header", $val); } $url = $this->config['url']; if (is_array($parameters)) { $parameters = $this->scalarizeArray($parameters); } $requestBody = xmlrpc_encode_request($methodName, array_values($parameters)); $this->debugSection('Request', $url . PHP_EOL . $requestBody); $this->client->request('POST', $url, [], [], [], $requestBody); $this->response = $this->client->getInternalResponse()->getContent(); $this->debugSection('Response', $this->response); } } <?php namespace Codeception\Module; use Codeception\Configuration; use Codeception\Lib\Framework; use Codeception\Exception\ModuleRequireException; use Codeception\Lib\Connector\Symfony as SymfonyConnector; use Codeception\Lib\Interfaces\DoctrineProvider; use Codeception\Lib\Interfaces\PartedModule; use Symfony\Component\Finder\Finder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\VarDumper\Cloner\Data; /** * This module uses Symfony Crawler and HttpKernel to emulate requests and test response. * * ## Demo Project * * <https://github.com/Codeception/symfony-demo> * * ## Status * * * Maintainer: **raistlin** * * Stability: **stable** * * ## Config * * ### Symfony 2.x * * * app_path: 'app' - specify custom path to your app dir, where bootstrap cache and kernel interface is located. * * environment: 'local' - environment used for load kernel * * debug: true - turn on/off debug mode * * em_service: 'doctrine.orm.entity_manager' - use the stated EntityManager to pair with Doctrine Module. * * cache_router: 'false' - enable router caching between tests in order to [increase performance](http://lakion.com/blog/how-did-we-speed-up-sylius-behat-suite-with-blackfire) * * rebootable_client: 'true' - reboot client's kernel before each request * * ### Example (`functional.suite.yml`) - Symfony 2.x Directory Structure * * ``` * modules: * - Symfony: * app_path: 'app/front' * environment: 'local_test' * ``` * * ### Symfony 3.x Directory Structure * * * app_path: 'app' - specify custom path to your app dir, where the kernel interface is located. * * var_path: 'var' - specify custom path to your var dir, where bootstrap cache is located. * * environment: 'local' - environment used for load kernel * * em_service: 'doctrine.orm.entity_manager' - use the stated EntityManager to pair with Doctrine Module. * * debug: true - turn on/off debug mode * * cache_router: 'false' - enable router caching between tests in order to [increase performance](http://lakion.com/blog/how-did-we-speed-up-sylius-behat-suite-with-blackfire) * * rebootable_client: 'true' - reboot client's kernel before each request * * ### Example (`functional.suite.yml`) - Symfony 3 Directory Structure * * modules: * enabled: * - Symfony: * app_path: 'app/front' * var_path: 'var' * environment: 'local_test' * * * ## Public Properties * * * kernel - HttpKernel instance * * client - current Crawler instance * * ## Parts * * * services - allows to use Symfony DIC only with WebDriver or PhpBrowser modules. * * Usage example: * * ```yaml * actor: AcceptanceTester * modules: * enabled: * - Symfony: * part: SERVICES * - Doctrine2: * depends: Symfony * - WebDriver: * url: http://your-url.com * browser: phantomjs * ``` * */ class Symfony extends Framework implements DoctrineProvider, PartedModule { /** * @var \Symfony\Component\HttpKernel\Kernel */ public $kernel; public $config = [ 'app_path' => 'app', 'var_path' => 'app', 'environment' => 'test', 'debug' => true, 'cache_router' => false, 'em_service' => 'doctrine.orm.entity_manager', 'rebootable_client' => true, ]; /** * @return array */ public function _parts() { return ['services']; } /** * @var */ protected $kernelClass; /** * Services that should be persistent permanently for all tests * * @var array */ protected $permanentServices = []; /** * Services that should be persistent during test execution between kernel reboots * * @var array */ protected $persistentServices = []; public function _initialize() { $this->initializeSymfonyCache(); $this->kernelClass = $this->getKernelClass(); $maxNestingLevel = 200; // Symfony may have very long nesting level $xdebugMaxLevelKey = 'xdebug.max_nesting_level'; if (ini_get($xdebugMaxLevelKey) < $maxNestingLevel) { ini_set($xdebugMaxLevelKey, $maxNestingLevel); } $this->kernel = new $this->kernelClass($this->config['environment'], $this->config['debug']); $this->kernel->boot(); if ($this->config['cache_router'] === true) { $this->persistService('router', true); } } /** * Require Symfonys bootstrap.php.cache only for PHP Version < 7 * * @throws ModuleRequireException */ private function initializeSymfonyCache() { $cache = Configuration::projectDir() . $this->config['var_path'] . DIRECTORY_SEPARATOR . 'bootstrap.php.cache'; if (PHP_VERSION_ID < 70000 && !file_exists($cache)) { throw new ModuleRequireException( __CLASS__, "Symfony bootstrap file not found in $cache\n \n" . "Please specify path to bootstrap file using `var_path` config option\n \n" . "If you are trying to load bootstrap from a Bundle provide path like:\n \n" . "modules:\n enabled:\n" . " - Symfony:\n" . " var_path: '../../app'\n" . " app_path: '../../app'" ); } if (file_exists($cache)) { require_once $cache; } } /** * Initialize new client instance before each test */ public function _before(\Codeception\TestInterface $test) { $this->persistentServices = array_merge($this->persistentServices, $this->permanentServices); $this->client = new SymfonyConnector($this->kernel, $this->persistentServices, $this->config['rebootable_client']); } /** * Update permanent services after each test */ public function _after(\Codeception\TestInterface $test) { foreach ($this->permanentServices as $serviceName => $service) { $this->permanentServices[$serviceName] = $this->grabService($serviceName); } parent::_after($test); } /** * Retrieve Entity Manager. * * EM service is retrieved once and then that instance returned on each call */ public function _getEntityManager() { if ($this->kernel === null) { $this->fail('Symfony2 platform module is not loaded'); } if (!isset($this->permanentServices[$this->config['em_service']])) { // try to persist configured EM $this->persistService($this->config['em_service'], true); if ($this->_getContainer()->has('doctrine')) { $this->persistService('doctrine', true); } if ($this->_getContainer()->has('doctrine.orm.default_entity_manager')) { $this->persistService('doctrine.orm.default_entity_manager', true); } if ($this->_getContainer()->has('doctrine.dbal.backend_connection')) { $this->persistService('doctrine.dbal.backend_connection', true); } } return $this->permanentServices[$this->config['em_service']]; } /** * Return container. * * @return ContainerInterface */ public function _getContainer() { return $this->kernel->getContainer(); } /** * Attempts to guess the kernel location. * * When the Kernel is located, the file is required. * * @return string The Kernel class name */ protected function getKernelClass() { $path = \Codeception\Configuration::projectDir() . $this->config['app_path']; if (!file_exists(\Codeception\Configuration::projectDir() . $this->config['app_path'])) { throw new ModuleRequireException( __CLASS__, "Can't load Kernel from $path.\n" . "Directory does not exists. Use `app_path` parameter to provide valid application path" ); } $finder = new Finder(); $finder->name('*Kernel.php')->depth('0')->in($path); $results = iterator_to_array($finder); if (!count($results)) { throw new ModuleRequireException( __CLASS__, "AppKernel was not found at $path. " . "Specify directory where Kernel class for your application is located with `app_path` parameter." ); } $file = current($results); $class = $file->getBasename('.php'); require_once $file; return $class; } /** * Get service $serviceName and add it to the lists of persistent services. * If $isPermanent then service becomes persistent between tests * * @param string $serviceName * @param boolean $isPermanent */ public function persistService($serviceName, $isPermanent = false) { $service = $this->grabService($serviceName); $this->persistentServices[$serviceName] = $service; if ($isPermanent) { $this->permanentServices[$serviceName] = $service; } if ($this->client) { $this->client->persistentServices[$serviceName] = $service; } } /** * Remove service $serviceName from the lists of persistent services. * * @param string $serviceName */ public function unpersistService($serviceName) { if (isset($this->persistentServices[$serviceName])) { unset($this->persistentServices[$serviceName]); } if (isset($this->permanentServices[$serviceName])) { unset($this->permanentServices[$serviceName]); } if ($this->client && isset($this->client->persistentServices[$serviceName])) { unset($this->client->persistentServices[$serviceName]); } } /** * Invalidate previously cached routes. */ public function invalidateCachedRouter() { $this->unpersistService('router'); } /** * Opens web page using route name and parameters. * * ``` php * <?php * $I->amOnRoute('posts.create'); * $I->amOnRoute('posts.show', array('id' => 34)); * ?> * ``` * * @param $routeName * @param array $params */ public function amOnRoute($routeName, array $params = []) { $router = $this->grabService('router'); if (!$router->getRouteCollection()->get($routeName)) { $this->fail(sprintf('Route with name "%s" does not exists.', $routeName)); } $url = $router->generate($routeName, $params); $this->amOnPage($url); } /** * Checks that current url matches route. * * ``` php * <?php * $I->seeCurrentRouteIs('posts.index'); * $I->seeCurrentRouteIs('posts.show', array('id' => 8)); * ?> * ``` * * @param $routeName * @param array $params */ public function seeCurrentRouteIs($routeName, array $params = []) { $router = $this->grabService('router'); if (!$router->getRouteCollection()->get($routeName)) { $this->fail(sprintf('Route with name "%s" does not exists.', $routeName)); } $uri = explode('?', $this->grabFromCurrentUrl())[0]; try { $match = $router->match($uri); } catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) { $this->fail(sprintf('The "%s" url does not match with any route', $uri)); } $expected = array_merge(array('_route' => $routeName), $params); $intersection = array_intersect_assoc($expected, $match); $this->assertEquals($expected, $intersection); } /** * Checks that current url matches route. * Unlike seeCurrentRouteIs, this can matches without exact route parameters * * ``` php * <?php * $I->seeCurrentRouteMatches('my_blog_pages'); * ?> * ``` * * @param $routeName */ public function seeInCurrentRoute($routeName) { $router = $this->grabService('router'); if (!$router->getRouteCollection()->get($routeName)) { $this->fail(sprintf('Route with name "%s" does not exists.', $routeName)); } $uri = explode('?', $this->grabFromCurrentUrl())[0]; try { $matchedRouteName = $router->match($uri)['_route']; } catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) { $this->fail(sprintf('The "%s" url does not match with any route', $uri)); } $this->assertEquals($matchedRouteName, $routeName); } /** * Checks if any email were sent by last request * * @throws \LogicException */ public function seeEmailIsSent() { $profile = $this->getProfile(); if (!$profile) { $this->fail('Emails can\'t be tested without Profiler'); } if (!$profile->hasCollector('swiftmailer')) { $this->fail('Emails can\'t be tested without SwiftMailer connector'); } $this->assertGreaterThan(0, $profile->getCollector('swiftmailer')->getMessageCount()); } /** * Grabs a service from Symfony DIC container. * Recommended to use for unit testing. * * ``` php * <?php * $em = $I->grabServiceFromContainer('doctrine'); * ?> * ``` * * @param $service * @return mixed * @part services * @deprecated Use grabService instead */ public function grabServiceFromContainer($service) { return $this->grabService($service); } /** * Grabs a service from Symfony DIC container. * Recommended to use for unit testing. * * ``` php * <?php * $em = $I->grabService('doctrine'); * ?> * ``` * * @param $service * @return mixed * @part services */ public function grabService($service) { $container = $this->_getContainer(); if (!$container->has($service)) { $this->fail("Service $service is not available in container"); } return $container->get($service); } /** * @return \Symfony\Component\HttpKernel\Profiler\Profile */ protected function getProfile() { $container = $this->_getContainer(); if (!$container->has('profiler')) { return null; } $profiler = $this->grabService('profiler'); $response = $this->client->getResponse(); if (null === $response) { $this->fail("You must perform a request before using this method."); } return $profiler->loadProfileFromResponse($response); } /** * @param $url */ protected function debugResponse($url) { parent::debugResponse($url); if ($profile = $this->getProfile()) { if ($profile->hasCollector('security')) { if ($profile->getCollector('security')->isAuthenticated()) { $roles = $profile->getCollector('security')->getRoles(); if ($roles instanceof Data) { $roles = $this->extractRawRoles($roles); } $this->debugSection( 'User', $profile->getCollector('security')->getUser() . ' [' . implode(',', $roles) . ']' ); } else { $this->debugSection('User', 'Anonymous'); } } if ($profile->hasCollector('swiftmailer')) { $messages = $profile->getCollector('swiftmailer')->getMessageCount(); if ($messages) { $this->debugSection('Emails', $messages . ' sent'); } } if ($profile->hasCollector('timer')) { $this->debugSection('Time', $profile->getCollector('timer')->getTime()); } } } /** * @param Data $data * @return array */ private function extractRawRoles(Data $data) { $raw = $data->getRawData(); return isset($raw[1]) ? $raw[1] : []; } /** * Returns a list of recognized domain names. * * @return array */ protected function getInternalDomains() { $internalDomains = []; $routes = $this->grabService('router')->getRouteCollection(); /* @var \Symfony\Component\Routing\Route $route */ foreach ($routes as $route) { if (!is_null($route->getHost())) { $compiled = $route->compile(); if (!is_null($compiled->getHostRegex())) { $internalDomains[] = $compiled->getHostRegex(); } } } return array_unique($internalDomains); } /** * Reboot client's kernel. * Can be used to manually reboot kernel when 'rebootable_client' => false * * ``` php * <?php * ... * perform some requests * ... * $I->rebootClientKernel(); * ... * perform other requests * ... * * ?> * ``` * */ public function rebootClientKernel() { if ($this->client) { $this->client->rebootKernel(); } } } <?php namespace Codeception\Module; use Codeception\Module as CodeceptionModule; use Codeception\TestInterface; use Codeception\Exception\ModuleConfigException; /** * Connects to [memcached](http://www.memcached.org/) using either _Memcache_ or _Memcached_ extension. * * Performs a cleanup by flushing all values after each test run. * * ## Status * * * Maintainer: **davert** * * Stability: **beta** * * Contact: davert@codeception.com * * ## Configuration * * * **`host`** (`string`, default `'localhost'`) - The memcached host * * **`port`** (`int`, default `11211`) - The memcached port * * ### Example (`unit.suite.yml`) * * ```yaml * modules: * - Memcache: * host: 'localhost' * port: 11211 * ``` * * Be sure you don't use the production server to connect. * * ## Public Properties * * * **memcache** - instance of _Memcache_ or _Memcached_ object * */ class Memcache extends CodeceptionModule { /** * @var \Memcache|\Memcached */ public $memcache = null; /** * {@inheritdoc} */ protected $config = [ 'host' => 'localhost', 'port' => 11211 ]; /** * Code to run before each test. * * @param TestInterface $test * @throws ModuleConfigException */ public function _before(TestInterface $test) { if (class_exists('\Memcache')) { $this->memcache = new \Memcache; $this->memcache->connect($this->config['host'], $this->config['port']); } elseif (class_exists('\Memcached')) { $this->memcache = new \Memcached; $this->memcache->addServer($this->config['host'], $this->config['port']); } else { throw new ModuleConfigException(__CLASS__, 'Memcache classes not loaded'); } } /** * Code to run after each test. * * @param TestInterface $test */ public function _after(TestInterface $test) { if (empty($this->memcache)) { return; } $this->memcache->flush(); switch (get_class($this->memcache)) { case 'Memcache': $this->memcache->close(); break; case 'Memcached': $this->memcache->quit(); break; } } /** * Grabs value from memcached by key. * * Example: * * ``` php * <?php * $users_count = $I->grabValueFromMemcached('users_count'); * ?> * ``` * * @param $key * @return array|string */ public function grabValueFromMemcached($key) { $value = $this->memcache->get($key); $this->debugSection("Value", $value); return $value; } /** * Checks item in Memcached exists and the same as expected. * * Examples: * * ``` php * <?php * // With only one argument, only checks the key exists * $I->seeInMemcached('users_count'); * * // Checks a 'users_count' exists and has the value 200 * $I->seeInMemcached('users_count', 200); * ?> * ``` * * @param $key * @param $value */ public function seeInMemcached($key, $value = null) { $actual = $this->memcache->get($key); $this->debugSection("Value", $actual); if (null === $value) { $this->assertTrue(false !== $actual, "Cannot find key '$key' in Memcached"); } else { $this->assertEquals($value, $actual, "Cannot find key '$key' in Memcached with the provided value"); } } /** * Checks item in Memcached doesn't exist or is the same as expected. * * Examples: * * ``` php * <?php * // With only one argument, only checks the key does not exist * $I->dontSeeInMemcached('users_count'); * * // Checks a 'users_count' exists does not exist or its value is not the one provided * $I->dontSeeInMemcached('users_count', 200); * ?> * ``` * * @param $key * @param $value */ public function dontSeeInMemcached($key, $value = null) { $actual = $this->memcache->get($key); $this->debugSection("Value", $actual); if (null === $value) { $this->assertTrue(false === $actual, "The key '$key' exists in Memcached"); } else { if (false !== $actual) { $this->assertEquals($value, $actual, "The key '$key' exists in Memcached with the provided value"); } } } /** * Stores an item `$value` with `$key` on the Memcached server. * * @param string $key * @param mixed $value * @param int $expiration */ public function haveInMemcached($key, $value, $expiration = null) { switch (get_class($this->memcache)) { case 'Memcache': $this->assertTrue($this->memcache->set($key, $value, null, $expiration)); break; case 'Memcached': $this->assertTrue($this->memcache->set($key, $value, $expiration)); break; } } /** * Flushes all Memcached data. */ public function clearMemcache() { $this->memcache->flush(); } } <?php namespace Codeception\Module; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\Module as CodeceptionModule; use Codeception\Configuration as Configuration; use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleException; use Codeception\Lib\Driver\MongoDb as MongoDbDriver; use Codeception\TestInterface; /** * Works with MongoDb database. * * The most important function of this module is cleaning database before each test. * To have your database properly cleaned you should configure it to access the database. * * In order to have your database populated with data you need a valid js file with data (of the same style which can be fed up to mongo binary) * File can be generated by RockMongo export command * You can also use directoy, generated by ```mongodump``` tool or it's ```.tar.gz``` archive (not available for Windows systems), generated by ```tar -czf <archve_file_name>.tar.gz <path_to dump directory>```. * Just put it in ``` tests/_data ``` dir (by default) and specify path to it in config. * Next time after database is cleared all your data will be restored from dump. * The DB preparation should as following: * - clean database * - system collection system.users should contain the user which will be authenticated while script performs DB operations * * Connection is done by MongoDb driver, which is stored in Codeception\Lib\Driver namespace. * Check out the driver if you get problems loading dumps and cleaning databases. * * HINT: This module can be used with [Mongofill](https://github.com/mongofill/mongofill) library which is Mongo client written in PHP without extension. * * ## Status * * * Maintainer: **judgedim**, **davert** * * Stability: **beta** * * Contact: davert@codeception.com * * *Please review the code of non-stable modules and provide patches if you have issues.* * * ## Config * * * dsn *required* - MongoDb DSN with the db name specified at the end of the host after slash * * user *required* - user to access database * * password *required* - password * * dump_type *required* - type of dump. * One of 'js' (MongoDb::DUMP_TYPE_JS), 'mongodump' (MongoDb::DUMP_TYPE_MONGODUMP) or 'mongodump-tar-gz' (MongoDb::DUMP_TYPE_MONGODUMP_TAR_GZ). * default: MongoDb::DUMP_TYPE_JS). * * dump - path to database dump * * populate: true - should the dump be loaded before test suite is started. * * cleanup: true - should the dump be reloaded after each test * */ class MongoDb extends CodeceptionModule implements RequiresPackage { const DUMP_TYPE_JS = 'js'; const DUMP_TYPE_MONGODUMP = 'mongodump'; const DUMP_TYPE_MONGODUMP_TAR_GZ = 'mongodump-tar-gz'; /** * @api * @var */ public $dbh; /** * @var */ protected $dumpFile; protected $isDumpFileEmpty = true; protected $config = [ 'populate' => true, 'cleanup' => true, 'dump' => null, 'dump_type' => self::DUMP_TYPE_JS, 'user' => null, 'password' => null, 'quiet' => false, ]; protected $populated = false; /** * @var \Codeception\Lib\Driver\MongoDb */ public $driver; protected $requiredFields = ['dsn']; public function _initialize() { try { $this->driver = MongoDbDriver::create( $this->config['dsn'], $this->config['user'], $this->config['password'] ); } catch (\MongoConnectionException $e) { throw new ModuleException(__CLASS__, $e->getMessage() . ' while creating Mongo connection'); } // starting with loading dump if ($this->config['populate']) { $this->cleanup(); $this->loadDump(); $this->populated = true; } } private function validateDump() { if ($this->config['dump'] && ($this->config['cleanup'] or ($this->config['populate']))) { if (!file_exists(Configuration::projectDir() . $this->config['dump'])) { throw new ModuleConfigException( __CLASS__, "File with dump doesn't exist.\n Please, check path for dump file: " . $this->config['dump'] ); } $this->dumpFile = Configuration::projectDir() . $this->config['dump']; $this->isDumpFileEmpty = false; if ($this->config['dump_type'] === self::DUMP_TYPE_JS) { $content = file_get_contents($this->dumpFile); $content = trim(preg_replace('%/\*(?:(?!\*/).)*\*/%s', "", $content)); if (!sizeof(explode("\n", $content))) { $this->isDumpFileEmpty = true; } return; } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP) { if (!is_dir($this->dumpFile)) { throw new ModuleConfigException( __CLASS__, "Dump must be a directory.\n Please, check dump: " . $this->config['dump'] ); } $this->isDumpFileEmpty = true; $dumpDir = dir($this->dumpFile); while (false !== ($entry = $dumpDir->read())) { if ($entry !== '..' && $entry !== '.') { $this->isDumpFileEmpty = false; break; } } $dumpDir->close(); return; } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP_TAR_GZ) { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { throw new ModuleConfigException( __CLASS__, "Tar gunzip archives are not supported for Windows systems" ); } if (strlen($this->dumpFile) <= 7 || substr($this->dumpFile, -7) !== '.tar.gz') { throw new ModuleConfigException( __CLASS__, "Dump file must be a valid tar gunzip archive.\n Please, check dump file: " . $this->config['dump'] ); } return; } throw new ModuleConfigException( __CLASS__, '\"dump_type\" must be one of ["' . self::DUMP_TYPE_JS . '", "' . self::DUMP_TYPE_MONGODUMP . '", "' . self::DUMP_TYPE_MONGODUMP_TAR_GZ . '"].' ); } } public function _before(TestInterface $test) { if ($this->config['cleanup'] && !$this->populated) { $this->cleanup(); $this->loadDump(); } } public function _after(TestInterface $test) { $this->populated = false; } protected function cleanup() { $dbh = $this->driver->getDbh(); if (!$dbh) { throw new ModuleConfigException( __CLASS__, "No connection to database. Remove this module from config if you don't need database repopulation" ); } try { $this->driver->cleanup(); } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } } protected function loadDump() { $this->validateDump(); if ($this->isDumpFileEmpty) { return; } try { if ($this->config['dump_type'] === self::DUMP_TYPE_JS) { $this->driver->load($this->dumpFile); } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP) { $this->driver->setQuiet($this->config['quiet']); $this->driver->loadFromMongoDump($this->dumpFile); } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP_TAR_GZ) { $this->driver->setQuiet($this->config['quiet']); $this->driver->loadFromTarGzMongoDump($this->dumpFile); } } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } } /** * Specify the database to use * * ``` php * <?php * $I->useDatabase('db_1'); * ``` * * @param $dbName */ public function useDatabase($dbName) { $this->driver->setDatabase($dbName); } /** * Inserts data into collection * * ``` php * <?php * $I->haveInCollection('users', array('name' => 'John', 'email' => 'john@coltrane.com')); * $user_id = $I->haveInCollection('users', array('email' => 'john@coltrane.com')); * ``` * * @param $collection * @param array $data */ public function haveInCollection($collection, array $data) { $collection = $this->driver->getDbh()->selectCollection($collection); if ($this->driver->isLegacy()) { $collection->insert($data); return $data['_id']; } else { $response = $collection->insertOne($data); return $response->getInsertedId()->__toString(); } } /** * Checks if collection contains an item. * * ``` php * <?php * $I->seeInCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria */ public function seeInCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit_Framework_Assert::assertGreaterThan(0, $res); } /** * Checks if collection doesn't contain an item. * * ``` php * <?php * $I->dontSeeInCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria */ public function dontSeeInCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit_Framework_Assert::assertLessThan(1, $res); } /** * Grabs a data from collection * * ``` php * <?php * $user = $I->grabFromCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria * @return array */ public function grabFromCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); return $collection->findOne($criteria); } /** * Grabs the documents count from a collection * * ``` php * <?php * $count = $I->grabCollectionCount('users'); * // or * $count = $I->grabCollectionCount('users', array('isAdmin' => true)); * ``` * * @param $collection * @param array $criteria * @return integer */ public function grabCollectionCount($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); return $collection->count($criteria); } /** * Asserts that an element in a collection exists and is an Array * * ``` php * <?php * $I->seeElementIsArray('users', array('name' => 'John Doe') , 'data.skills'); * ``` * * @param String $collection * @param Array $criteria * @param String $elementToCheck */ public function seeElementIsArray($collection, $criteria = [], $elementToCheck = null) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count( array_merge( $criteria, [ $elementToCheck => ['$exists' => true], '$where' => "Array.isArray(this.{$elementToCheck})" ] ) ); if ($res > 1) { throw new \PHPUnit_Framework_ExpectationFailedException( 'Error: you should test against a single element criteria when asserting that elementIsArray' ); } \PHPUnit_Framework_Assert::assertEquals(1, $res, 'Specified element is not a Mongo Object'); } /** * Asserts that an element in a collection exists and is an Object * * ``` php * <?php * $I->seeElementIsObject('users', array('name' => 'John Doe') , 'data'); * ``` * * @param String $collection * @param Array $criteria * @param String $elementToCheck */ public function seeElementIsObject($collection, $criteria = [], $elementToCheck = null) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count( array_merge( $criteria, [ $elementToCheck => ['$exists' => true], '$where' => "! Array.isArray(this.{$elementToCheck}) && isObject(this.{$elementToCheck})" ] ) ); if ($res > 1) { throw new \PHPUnit_Framework_ExpectationFailedException( 'Error: you should test against a single element criteria when asserting that elementIsObject' ); } \PHPUnit_Framework_Assert::assertEquals(1, $res, 'Specified element is not a Mongo Object'); } /** * Count number of records in a collection * * ``` php * <?php * $I->seeNumElementsInCollection('users', 2); * $I->seeNumElementsInCollection('users', 1, array('name' => 'miles')); * ``` * * @param $collection * @param integer $expected * @param array $criteria */ public function seeNumElementsInCollection($collection, $expected, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit_Framework_Assert::assertSame($expected, $res); } /** * Returns list of classes and corresponding packages required for this module */ public function _requires() { return ['MongoDB\Client' => '"mongodb/mongodb": "^1.0"']; } } <?php namespace Codeception\Module; use Codeception\Lib\Framework; use Codeception\TestInterface; use Codeception\Configuration; use Codeception\Lib\Interfaces\DoctrineProvider; use Codeception\Lib\Interfaces\PartedModule; use Codeception\Util\ReflectionHelper; use Zend\Console\Console; use Zend\EventManager\StaticEventManager; use Codeception\Lib\Connector\ZF2 as ZF2Connector; /** * This module allows you to run tests inside Zend Framework 2 and Zend Framework 3. * * File `init_autoloader` in project's root is required by Zend Framework 2. * Uses `tests/application.config.php` config file by default. * * Note: services part and Doctrine integration is not compatible with ZF3 yet * * ## Status * * * Maintainer: **Naktibalda** * * Stability: **stable** * * ## Config * * * config: relative path to config file (default: `tests/application.config.php`) * * ## Public Properties * * * application - instance of `\Zend\Mvc\ApplicationInterface` * * db - instance of `\Zend\Db\Adapter\AdapterInterface` * * client - BrowserKit client * * ## Parts * * * services - allows to use grabServiceFromContainer and addServiceToContainer with WebDriver or PhpBrowser modules. * * Usage example: * * ```yaml * actor: AcceptanceTester * modules: * enabled: * - ZF2: * part: services * - Doctrine2: * depends: ZF2 * - WebDriver: * url: http://your-url.com * browser: phantomjs * ``` */ class ZF2 extends Framework implements DoctrineProvider, PartedModule { protected $config = [ 'config' => 'tests/application.config.php', ]; /** * @var \Zend\Mvc\ApplicationInterface */ public $application; /** * @var \Zend\Db\Adapter\AdapterInterface */ public $db; /** * @var \Codeception\Lib\Connector\ZF2 */ public $client; protected $applicationConfig; protected $queries = 0; protected $time = 0; /** * @var array Used to collect domains while recusively traversing route tree */ private $domainCollector = []; public function _initialize() { $initAutoloaderFile = Configuration::projectDir() . 'init_autoloader.php'; if (file_exists($initAutoloaderFile)) { require $initAutoloaderFile; } $this->applicationConfig = require Configuration::projectDir() . $this->config['config']; if (isset($this->applicationConfig['module_listener_options']['config_cache_enabled'])) { $this->applicationConfig['module_listener_options']['config_cache_enabled'] = false; } Console::overrideIsConsole(false); //grabServiceFromContainer may need client in beforeClass hooks of modules or helpers $this->client = new ZF2Connector(); $this->client->setApplicationConfig($this->applicationConfig); } public function _before(TestInterface $test) { $this->client = new ZF2Connector(); $this->client->setApplicationConfig($this->applicationConfig); $_SERVER['REQUEST_URI'] = ''; } public function _after(TestInterface $test) { $_SESSION = []; $_GET = []; $_POST = []; $_COOKIE = []; if (class_exists('Zend\EventManager\StaticEventManager')) { // reset singleton (ZF2) StaticEventManager::resetInstance(); } $this->queries = 0; $this->time = 0; parent::_after($test); } public function _afterSuite() { unset($this->client); } public function _getEntityManager() { if (!$this->client) { $this->client = new ZF2Connector(); $this->client->setApplicationConfig($this->applicationConfig); } return $this->grabServiceFromContainer('Doctrine\ORM\EntityManager'); } /** * Grabs a service from ZF2 container. * Recommended to use for unit testing. * * ``` php * <?php * $em = $I->grabServiceFromContainer('Doctrine\ORM\EntityManager'); * ?> * ``` * * @param $service * @return mixed * @part services */ public function grabServiceFromContainer($service) { return $this->client->grabServiceFromContainer($service); } /** * Adds service to ZF2 container * @param string $name * @param object $service * @part services */ public function addServiceToContainer($name, $service) { $this->client->addServiceToContainer($name, $service); } /** * Opens web page using route name and parameters. * * ``` php * <?php * $I->amOnRoute('posts.create'); * $I->amOnRoute('posts.show', array('id' => 34)); * ?> * ``` * * @param $routeName * @param array $params */ public function amOnRoute($routeName, array $params = []) { $router = $this->client->grabServiceFromContainer('router'); $url = $router->assemble($params, ['name' => $routeName]); $this->amOnPage($url); } /** * Checks that current url matches route. * * ``` php * <?php * $I->seeCurrentRouteIs('posts.index'); * $I->seeCurrentRouteIs('posts.show', ['id' => 8])); * ?> * ``` * * @param $routeName * @param array $params */ public function seeCurrentRouteIs($routeName, array $params = []) { $router = $this->client->grabServiceFromContainer('router'); $url = $router->assemble($params, ['name' => $routeName]); $this->seeCurrentUrlEquals($url); } protected function getInternalDomains() { /** * @var Zend\Mvc\Router\Http\TreeRouteStack */ $router = $this->client->grabServiceFromContainer('router'); $this->domainCollector = []; $this->addInternalDomainsFromRoutes($router->getRoutes()); return array_unique($this->domainCollector); } private function addInternalDomainsFromRoutes($routes) { foreach ($routes as $name => $route) { if ($route instanceof \Zend\Mvc\Router\Http\Hostname || $route instanceof \Zend\Router\Http\Hostname) { $this->addInternalDomain($route); } elseif ($route instanceof \Zend\Mvc\Router\Http\Part || $route instanceof \Zend\Router\Http\Part) { $parentRoute = ReflectionHelper::readPrivateProperty($route, 'route'); if ($parentRoute instanceof \Zend\Mvc\Router\Http\Hostname || $parentRoute instanceof \Zend\Mvc\Router\Http\Hostname) { $this->addInternalDomain($parentRoute); } // this is necessary to instantiate child routes try { $route->assemble([], []); } catch (\Exception $e) { } $this->addInternalDomainsFromRoutes($route->getRoutes()); } } } private function addInternalDomain($route) { $regex = ReflectionHelper::readPrivateProperty($route, 'regex'); $this->domainCollector []= '/^' . $regex . '$/'; } public function _parts() { return ['services']; } } <?php namespace Codeception\Module; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\Module as CodeceptionModule; use Codeception\Exception\ModuleException as ModuleException; use Codeception\TestInterface; use Exception; use PhpAmqpLib\Channel\AMQPChannel; use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; use PhpAmqpLib\Exception\AMQPProtocolChannelException; /** * This module interacts with message broker software that implements * the Advanced Message Queuing Protocol (AMQP) standard. For example, RabbitMQ (tested). * * <div class="alert alert-info"> * To use this module with Composer you need <em>"php-amqplib/php-amqplib": "~2.4"</em> package. * </div> * * ## Config * * * host: localhost - host to connect * * username: guest - username to connect * * password: guest - password to connect * * vhost: '/' - vhost to connect * * cleanup: true - defined queues will be purged before running every test. * * queues: [mail, twitter] - queues to cleanup * * ### Example * * modules: * enabled: * - AMQP: * host: 'localhost' * port: '5672' * username: 'guest' * password: 'guest' * vhost: '/' * queues: [queue1, queue2] * * ## Public Properties * * * connection - AMQPStreamConnection - current connection */ class AMQP extends CodeceptionModule implements RequiresPackage { protected $config = [ 'host' => 'localhost', 'username' => 'guest', 'password' => 'guest', 'port' => '5672', 'vhost' => '/', 'cleanup' => true, ]; /** * @var AMQPStreamConnection */ public $connection; /** * @var AMQPChannel */ protected $channel; protected $requiredFields = ['host', 'username', 'password', 'vhost']; public function _requires() { return ['PhpAmqpLib\Connection\AMQPStreamConnection' => '"php-amqplib/php-amqplib": "~2.4"']; } public function _initialize() { $host = $this->config['host']; $port = $this->config['port']; $username = $this->config['username']; $password = $this->config['password']; $vhost = $this->config['vhost']; try { $this->connection = new AMQPStreamConnection($host, $port, $username, $password, $vhost); } catch (Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage() . ' while establishing connection to MQ server'); } } public function _before(TestInterface $test) { if ($this->config['cleanup']) { $this->cleanup(); } } /** * Sends message to exchange by sending exchange name, message * and (optionally) a routing key * * ``` php * <?php * $I->pushToExchange('exchange.emails', 'thanks'); * $I->pushToExchange('exchange.emails', new AMQPMessage('Thanks!')); * $I->pushToExchange('exchange.emails', new AMQPMessage('Thanks!'), 'severity'); * ?> * ``` * * @param string $exchange * @param string|AMQPMessage $message * @param string $routing_key */ public function pushToExchange($exchange, $message, $routing_key = null) { $message = $message instanceof AMQPMessage ? $message : new AMQPMessage($message); $this->connection->channel()->basic_publish($message, $exchange, $routing_key); } /** * Sends message to queue * * ``` php * <?php * $I->pushToQueue('queue.jobs', 'create user'); * $I->pushToQueue('queue.jobs', new AMQPMessage('create')); * ?> * ``` * * @param string $queue * @param string|AMQPMessage $message */ public function pushToQueue($queue, $message) { $message = $message instanceof AMQPMessage ? $message : new AMQPMessage($message); $this->connection->channel()->queue_declare($queue); $this->connection->channel()->basic_publish($message, '', $queue); } /** * Declares an exchange * * This is an alias of method `exchange_declare` of `PhpAmqpLib\Channel\AMQPChannel`. * * ```php * <?php * $I->declareExchange( * 'nameOfMyExchange', // exchange name * 'topic' // exchange type * ) * ``` * * @param string $exchange * @param string $type * @param bool $passive * @param bool $durable * @param bool $auto_delete * @param bool $internal * @param bool $nowait * @param array $arguments * @param int $ticket * @return mixed|null */ public function declareExchange( $exchange, $type, $passive = false, $durable = false, $auto_delete = true, $internal = false, $nowait = false, $arguments = null, $ticket = null ) { return $this->connection->channel()->exchange_declare( $exchange, $type, $passive, $durable, $auto_delete, $internal, $nowait, $arguments, $ticket ); } /** * Declares queue, creates if needed * * This is an alias of method `queue_declare` of `PhpAmqpLib\Channel\AMQPChannel`. * * ```php * <?php * $I->declareQueue( * 'nameOfMyQueue', // exchange name * ) * ``` * * @param string $queue * @param bool $passive * @param bool $durable * @param bool $exclusive * @param bool $auto_delete * @param bool $nowait * @param array $arguments * @param int $ticket * @return mixed|null */ public function declareQueue( $queue = '', $passive = false, $durable = false, $exclusive = false, $auto_delete = true, $nowait = false, $arguments = null, $ticket = null ) { return $this->connection->channel()->queue_declare( $queue, $passive, $durable, $exclusive, $auto_delete, $nowait, $arguments, $ticket ); } /** * Binds a queue to an exchange * * This is an alias of method `queue_bind` of `PhpAmqpLib\Channel\AMQPChannel`. * * ```php * <?php * $I->bindQueueToExchange( * 'nameOfMyQueueToBind', // name of the queue * 'transactionTracking.transaction', // exchange name to bind to * 'your.routing.key' // Optionally, provide a binding key * ) * ``` * * @param string $queue * @param string $exchange * @param string $routing_key * @param bool $nowait * @param array $arguments * @param int $ticket * @return mixed|null */ public function bindQueueToExchange( $queue, $exchange, $routing_key = '', $nowait = false, $arguments = null, $ticket = null ) { return $this->connection->channel()->queue_bind( $queue, $exchange, $routing_key, $nowait, $arguments, $ticket ); } /** * Checks if message containing text received. * * **This method drops message from queue** * **This method will wait for message. If none is sent the script will stuck**. * * ``` php * <?php * $I->pushToQueue('queue.emails', 'Hello, davert'); * $I->seeMessageInQueueContainsText('queue.emails','davert'); * ?> * ``` * * @param string $queue * @param string $text */ public function seeMessageInQueueContainsText($queue, $text) { $msg = $this->connection->channel()->basic_get($queue); if (!$msg) { $this->fail("Message was not received"); } if (!$msg instanceof AMQPMessage) { $this->fail("Received message is not format of AMQPMessage"); } $this->debugSection("Message", $msg->body); $this->assertContains($text, $msg->body); } /** * Takes last message from queue. * * ``` php * <?php * $message = $I->grabMessageFromQueue('queue.emails'); * ?> * ``` * * @param string $queue * @return AMQPMessage */ public function grabMessageFromQueue($queue) { $message = $this->connection->channel()->basic_get($queue); return $message; } /** * Purge a specific queue defined in config. * * ``` php * <?php * $I->purgeQueue('queue.emails'); * ?> * ``` * * @param string $queueName */ public function purgeQueue($queueName = '') { if (! in_array($queueName, $this->config['queues'])) { throw new ModuleException(__CLASS__, "'$queueName' doesn't exist in queues config list"); } $this->connection->channel()->queue_purge($queueName, true); } /** * Purge all queues defined in config. * * ``` php * <?php * $I->purgeAllQueues(); * ?> * ``` */ public function purgeAllQueues() { $this->cleanup(); } protected function cleanup() { if (!isset($this->config['queues'])) { throw new ModuleException(__CLASS__, "please set queues for cleanup"); } if (!$this->connection) { return; } foreach ($this->config['queues'] as $queue) { try { $this->connection->channel()->queue_purge($queue); } catch (AMQPProtocolChannelException $e) { // ignore if exchange/queue doesn't exist and rethrow exception if it's something else if ($e->getCode() !== 404) { throw $e; } } } } } <?php namespace Codeception\Module; use Codeception\Module as CodeceptionModule; /** * Special module for using asserts in your tests. */ class Asserts extends CodeceptionModule { /** * Checks that two variables are equal. If you're comparing floating-point values, * you can specify the optional "delta" parameter which dictates how great of a precision * error are you willing to tolerate in order to consider the two values equal. * * Regular example: * ```php * <?php * $I->assertEquals($element->getChildrenCount(), 5); * ``` * * Floating-point example: * ```php * <?php * $I->assertEquals($calculator->add(0.1, 0.2), 0.3, 'Calculator should add the two numbers correctly.', 0.01); * ``` * * @param $expected * @param $actual * @param string $message * @param float $delta */ public function assertEquals($expected, $actual, $message = '', $delta = 0.0) { parent::assertEquals($expected, $actual, $message, $delta); } /** * Checks that two variables are not equal. If you're comparing floating-point values, * you can specify the optional "delta" parameter which dictates how great of a precision * error are you willing to tolerate in order to consider the two values not equal. * * Regular example: * ```php * <?php * $I->assertNotEquals($element->getChildrenCount(), 0); * ``` * * Floating-point example: * ```php * <?php * $I->assertNotEquals($calculator->add(0.1, 0.2), 0.4, 'Calculator should add the two numbers correctly.', 0.01); * ``` * * @param $expected * @param $actual * @param string $message * @param float $delta */ public function assertNotEquals($expected, $actual, $message = '', $delta = 0.0) { parent::assertNotEquals($expected, $actual, $message, $delta); } /** * Checks that two variables are same * * @param $expected * @param $actual * @param string $message */ public function assertSame($expected, $actual, $message = '') { parent::assertSame($expected, $actual, $message); } /** * Checks that two variables are not same * * @param $expected * @param $actual * @param string $message */ public function assertNotSame($expected, $actual, $message = '') { parent::assertNotSame($expected, $actual, $message); } /** * Checks that actual is greater than expected * * @param $expected * @param $actual * @param string $message */ public function assertGreaterThan($expected, $actual, $message = '') { parent::assertGreaterThan($expected, $actual, $message); } /** * Checks that actual is greater or equal than expected * * @param $expected * @param $actual * @param string $message */ public function assertGreaterThanOrEqual($expected, $actual, $message = '') { parent::assertGreaterThanOrEqual($expected, $actual, $message); } /** * Checks that actual is less than expected * * @param $expected * @param $actual * @param string $message */ public function assertLessThan($expected, $actual, $message = '') { parent::assertLessThan($expected, $actual, $message); } /** * Checks that actual is less or equal than expected * * @param $expected * @param $actual * @param string $message */ public function assertLessThanOrEqual($expected, $actual, $message = '') { parent::assertLessThanOrEqual($expected, $actual, $message); } /** * Checks that haystack contains needle * * @param $needle * @param $haystack * @param string $message */ public function assertContains($needle, $haystack, $message = '') { parent::assertContains($needle, $haystack, $message); } /** * Checks that haystack doesn't contain needle. * * @param $needle * @param $haystack * @param string $message */ public function assertNotContains($needle, $haystack, $message = '') { parent::assertNotContains($needle, $haystack, $message); } /** * Checks that string match with pattern * * @param string $pattern * @param string $string * @param string $message */ public function assertRegExp($pattern, $string, $message = '') { parent::assertRegExp($pattern, $string, $message); } /** * Checks that string not match with pattern * * @param string $pattern * @param string $string * @param string $message */ public function assertNotRegExp($pattern, $string, $message = '') { parent::assertNotRegExp($pattern, $string, $message); } /** * Checks that variable is empty. * * @param $actual * @param string $message */ public function assertEmpty($actual, $message = '') { parent::assertEmpty($actual, $message); } /** * Checks that variable is not empty. * * @param $actual * @param string $message */ public function assertNotEmpty($actual, $message = '') { parent::assertNotEmpty($actual, $message); } /** * Checks that variable is NULL * * @param $actual * @param string $message */ public function assertNull($actual, $message = '') { parent::assertNull($actual, $message); } /** * Checks that variable is not NULL * * @param $actual * @param string $message */ public function assertNotNull($actual, $message = '') { parent::assertNotNull($actual, $message); } /** * Checks that condition is positive. * * @param $condition * @param string $message */ public function assertTrue($condition, $message = '') { parent::assertTrue($condition, $message); } /** * Checks that condition is negative. * * @param $condition * @param string $message */ public function assertFalse($condition, $message = '') { parent::assertFalse($condition, $message); } /** * Checks if file exists * * @param string $filename * @param string $message */ public function assertFileExists($filename, $message = '') { parent::assertFileExists($filename, $message); } /** * Checks if file doesn't exist * * @param string $filename * @param string $message */ public function assertFileNotExists($filename, $message = '') { parent::assertFileNotExists($filename, $message); } /** * @param $expected * @param $actual * @param $description */ public function assertGreaterOrEquals($expected, $actual, $description = '') { $this->assertGreaterThanOrEqual($expected, $actual, $description); } /** * @param $expected * @param $actual * @param $description */ public function assertLessOrEquals($expected, $actual, $description = '') { $this->assertLessThanOrEqual($expected, $actual, $description); } /** * @param $actual * @param $description */ public function assertIsEmpty($actual, $description = '') { $this->assertEmpty($actual, $description); } /** * @param $key * @param $actual * @param $description */ public function assertArrayHasKey($key, $actual, $description = '') { parent::assertArrayHasKey($key, $actual, $description); } /** * @param $key * @param $actual * @param $description */ public function assertArrayNotHasKey($key, $actual, $description = '') { parent::assertArrayNotHasKey($key, $actual, $description); } /** * @param $expectedCount * @param $actual * @param $description */ public function assertCount($expectedCount, $actual, $description = '') { parent::assertCount($expectedCount, $actual, $description); } /** * @param $class * @param $actual * @param $description */ public function assertInstanceOf($class, $actual, $description = '') { parent::assertInstanceOf($class, $actual, $description); } /** * @param $class * @param $actual * @param $description */ public function assertNotInstanceOf($class, $actual, $description = '') { parent::assertNotInstanceOf($class, $actual, $description); } /** * @param $type * @param $actual * @param $description */ public function assertInternalType($type, $actual, $description = '') { parent::assertInternalType($type, $actual, $description); } /** * Fails the test with message. * * @param $message */ public function fail($message) { parent::fail($message); } /** * Handles and checks exception called inside callback function. * Either exception class name or exception instance should be provided. * * ```php * <?php * $I->expectException(MyException::class, function() { * $this->doSomethingBad(); * }); * * $I->expectException(new MyException(), function() { * $this->doSomethingBad(); * }); * ``` * If you want to check message or exception code, you can pass them with exception instance: * ```php * <?php * // will check that exception MyException is thrown with "Don't do bad things" message * $I->expectException(new MyException("Don't do bad things"), function() { * $this->doSomethingBad(); * }); * ``` * * @param $exception string or \Exception * @param $callback */ public function expectException($exception, $callback) { $code = null; $msg = null; if (is_object($exception)) { /** @var $exception \Exception **/ $class = get_class($exception); $msg = $exception->getMessage(); $code = $exception->getCode(); } else { $class = $exception; } try { $callback(); } catch (\Exception $e) { if (!$e instanceof $class) { $this->fail(sprintf("Exception of class $class expected to be thrown, but %s caught", get_class($e))); } if (null !== $msg and $e->getMessage() !== $msg) { $this->fail(sprintf( "Exception of $class expected to be '$msg', but actual message was '%s'", $e->getMessage() )); } if (null !== $code and $e->getCode() !== $code) { $this->fail(sprintf( "Exception of $class expected to have code $code, but actual code was %s", $e->getCode() )); } $this->assertTrue(true); // increment assertion counter return; } $this->fail("Expected exception to be thrown, but nothing was caught"); } } <?php namespace Codeception\Module; use Codeception\Coverage\Subscriber\LocalServer; use Codeception\Exception\ConnectionException; use Codeception\Exception\ElementNotFound; use Codeception\Exception\MalformedLocatorException; use Codeception\Exception\ModuleConfigException as ModuleConfigException; use Codeception\Exception\ModuleException; use Codeception\Exception\TestRuntimeException; use Codeception\Lib\Interfaces\ConflictsWithModule; use Codeception\Lib\Interfaces\ElementLocator; use Codeception\Lib\Interfaces\MultiSession as MultiSessionInterface; use Codeception\Lib\Interfaces\PageSourceSaver; use Codeception\Lib\Interfaces\Remote as RemoteInterface; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\Lib\Interfaces\ScreenshotSaver; use Codeception\Lib\Interfaces\SessionSnapshot; use Codeception\Lib\Interfaces\Web as WebInterface; use Codeception\Module as CodeceptionModule; use Codeception\PHPUnit\Constraint\Page as PageConstraint; use Codeception\PHPUnit\Constraint\WebDriver as WebDriverConstraint; use Codeception\PHPUnit\Constraint\WebDriverNot as WebDriverConstraintNot; use Codeception\Test\Descriptor; use Codeception\Test\Interfaces\ScenarioDriven; use Codeception\TestInterface; use Codeception\Util\Debug; use Codeception\Util\ActionSequence; use Codeception\Util\Locator; use Codeception\Util\Uri; use Facebook\WebDriver\Cookie; use Facebook\WebDriver\Exception\InvalidElementStateException; use Facebook\WebDriver\Exception\InvalidSelectorException; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\UnknownServerException; use Facebook\WebDriver\Exception\WebDriverCurlException; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\Remote\LocalFileDetector; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\UselessFileDetector; use Facebook\WebDriver\Remote\RemoteWebElement; use Facebook\WebDriver\Remote\WebDriverCapabilityType; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverDimension; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverKeys; use Facebook\WebDriver\WebDriverSelect; use GuzzleHttp\Cookie\SetCookie; use Symfony\Component\DomCrawler\Crawler; /** * New generation Selenium WebDriver module. * * ## Local Testing * * ### Selenium * * To run Selenium Server you need [Java](https://www.java.com/) as well as Chrome or Firefox browser installed. * * 1. Download [Selenium Standalone Server](http://docs.seleniumhq.org/download/) * 2. To use Chrome, install [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/getting-started). To use Firefox, install [GeckoDriver](https://github.com/mozilla/geckodriver). * 3. Launch the Selenium Server: `java -jar selenium-server-standalone-3.xx.xxx.jar`. To locate Chromedriver binary use `-Dwebdriver.chrome.driver=./chromedriver` option. For Geckodriver use `-Dwebdriver.gecko.driver=./geckodriver`. * 4. Configure this module (in `acceptance.suite.yml`) by setting `url` and `browser`: * * ```yaml * modules: * enabled: * - WebDriver: * url: 'http://localhost/' * browser: chrome # 'chrome' or 'firefox' * ``` * * ### PhantomJS * * PhantomJS is a [headless browser](https://en.wikipedia.org/wiki/Headless_browser) alternative to Selenium Server that implements * [the WebDriver protocol](https://code.google.com/p/selenium/wiki/JsonWireProtocol). * It allows you to run Selenium tests on a server without a GUI installed. * * 1. Download [PhantomJS](http://phantomjs.org/download.html) * 2. Run PhantomJS in WebDriver mode: `phantomjs --webdriver=4444` * 3. Configure this module (in `acceptance.suite.yml`) by setting url and `phantomjs` as browser: * * ```yaml * modules: * enabled: * - WebDriver: * url: 'http://localhost/' * browser: phantomjs * ``` * * Since PhantomJS doesn't give you any visual feedback, it's probably a good idea to install [Codeception\Extension\Recorder](http://codeception.com/extensions#CodeceptionExtensionRecorder) which gives you screenshots of how PhantomJS "sees" your pages. * * ### Headless Selenium in Docker * * Docker can ship Selenium Server with all its dependencies and browsers inside a single container. * Running tests inside Docker is as easy as pulling [official selenium image](https://github.com/SeleniumHQ/docker-selenium) and starting a container with Chrome: * * ``` * docker run --net=host selenium/standalone-chrome * ``` * * By using `--net=host` we allow selenium to access local websites. * * ## Cloud Testing * * Cloud Testing services can run your WebDriver tests in the cloud. * In case you want to test a local site or site behind a firewall * you should use a tunnel application provided by a service. * * ### SauceLabs * * 1. Create an account at [SauceLabs.com](http://SauceLabs.com) to get your username and access key * 2. In the module configuration use the format `username`:`access_key`@ondemand.saucelabs.com' for `host` * 3. Configure `platform` under `capabilities` to define the [Operating System](https://docs.saucelabs.com/reference/platforms-configurator/#/) * 4. run a tunnel app if your site can't be accessed from Internet * * ```yaml * modules: * enabled: * - WebDriver: * url: http://mysite.com * host: '<username>:<access key>@ondemand.saucelabs.com' * port: 80 * browser: chrome * capabilities: * platform: 'Windows 10' * ``` * * ### BrowserStack * * 1. Create an account at [BrowserStack](https://www.browserstack.com/) to get your username and access key * 2. In the module configuration use the format `username`:`access_key`@hub.browserstack.com' for `host` * 3. Configure `os` and `os_version` under `capabilities` to define the operating System * 4. If your site is available only locally or via VPN you should use a tunnel app. In this case add `browserstack.local` capability and set it to true. * * ```yaml * modules: * enabled: * - WebDriver: * url: http://mysite.com * host: '<username>:<access key>@hub.browserstack.com' * port: 80 * browser: chrome * capabilities: * os: Windows * os_version: 10 * browserstack.local: true # for local testing * ``` * ### TestingBot * * 1. Create an account at [TestingBot](https://testingbot.com/) to get your key and secret * 2. In the module configuration use the format `key`:`secret`@hub.testingbot.com' for `host` * 3. Configure `platform` under `capabilities` to define the [Operating System](https://testingbot.com/support/getting-started/browsers.html) * 4. Run [TestingBot Tunnel](https://testingbot.com/support/other/tunnel) if your site can't be accessed from Internet * * ```yaml * modules: * enabled: * - WebDriver: * url: http://mysite.com * host: '<key>:<secret>@hub.testingbot.com' * port: 80 * browser: chrome * capabilities: * platform: Windows 10 * ``` * * ## Configuration * * * `url` *required* - Starting URL for your app. * * `browser` *required* - Browser to launch. * * `host` - Selenium server host (127.0.0.1 by default). * * `port` - Selenium server port (4444 by default). * * `restart` - Set to `false` (default) to use the same browser window for all tests, or set to `true` to create a new window for each test. In any case, when all tests are finished the browser window is closed. * * `window_size` - Initial window size. Set to `maximize` or a dimension in the format `640x480`. * * `clear_cookies` - Set to false to keep cookies, or set to true (default) to delete all cookies between tests. * * `wait` - Implicit wait (default 0 seconds). * * `capabilities` - Sets Selenium [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities). Should be a key-value array. * * `connection_timeout` - timeout for opening a connection to remote selenium server (30 seconds by default). * * `request_timeout` - timeout for a request to return something from remote selenium server (30 seconds by default). * * `pageload_timeout` - amount of time to wait for a page load to complete before throwing an error (default 0 seconds). * * `http_proxy` - sets http proxy server url for testing a remote server. * * `http_proxy_port` - sets http proxy server port * * `debug_log_entries` - how many selenium entries to print with `debugWebDriverLogs` or on fail (15 by default). * * `log_js_errors` - Set to true to include possible JavaScript to HTML report, or set to false (default) to deactivate. * * Example (`acceptance.suite.yml`) * * ```yaml * modules: * enabled: * - WebDriver: * url: 'http://localhost/' * browser: firefox * window_size: 1024x768 * capabilities: * unexpectedAlertBehaviour: 'accept' * firefox_profile: '~/firefox-profiles/codeception-profile.zip.b64' * ``` * * ## Usage * * ### Locating Elements * * Most methods in this module that operate on a DOM element (e.g. `click`) accept a locator as the first argument, * which can be either a string or an array. * * If the locator is an array, it should have a single element, * with the key signifying the locator type (`id`, `name`, `css`, `xpath`, `link`, or `class`) * and the value being the locator itself. * This is called a "strict" locator. * Examples: * * * `['id' => 'foo']` matches `<div id="foo">` * * `['name' => 'foo']` matches `<div name="foo">` * * `['css' => 'input[type=input][value=foo]']` matches `<input type="input" value="foo">` * * `['xpath' => "//input[@type='submit'][contains(@value, 'foo')]"]` matches `<input type="submit" value="foobar">` * * `['link' => 'Click here']` matches `<a target="_top" href="/newspapers?url=google.com">Click here</a>` * * `['class' => 'foo']` matches `<div class="foo">` * * Writing good locators can be tricky. * The Mozilla team has written an excellent guide titled [Writing reliable locators for Selenium and WebDriver tests](https://blog.mozilla.org/webqa/2013/09/26/writing-reliable-locators-for-selenium-and-webdriver-tests/). * * If you prefer, you may also pass a string for the locator. This is called a "fuzzy" locator. * In this case, Codeception uses a a variety of heuristics (depending on the exact method called) to determine what element you're referring to. * For example, here's the heuristic used for the `submitForm` method: * * 1. Does the locator look like an ID selector (e.g. "#foo")? If so, try to find a form matching that ID. * 2. If nothing found, check if locator looks like a CSS selector. If so, run it. * 3. If nothing found, check if locator looks like an XPath expression. If so, run it. * 4. Throw an `ElementNotFound` exception. * * Be warned that fuzzy locators can be significantly slower than strict locators. * Especially if you use Selenium WebDriver with `wait` (aka implicit wait) option. * In the example above if you set `wait` to 5 seconds and use XPath string as fuzzy locator, * `submitForm` method will wait for 5 seconds at each step. * That means 5 seconds finding the form by ID, another 5 seconds finding by CSS * until it finally tries to find the form by XPath). * If speed is a concern, it's recommended you stick with explicitly specifying the locator type via the array syntax. * * ## Public Properties * * * `webDriver` - instance of `\Facebook\WebDriver\Remote\RemoteWebDriver`. Can be accessed from Helper classes for complex WebDriver interactions. * * ```php * // inside Helper class * $this->getModule('WebDriver')->webDriver->getKeyboard()->sendKeys('hello, webdriver'); * ``` * */ class WebDriver extends CodeceptionModule implements WebInterface, RemoteInterface, MultiSessionInterface, SessionSnapshot, ScreenshotSaver, PageSourceSaver, ElementLocator, ConflictsWithModule, RequiresPackage { protected $requiredFields = ['browser', 'url']; protected $config = [ 'protocol' => 'http', 'host' => '127.0.0.1', 'port' => '4444', 'path' => '/wd/hub', 'restart' => false, 'wait' => 0, 'clear_cookies' => true, 'window_size' => false, 'capabilities' => [], 'connection_timeout' => null, 'request_timeout' => null, 'pageload_timeout' => null, 'http_proxy' => null, 'http_proxy_port' => null, 'ssl_proxy' => null, 'ssl_proxy_port' => null, 'debug_log_entries' => 15, 'log_js_errors' => false ]; protected $wd_host; protected $capabilities; protected $connectionTimeoutInMs; protected $requestTimeoutInMs; protected $test; protected $sessionSnapshots = []; protected $sessions = []; protected $httpProxy; protected $httpProxyPort; protected $sslProxy; protected $sslProxyPort; /** * @var RemoteWebDriver */ public $webDriver; /** * @var RemoteWebElement */ protected $baseElement; public function _requires() { return ['Facebook\WebDriver\Remote\RemoteWebDriver' => '"facebook/webdriver": "^1.0.1"']; } public function _initialize() { $this->wd_host = sprintf('%s://%s:%s%s', $this->config['protocol'], $this->config['host'], $this->config['port'], $this->config['path']); $this->capabilities = $this->config['capabilities']; $this->capabilities[WebDriverCapabilityType::BROWSER_NAME] = $this->config['browser']; if ($proxy = $this->getProxy()) { $this->capabilities[WebDriverCapabilityType::PROXY] = $proxy; } $this->connectionTimeoutInMs = $this->config['connection_timeout'] * 1000; $this->requestTimeoutInMs = $this->config['request_timeout'] * 1000; $this->loadFirefoxProfile(); } public function _conflicts() { return 'Codeception\Lib\Interfaces\Web'; } public function _before(TestInterface $test) { if (!isset($this->webDriver)) { $this->_initializeSession(); } $this->setBaseElement(); $test->getMetadata()->setCurrent([ 'browser' => $this->config['browser'], 'capabilities' => $this->config['capabilities'] ]); } /** * Restarts a web browser. * Can be used with `_reconfigure` to open browser with different configuration * * ```php * <?php * // inside a Helper * $this->getModule('WebDriver')->_restart(); // just restart * $this->getModule('WebDriver')->_restart(['browser' => $browser]); // reconfigure + restart * ``` * * @param array $config * @api */ public function _restart($config = []) { $this->webDriver->quit(); if (!empty($config)) { $this->_reconfigure($config); } $this->_initialize(); $this->_initializeSession(); } protected function loadFirefoxProfile() { if (!array_key_exists('firefox_profile', $this->config['capabilities'])) { return; } $firefox_profile = $this->config['capabilities']['firefox_profile']; if (file_exists($firefox_profile) === false) { throw new ModuleConfigException( __CLASS__, "Firefox profile does not exist under given path " . $firefox_profile ); } // Set firefox profile as capability $this->capabilities['firefox_profile'] = file_get_contents($firefox_profile); } protected function initialWindowSize() { if ($this->config['window_size'] == 'maximize') { $this->maximizeWindow(); return; } $size = explode('x', $this->config['window_size']); if (count($size) == 2) { $this->resizeWindow(intval($size[0]), intval($size[1])); } } public function _after(TestInterface $test) { if ($this->config['restart']) { $this->cleanWebDriver(); return; } if ($this->config['clear_cookies'] && isset($this->webDriver)) { $this->webDriver->manage()->deleteAllCookies(); } } public function _failed(TestInterface $test, $fail) { $this->debugWebDriverLogs($test); $filename = preg_replace('~\W~', '.', Descriptor::getTestSignature($test)); $outputDir = codecept_output_dir(); $this->_saveScreenshot($report = $outputDir . mb_strcut($filename, 0, 245, 'utf-8') . '.fail.png'); $test->getMetadata()->addReport('png', $report); $this->_savePageSource($report = $outputDir . mb_strcut($filename, 0, 244, 'utf-8') . '.fail.html'); $test->getMetadata()->addReport('html', $report); $this->debug("Screenshot and page source were saved into '$outputDir' dir"); } /** * Print out latest Selenium Logs in debug mode * * @param TestInterface $test */ public function debugWebDriverLogs(TestInterface $test = null) { if (!isset($this->webDriver)) { $this->debug('WebDriver::debugWebDriverLogs method has been called when webDriver is not set'); return; } try { // Dump out latest Selenium logs $logs = $this->webDriver->manage()->getAvailableLogTypes(); foreach ($logs as $logType) { $logEntries = array_slice( $this->webDriver->manage()->getLog($logType), -$this->config['debug_log_entries'] ); if (empty($logEntries)) { $this->debugSection("Selenium {$logType} Logs", " EMPTY "); continue; } $this->debugSection("Selenium {$logType} Logs", "\n" . $this->formatLogEntries($logEntries)); if ($logType === 'browser' && $this->config['log_js_errors'] && ($test instanceof ScenarioDriven) ) { $this->logJSErrors($test, $logEntries); } } } catch (\Exception $e) { $this->debug('Unable to retrieve Selenium logs : ' . $e->getMessage()); } } /** * Turns an array of log entries into a human-readable string. * Each log entry is an array with the keys "timestamp", "level", and "message". * See https://code.google.com/p/selenium/wiki/JsonWireProtocol#Log_Entry_JSON_Object * * @param array $logEntries * @return string */ protected function formatLogEntries(array $logEntries) { $formattedLogs = ''; foreach ($logEntries as $logEntry) { // Timestamp is in milliseconds, but date() requires seconds. $time = date('H:i:s', $logEntry['timestamp'] / 1000) . // Append the milliseconds to the end of the time string '.' . ($logEntry['timestamp'] % 1000); $formattedLogs .= "{$time} {$logEntry['level']} - {$logEntry['message']}\n"; } return $formattedLogs; } /** * Logs JavaScript errors as comments. * * @param ScenarioDriven $test * @param array $browserLogEntries */ protected function logJSErrors(ScenarioDriven $test, array $browserLogEntries) { foreach ($browserLogEntries as $logEntry) { if (true === isset($logEntry['level']) && true === isset($logEntry['message']) && $this->isJSError($logEntry['level'], $logEntry['message']) ) { // Timestamp is in milliseconds, but date() requires seconds. $time = date('H:i:s', $logEntry['timestamp'] / 1000) . // Append the milliseconds to the end of the time string '.' . ($logEntry['timestamp'] % 1000); $test->getScenario()->comment("{$time} {$logEntry['level']} - {$logEntry['message']}"); } } } /** * Determines if the log entry is an error. * The decision is made depending on browser and log-level. * * @param string $logEntryLevel * @param string $message * @return bool */ protected function isJSError($logEntryLevel, $message) { return ( ($this->isPhantom() && $logEntryLevel != 'INFO') // phantomjs logs errors as "WARNING" || $logEntryLevel === 'SEVERE' // other browsers log errors as "SEVERE" ) && strpos($message, 'ERR_PROXY_CONNECTION_FAILED') === false; // ignore blackhole proxy } public function _afterSuite() { // this is just to make sure webDriver is cleared after suite $this->cleanWebDriver(); } protected function cleanWebDriver() { foreach ($this->sessions as $session) { $this->_loadSession($session); try { $this->webDriver->quit(); } catch (\Exception $e) { // Session already closed so nothing to do } unset($this->webDriver); } $this->sessions = []; } public function amOnSubdomain($subdomain) { $url = $this->config['url']; $url = preg_replace('~(https?:\/\/)(.*\.)(.*\.)~', "$1$3", $url); // removing current subdomain $url = preg_replace('~(https?:\/\/)(.*)~', "$1$subdomain.$2", $url); // inserting new $this->_reconfigure(['url' => $url]); } /** * Returns URL of a host. * @api * @return mixed * @throws ModuleConfigException */ public function _getUrl() { if (!isset($this->config['url'])) { throw new ModuleConfigException( __CLASS__, "Module connection failure. The URL for client can't bre retrieved" ); } return $this->config['url']; } protected function getProxy() { $proxyConfig = []; if ($this->config['http_proxy']) { $proxyConfig['httpProxy'] = $this->config['http_proxy']; if ($this->config['http_proxy_port']) { $proxyConfig['httpProxy'] .= ':' . $this->config['http_proxy_port']; } } if ($this->config['ssl_proxy']) { $proxyConfig['sslProxy'] = $this->config['ssl_proxy']; if ($this->config['ssl_proxy_port']) { $proxyConfig['sslProxy'] .= ':' . $this->config['ssl_proxy_port']; } } if (!empty($proxyConfig)) { $proxyConfig['proxyType'] = 'manual'; return $proxyConfig; } return null; } /** * Uri of currently opened page. * @return string * @api * @throws ModuleException */ public function _getCurrentUri() { $url = $this->webDriver->getCurrentURL(); if ($url == 'about:blank') { throw new ModuleException($this, 'Current url is blank, no page was opened'); } return Uri::retrieveUri($url); } public function _saveScreenshot($filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_saveScreenshot method has been called when webDriver is not set'); return; } try { $this->webDriver->takeScreenshot($filename); } catch (\Exception $e) { $this->debug('Unable to retrieve screenshot from Selenium : ' . $e->getMessage()); } } public function _findElements($locator) { return $this->match($this->webDriver, $locator); } /** * Saves HTML source of a page to a file * @param $filename */ public function _savePageSource($filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_savePageSource method has been called when webDriver is not set'); return; } try { file_put_contents($filename, $this->webDriver->getPageSource()); } catch (\Exception $e) { $this->debug('Unable to retrieve source page from Selenium : ' . $e->getMessage()); } } /** * Takes a screenshot of the current window and saves it to `tests/_output/debug`. * * ``` php * <?php * $I->amOnPage('/user/edit'); * $I->makeScreenshot('edit_page'); * // saved to: tests/_output/debug/edit_page.png * ?> * ``` * * @param $name */ public function makeScreenshot($name) { $debugDir = codecept_log_dir() . 'debug'; if (!is_dir($debugDir)) { mkdir($debugDir, 0777); } $screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png'; $this->_saveScreenshot($screenName); $this->debug("Screenshot saved to $screenName"); } /** * Resize the current window. * * ``` php * <?php * $I->resizeWindow(800, 600); * * ``` * * @param int $width * @param int $height */ public function resizeWindow($width, $height) { $this->webDriver->manage()->window()->setSize(new WebDriverDimension($width, $height)); } public function seeCookie($cookie, array $params = []) { $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); $cookies = array_map( function ($c) { return $c['name']; }, $cookies ); $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies())); $this->assertContains($cookie, $cookies); } public function dontSeeCookie($cookie, array $params = []) { $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); $cookies = array_map( function ($c) { return $c['name']; }, $cookies ); $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies())); $this->assertNotContains($cookie, $cookies); } public function setCookie($cookie, $value, array $params = []) { $params['name'] = $cookie; $params['value'] = $value; if (isset($params['expires'])) { // PhpBrowser compatibility $params['expiry'] = $params['expires']; } if (!isset($params['domain'])) { $urlParts = parse_url($this->config['url']); if (isset($urlParts['host'])) { $params['domain'] = $urlParts['host']; } } $this->webDriver->manage()->addCookie($params); $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies())); } public function resetCookie($cookie, array $params = []) { $this->webDriver->manage()->deleteCookieNamed($cookie); $this->debugSection('Cookies', json_encode($this->webDriver->manage()->getCookies())); } public function grabCookie($cookie, array $params = []) { $params['name'] = $cookie; $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); if (empty($cookies)) { return null; } $cookie = reset($cookies); return $cookie['value']; } /** * Grabs current page source code. * * @throws ModuleException if no page was opened. * * @return string Current page source code. */ public function grabPageSource() { // Make sure that some page was opened. $this->_getCurrentUri(); return $this->webDriver->getPageSource(); } protected function filterCookies($cookies, $params = []) { foreach (['domain', 'path', 'name'] as $filter) { if (!isset($params[$filter])) { continue; } $cookies = array_filter( $cookies, function ($item) use ($filter, $params) { return $item[$filter] == $params[$filter]; } ); } return $cookies; } public function amOnUrl($url) { $host = Uri::retrieveHost($url); $this->_reconfigure(['url' => $host]); $this->debugSection('Host', $host); $this->webDriver->get($url); } public function amOnPage($page) { $url = Uri::appendPath($this->config['url'], $page); $this->debugSection('GET', $url); $this->webDriver->get($url); } public function see($text, $selector = null) { if (!$selector) { return $this->assertPageContains($text); } $nodes = $this->matchVisible($selector); $this->assertNodesContain($text, $nodes, $selector); } public function dontSee($text, $selector = null) { if (!$selector) { return $this->assertPageNotContains($text); } $nodes = $this->matchVisible($selector); $this->assertNodesNotContain($text, $nodes, $selector); } public function seeInSource($raw) { $this->assertPageSourceContains($raw); } public function dontSeeInSource($raw) { $this->assertPageSourceNotContains($raw); } /** * Checks that the page source contains the given string. * * ```php * <?php * $I->seeInPageSource('<link rel="apple-touch-icon"'); * ``` * * @param $text */ public function seeInPageSource($text) { $this->assertThat( $this->webDriver->getPageSource(), new PageConstraint($text, $this->_getCurrentUri()), '' ); } /** * Checks that the page source doesn't contain the given string. * * @param $text */ public function dontSeeInPageSource($text) { $this->assertThatItsNot( $this->webDriver->getPageSource(), new PageConstraint($text, $this->_getCurrentUri()), '' ); } public function click($link, $context = null) { $page = $this->webDriver; if ($context) { $page = $this->matchFirstOrFail($this->webDriver, $context); } $el = $this->_findClickable($page, $link); if (!$el) { try { $els = $this->match($page, $link); } catch (MalformedLocatorException $e) { throw new ElementNotFound("name=$link", "'$link' is invalid CSS and XPath selector and Link or Button"); } $el = reset($els); } if (!$el) { throw new ElementNotFound($link, 'Link or Button or CSS or XPath'); } $el->click(); } /** * Locates a clickable element. * * Use it in Helpers or GroupObject or Extension classes: * * ```php * <?php * $module = $this->getModule('WebDriver'); * $page = $module->webDriver; * * // search a link or button on a page * $el = $module->_findClickable($page, 'Click Me'); * * // search a link or button within an element * $topBar = $module->_findElements('.top-bar')[0]; * $el = $module->_findClickable($topBar, 'Click Me'); * * ``` * @api * @param $page WebDriver instance or an element to search within * @param $link a link text or locator to click * @return WebDriverElement */ public function _findClickable($page, $link) { if (is_array($link) or ($link instanceof WebDriverBy)) { return $this->matchFirstOrFail($page, $link); } // try to match by CSS or XPath try { $els = $this->match($page, $link, false); if (!empty($els)) { return reset($els); } } catch (MalformedLocatorException $e) { //ignore exception, link could still match on of the things below } $locator = Crawler::xpathLiteral(trim($link)); // narrow $xpath = Locator::combine( ".//a[normalize-space(.)=$locator]", ".//button[normalize-space(.)=$locator]", ".//a/img[normalize-space(@alt)=$locator]/ancestor::a", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)=$locator]" ); $els = $page->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } // wide $xpath = Locator::combine( ".//a[./@href][((contains(normalize-space(string(.)), $locator)) or contains(./@title, $locator) or .//img[contains(./@alt, $locator)])]", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, $locator)]", ".//input[./@type = 'image'][contains(./@alt, $locator)]", ".//button[contains(normalize-space(string(.)), $locator)]", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = $locator]", ".//button[./@name = $locator]" ); $els = $page->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } return null; } /** * @param $selector * @return WebDriverElement[] * @throws \Codeception\Exception\ElementNotFound */ protected function findFields($selector) { if ($selector instanceof WebDriverElement) { return [$selector]; } if (is_array($selector) || ($selector instanceof WebDriverBy)) { $fields = $this->match($this->webDriver, $selector); if (empty($fields)) { throw new ElementNotFound($selector); } return $fields; } $locator = Crawler::xpathLiteral(trim($selector)); // by text or label $xpath = Locator::combine( // @codingStandardsIgnoreStart ".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = $locator) or ./@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or ./@placeholder = $locator)]", ".//label[contains(normalize-space(string(.)), $locator)]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" // @codingStandardsIgnoreEnd ); $fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath)); if (!empty($fields)) { return $fields; } // by name $xpath = ".//*[self::input | self::textarea | self::select][@name = $locator]"; $fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath)); if (!empty($fields)) { return $fields; } // try to match by CSS or XPath $fields = $this->match($this->webDriver, $selector, false); if (!empty($fields)) { return $fields; } throw new ElementNotFound($selector, "Field by name, label, CSS or XPath"); } /** * @param $selector * @return WebDriverElement * @throws \Codeception\Exception\ElementNotFound */ protected function findField($selector) { $arr = $this->findFields($selector); return reset($arr); } public function seeLink($text, $url = null) { $nodes = $this->baseElement->findElements(WebDriverBy::partialLinkText($text)); $currentUri = $this->_getCurrentUri(); if (empty($nodes)) { $this->fail("No links containing text '$text' were found in page $currentUri"); } if ($url) { $nodes = $this->filterNodesByHref($url, $nodes); } $this->assertNotEmpty($nodes, "No links containing text '$text' and URL '$url' were found in page $currentUri"); } public function dontSeeLink($text, $url = null) { $nodes = $this->baseElement->findElements(WebDriverBy::partialLinkText($text)); $currentUri = $this->_getCurrentUri(); if (!$url) { $this->assertEmpty($nodes, "Link containing text '$text' was found in page $currentUri"); } else { $nodes = $this->filterNodesByHref($url, $nodes); $this->assertEmpty($nodes, "Link containing text '$text' and URL '$url' was found in page $currentUri"); } } /** * @param string $url * @param $nodes * @return array */ private function filterNodesByHref($url, $nodes) { //current uri can be relative, merging it with configured base url gives absolute url $absoluteCurrentUrl = Uri::mergeUrls($this->_getUrl(), $this->_getCurrentUri()); $expectedUrl = Uri::mergeUrls($absoluteCurrentUrl, $url); $nodes = array_filter( $nodes, function (WebDriverElement $e) use ($expectedUrl, $absoluteCurrentUrl) { $elementHref = Uri::mergeUrls($absoluteCurrentUrl, $e->getAttribute('href')); return $elementHref === $expectedUrl; } ); return $nodes; } public function seeInCurrentUrl($uri) { $this->assertContains($uri, $this->_getCurrentUri()); } public function seeCurrentUrlEquals($uri) { $this->assertEquals($uri, $this->_getCurrentUri()); } public function seeCurrentUrlMatches($uri) { $this->assertRegExp($uri, $this->_getCurrentUri()); } public function dontSeeInCurrentUrl($uri) { $this->assertNotContains($uri, $this->_getCurrentUri()); } public function dontSeeCurrentUrlEquals($uri) { $this->assertNotEquals($uri, $this->_getCurrentUri()); } public function dontSeeCurrentUrlMatches($uri) { $this->assertNotRegExp($uri, $this->_getCurrentUri()); } public function grabFromCurrentUrl($uri = null) { if (!$uri) { return $this->_getCurrentUri(); } $matches = []; $res = preg_match($uri, $this->_getCurrentUri(), $matches); if (!$res) { $this->fail("Couldn't match $uri in " . $this->_getCurrentUri()); } if (!isset($matches[1])) { $this->fail("Nothing to grab. A regex parameter required. Ex: '/user/(\\d+)'"); } return $matches[1]; } public function seeCheckboxIsChecked($checkbox) { $this->assertTrue($this->findField($checkbox)->isSelected()); } public function dontSeeCheckboxIsChecked($checkbox) { $this->assertFalse($this->findField($checkbox)->isSelected()); } public function seeInField($field, $value) { $els = $this->findFields($field); $this->assert($this->proceedSeeInField($els, $value)); } public function dontSeeInField($field, $value) { $els = $this->findFields($field); $this->assertNot($this->proceedSeeInField($els, $value)); } public function seeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, false); } public function dontSeeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, true); } protected function proceedSeeInFormFields($formSelector, array $params, $assertNot) { $form = $this->match($this->baseElement, $formSelector); if (empty($form)) { throw new ElementNotFound($formSelector, "Form via CSS or XPath"); } $form = reset($form); foreach ($params as $name => $values) { $els = $form->findElements(WebDriverBy::name($name)); if (empty($els)) { throw new ElementNotFound($name); } if (!is_array($values)) { $values = [$values]; } foreach ($values as $value) { $ret = $this->proceedSeeInField($els, $value); if ($assertNot) { $this->assertNot($ret); } else { $this->assert($ret); } } } } /** * @param RemoteWebElement[] $elements * @param $value * @return array */ protected function proceedSeeInField(array $elements, $value) { $strField = reset($elements)->getAttribute('name'); if (reset($elements)->getTagName() === 'select') { $el = reset($elements); $elements = $el->findElements(WebDriverBy::xpath('.//option')); if (empty($value) && empty($elements)) { return ['True', true]; } } $currentValues = []; if (is_bool($value)) { $currentValues = [false]; } foreach ($elements as $el) { switch ($el->getTagName()) { case 'input': if ($el->getAttribute('type') === 'radio' || $el->getAttribute('type') === 'checkbox') { if ($el->getAttribute('checked')) { if (is_bool($value)) { $currentValues = [true]; break; } else { $currentValues[] = $el->getAttribute('value'); } } } else { $currentValues[] = $el->getAttribute('value'); } break; case 'option': // no break we need the trim text and the value also if (!$el->isSelected()) { break; } $currentValues[] = $el->getText(); case 'textarea': // we include trimmed and real value of textarea for check $currentValues[] = trim($el->getText()); default: $currentValues[] = $el->getAttribute('value'); // raw value break; } } return [ 'Contains', $value, $currentValues, "Failed testing for '$value' in $strField's value: '" . implode("', '", $currentValues) . "'" ]; } public function selectOption($select, $option) { $el = $this->findField($select); if ($el->getTagName() != 'select') { $els = $this->matchCheckables($select); $radio = null; foreach ($els as $el) { $radio = $this->findCheckable($el, $option, true); if ($radio) { break; } } if (!$radio) { throw new ElementNotFound($select, "Radiobutton with value or name '$option in"); } $radio->click(); return; } $wdSelect = new WebDriverSelect($el); if ($wdSelect->isMultiple()) { $wdSelect->deselectAll(); } if (!is_array($option)) { $option = [$option]; } $matched = false; if (key($option) !== 'value') { foreach ($option as $opt) { try { $wdSelect->selectByVisibleText($opt); $matched = true; } catch (NoSuchElementException $e) { } } } if ($matched) { return; } if (key($option) !== 'text') { foreach ($option as $opt) { try { $wdSelect->selectByValue($opt); $matched = true; } catch (NoSuchElementException $e) { } } } if ($matched) { return; } // partially matching foreach ($option as $opt) { try { $optElement = $el->findElement(WebDriverBy::xpath('.//option [contains (., "' . $opt . '")]')); $matched = true; if (!$optElement->isSelected()) { $optElement->click(); } } catch (NoSuchElementException $e) { // exception treated at the end } } if ($matched) { return; } throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value"); } public function _initializeSession() { try { $this->webDriver = RemoteWebDriver::create( $this->wd_host, $this->capabilities, $this->connectionTimeoutInMs, $this->requestTimeoutInMs, $this->httpProxy, $this->httpProxyPort ); $this->sessions[] = $this->_backupSession(); $this->webDriver->manage()->timeouts()->implicitlyWait($this->config['wait']); if (!is_null($this->config['pageload_timeout'])) { $this->webDriver->manage()->timeouts()->pageLoadTimeout($this->config['pageload_timeout']); } $this->setBaseElement(); $this->initialWindowSize(); } catch (WebDriverCurlException $e) { throw new ConnectionException("Can't connect to Webdriver at {$this->wd_host}. Please make sure that Selenium Server or PhantomJS is running."); } } public function _loadSession($session) { $this->webDriver = $session; $this->setBaseElement(); } public function _backupSession() { return $this->webDriver; } public function _closeSession($webDriver) { $keys = array_keys($this->sessions, $webDriver, true); $key = array_shift($keys); try { $webDriver->quit(); } catch (UnknownServerException $e) { // Session already closed so nothing to do } $this->setBaseElement(); unset($this->sessions[$key]); } /** * Unselect an option in the given select box. * * @param $select * @param $option */ public function unselectOption($select, $option) { $el = $this->findField($select); $wdSelect = new WebDriverSelect($el); if (!is_array($option)) { $option = [$option]; } $matched = false; foreach ($option as $opt) { try { $wdSelect->deselectByVisibleText($opt); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } try { $wdSelect->deselectByValue($opt); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } } if ($matched) { return; } throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value"); } /** * @param $context * @param $radioOrCheckbox * @param bool $byValue * @return mixed|null */ protected function findCheckable($context, $radioOrCheckbox, $byValue = false) { if ($radioOrCheckbox instanceof WebDriverElement) { return $radioOrCheckbox; } if (is_array($radioOrCheckbox) or ($radioOrCheckbox instanceof WebDriverBy)) { return $this->matchFirstOrFail($this->baseElement, $radioOrCheckbox); } $locator = Crawler::xpathLiteral($radioOrCheckbox); if ($context instanceof WebDriverElement && $context->getTagName() === 'input') { $contextType = $context->getAttribute('type'); if (!in_array($contextType, ['checkbox', 'radio'], true)) { return null; } $nameLiteral = Crawler::xPathLiteral($context->getAttribute('name')); $typeLiteral = Crawler::xPathLiteral($contextType); $inputLocatorFragment = "input[@type = $typeLiteral][@name = $nameLiteral]"; $xpath = Locator::combine( // @codingStandardsIgnoreStart "ancestor::form//{$inputLocatorFragment}[(@id = ancestor::form//label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator]", // @codingStandardsIgnoreEnd "ancestor::form//label[contains(normalize-space(string(.)), $locator)]//{$inputLocatorFragment}" ); if ($byValue) { $xpath = Locator::combine($xpath, "ancestor::form//{$inputLocatorFragment}[@value = $locator]"); } } else { $xpath = Locator::combine( // @codingStandardsIgnoreStart "//input[@type = 'checkbox' or @type = 'radio'][(@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator or @name = $locator]", // @codingStandardsIgnoreEnd "//label[contains(normalize-space(string(.)), $locator)]//input[@type = 'radio' or @type = 'checkbox']" ); if ($byValue) { $xpath = Locator::combine($xpath, "//input[@type = 'checkbox' or @type = 'radio'][@value = $locator]"); } } $els = $context->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } $els = $context->findElements(WebDriverBy::xpath(str_replace('ancestor::form', '', $xpath))); if (count($els)) { return reset($els); } $els = $this->match($context, $radioOrCheckbox); if (count($els)) { return reset($els); } return null; } protected function matchCheckables($selector) { $els = $this->match($this->webDriver, $selector); if (!count($els)) { throw new ElementNotFound($selector, "Element containing radio by CSS or XPath"); } return $els; } public function checkOption($option) { $field = $this->findCheckable($this->webDriver, $option); if (!$field) { throw new ElementNotFound($option, "Checkbox or Radio by Label or CSS or XPath"); } if ($field->isSelected()) { return; } $field->click(); } public function uncheckOption($option) { $field = $this->findCheckable($this->baseElement, $option); if (!$field) { throw new ElementNotFound($option, "Checkbox by Label or CSS or XPath"); } if (!$field->isSelected()) { return; } $field->click(); } public function fillField($field, $value) { $el = $this->findField($field); $el->clear(); $el->sendKeys($value); } public function attachFile($field, $filename) { $el = $this->findField($field); // in order to be compatible on different OS $filePath = codecept_data_dir() . $filename; if (!file_exists($filePath)) { throw new \InvalidArgumentException("File does not exist: $filePath"); } if (!is_readable($filePath)) { throw new \InvalidArgumentException("File is not readable: $filePath"); } // in order for remote upload to be enabled $el->setFileDetector(new LocalFileDetector()); // skip file detector for phantomjs if ($this->isPhantom()) { $el->setFileDetector(new UselessFileDetector()); } $el->sendKeys(realpath($filePath)); } /** * Grabs all visible text from the current page. * * @return string */ protected function getVisibleText() { if ($this->baseElement instanceof RemoteWebElement) { return $this->baseElement->getText(); } $els = $this->baseElement->findElements(WebDriverBy::cssSelector('body')); if (isset($els[0])) { return $els[0]->getText(); } return ''; } public function grabTextFrom($cssOrXPathOrRegex) { $els = $this->match($this->baseElement, $cssOrXPathOrRegex, false); if (count($els)) { return $els[0]->getText(); } if (@preg_match($cssOrXPathOrRegex, $this->webDriver->getPageSource(), $matches)) { return $matches[1]; } throw new ElementNotFound($cssOrXPathOrRegex, 'CSS or XPath or Regex'); } public function grabAttributeFrom($cssOrXpath, $attribute) { $el = $this->matchFirstOrFail($this->baseElement, $cssOrXpath); return $el->getAttribute($attribute); } public function grabValueFrom($field) { $el = $this->findField($field); // value of multiple select is the value of the first selected option if ($el->getTagName() == 'select') { $select = new WebDriverSelect($el); return $select->getFirstSelectedOption()->getAttribute('value'); } return $el->getAttribute('value'); } public function grabMultiple($cssOrXpath, $attribute = null) { $els = $this->match($this->baseElement, $cssOrXpath); return array_map( function (WebDriverElement $e) use ($attribute) { if ($attribute) { return $e->getAttribute($attribute); } return $e->getText(); }, $els ); } protected function filterByAttributes($els, array $attributes) { foreach ($attributes as $attr => $value) { $els = array_filter( $els, function (WebDriverElement $el) use ($attr, $value) { return $el->getAttribute($attr) == $value; } ); } return $els; } public function seeElement($selector, $attributes = []) { $els = $this->matchVisible($selector); $els = $this->filterByAttributes($els, $attributes); $this->assertNotEmpty($els); } public function dontSeeElement($selector, $attributes = []) { $els = $this->matchVisible($selector); $els = $this->filterByAttributes($els, $attributes); $this->assertEmpty($els); } /** * Checks that the given element exists on the page, even it is invisible. * * ``` php * <?php * $I->seeElementInDOM('//form/input[type=hidden]'); * ?> * ``` * * @param $selector * @param array $attributes */ public function seeElementInDOM($selector, $attributes = []) { $els = $this->match($this->baseElement, $selector); $els = $this->filterByAttributes($els, $attributes); $this->assertNotEmpty($els); } /** * Opposite of `seeElementInDOM`. * * @param $selector * @param array $attributes */ public function dontSeeElementInDOM($selector, $attributes = []) { $els = $this->match($this->baseElement, $selector); $els = $this->filterByAttributes($els, $attributes); $this->assertEmpty($els); } public function seeNumberOfElements($selector, $expected) { $counted = count($this->matchVisible($selector)); if (is_array($expected)) { list($floor, $ceil) = $expected; $this->assertTrue( $floor <= $counted && $ceil >= $counted, 'Number of elements counted differs from expected range' ); } else { $this->assertEquals( $expected, $counted, 'Number of elements counted differs from expected number' ); } } public function seeNumberOfElementsInDOM($selector, $expected) { $counted = count($this->match($this->baseElement, $selector)); if (is_array($expected)) { list($floor, $ceil) = $expected; $this->assertTrue( $floor <= $counted && $ceil >= $counted, 'Number of elements counted differs from expected range' ); } else { $this->assertEquals( $expected, $counted, 'Number of elements counted differs from expected number' ); } } public function seeOptionIsSelected($selector, $optionText) { $el = $this->findField($selector); if ($el->getTagName() !== 'select') { $els = $this->matchCheckables($selector); foreach ($els as $k => $el) { $els[$k] = $this->findCheckable($el, $optionText, true); } $this->assertNotEmpty( array_filter( $els, function ($e) { return $e && $e->isSelected(); } ) ); return; } $select = new WebDriverSelect($el); $this->assertNodesContain($optionText, $select->getAllSelectedOptions(), 'option'); } public function dontSeeOptionIsSelected($selector, $optionText) { $el = $this->findField($selector); if ($el->getTagName() !== 'select') { $els = $this->matchCheckables($selector); foreach ($els as $k => $el) { $els[$k] = $this->findCheckable($el, $optionText, true); } $this->assertEmpty( array_filter( $els, function ($e) { return $e && $e->isSelected(); } ) ); return; } $select = new WebDriverSelect($el); $this->assertNodesNotContain($optionText, $select->getAllSelectedOptions(), 'option'); } public function seeInTitle($title) { $this->assertContains($title, $this->webDriver->getTitle()); } public function dontSeeInTitle($title) { $this->assertNotContains($title, $this->webDriver->getTitle()); } /** * Accepts the active JavaScript native popup window, as created by `window.alert`|`window.confirm`|`window.prompt`. * Don't confuse popups with modal windows, * as created by [various libraries](http://jster.net/category/windows-modals-popups). */ public function acceptPopup() { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->accept(); } /** * Dismisses the active JavaScript popup, as created by `window.alert`|`window.confirm`|`window.prompt`. */ public function cancelPopup() { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->dismiss(); } /** * Checks that the active JavaScript popup, * as created by `window.alert`|`window.confirm`|`window.prompt`, contains the given string. * * @param $text * * @throws \Codeception\Exception\ModuleException */ public function seeInPopup($text) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $alert = $this->webDriver->switchTo()->alert(); try { $this->assertContains($text, $alert->getText()); } catch (\PHPUnit_Framework_AssertionFailedError $e) { $alert->dismiss(); throw $e; } } /** * Enters text into a native JavaScript prompt popup, as created by `window.prompt`. * * @param $keys * * @throws \Codeception\Exception\ModuleException */ public function typeInPopup($keys) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->sendKeys($keys); } /** * Reloads the current page. */ public function reloadPage() { $this->webDriver->navigate()->refresh(); } /** * Moves back in history. */ public function moveBack() { $this->webDriver->navigate()->back(); $this->debug($this->_getCurrentUri()); } /** * Moves forward in history. */ public function moveForward() { $this->webDriver->navigate()->forward(); $this->debug($this->_getCurrentUri()); } protected function getSubmissionFormFieldName($name) { if (substr($name, -2) === '[]') { return substr($name, 0, -2); } return $name; } /** * Submits the given form on the page, optionally with the given form * values. Give the form fields values as an array. Note that hidden fields * can't be accessed. * * Skipped fields will be filled by their values from the page. * You don't need to click the 'Submit' button afterwards. * This command itself triggers the request to form's action. * * You can optionally specify what button's value to include * in the request with the last parameter as an alternative to * explicitly setting its value in the second parameter, as * button values are not otherwise included in the request. * * Examples: * * ``` php * <?php * $I->submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ]); * // or * $I->submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ], 'submitButtonName'); * * ``` * * For example, given this sample "Sign Up" form: * * ``` html * <form action="/sign_up"> * Login: * <input type="text" name="user[login]" /><br/> * Password: * <input type="password" name="user[password]" /><br/> * Do you agree to our terms? * <input type="checkbox" name="user[agree]" /><br/> * Select pricing plan: * <select name="plan"> * <option value="1">Free</option> * <option value="2" selected="selected">Paid</option> * </select> * <input type="submit" name="submitButton" value="Submit" /> * </form> * ``` * * You could write the following to submit it: * * ``` php * <?php * $I->submitForm( * '#userForm', * [ * 'user[login]' => 'Davert', * 'user[password]' => '123456', * 'user[agree]' => true * ], * 'submitButton' * ); * ``` * Note that "2" will be the submitted value for the "plan" field, as it is * the selected option. * * Also note that this differs from PhpBrowser, in that * ```'user' => [ 'login' => 'Davert' ]``` is not supported at the moment. * Named array keys *must* be included in the name as above. * * Pair this with seeInFormFields for quick testing magic. * * ``` php * <?php * $form = [ * 'field1' => 'value', * 'field2' => 'another value', * 'checkbox1' => true, * // ... * ]; * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); * // $I->amOnPage('/path/to/form-page') may be needed * $I->seeInFormFields('//form[@id=my-form]', $form); * ?> * ``` * * Parameter values must be set to arrays for multiple input fields * of the same name, or multi-select combo boxes. For checkboxes, * either the string value can be used, or boolean values which will * be replaced by the checkbox's value in the DOM. * * ``` php * <?php * $I->submitForm('#my-form', [ * 'field1' => 'value', * 'checkbox' => [ * 'value of first checkbox', * 'value of second checkbox, * ], * 'otherCheckboxes' => [ * true, * false, * false * ], * 'multiselect' => [ * 'first option value', * 'second option value' * ] * ]); * ?> * ``` * * Mixing string and boolean values for a checkbox's value is not supported * and may produce unexpected results. * * Field names ending in "[]" must be passed without the trailing square * bracket characters, and must contain an array for its value. This allows * submitting multiple values with the same name, consider: * * ```php * $I->submitForm('#my-form', [ * 'field[]' => 'value', * 'field[]' => 'another value', // 'field[]' is already a defined key * ]); * ``` * * The solution is to pass an array value: * * ```php * // this way both values are submitted * $I->submitForm('#my-form', [ * 'field' => [ * 'value', * 'another value', * ] * ]); * ``` * * The `$button` parameter can be either a string, an array or an instance * of Facebook\WebDriver\WebDriverBy. When it is a string, the * button will be found by its "name" attribute. If $button is an * array then it will be treated as a strict selector and a WebDriverBy * will be used verbatim. * * For example, given the following HTML: * * ``` html * <input type="submit" name="submitButton" value="Submit" /> * ``` * * `$button` could be any one of the following: * - 'submitButton' * - ['name' => 'submitButton'] * - WebDriverBy::name('submitButton') * * @param $selector * @param $params * @param $button */ public function submitForm($selector, array $params, $button = null) { $form = $this->match($this->baseElement, $selector); if (empty($form)) { throw new ElementNotFound($selector, "Form via CSS or XPath"); } $form = reset($form); $fields = $form->findElements( WebDriverBy::cssSelector('input:enabled,textarea:enabled,select:enabled,input[type=hidden]') ); foreach ($fields as $field) { $fieldName = $this->getSubmissionFormFieldName($field->getAttribute('name')); if (!isset($params[$fieldName])) { continue; } $value = $params[$fieldName]; if (is_array($value) && $field->getTagName() !== 'select') { if ($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') { $found = false; foreach ($value as $index => $val) { if (!is_bool($val) && $val === $field->getAttribute('value')) { array_splice($params[$fieldName], $index, 1); $value = $val; $found = true; break; } } if (!$found && !empty($value) && is_bool(reset($value))) { $value = array_pop($params[$fieldName]); } } else { $value = array_pop($params[$fieldName]); } } if ($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') { if ($value === true || $value === $field->getAttribute('value')) { $this->checkOption($field); } else { $this->uncheckOption($field); } } elseif ($field->getAttribute('type') === 'button' || $field->getAttribute('type') === 'submit') { continue; } elseif ($field->getTagName() === 'select') { $this->selectOption($field, $value); } else { $this->fillField($field, $value); } } $this->debugSection( 'Uri', $form->getAttribute('action') ? $form->getAttribute('action') : $this->_getCurrentUri() ); $this->debugSection('Method', $form->getAttribute('method') ? $form->getAttribute('method') : 'GET'); $this->debugSection('Parameters', json_encode($params)); $submitted = false; if (!empty($button)) { if (is_array($button)) { $buttonSelector = $this->getStrictLocator($button); } elseif ($button instanceof WebDriverBy) { $buttonSelector = $button; } else { $buttonSelector = WebDriverBy::name($button); } $els = $form->findElements($buttonSelector); if (!empty($els)) { $el = reset($els); $el->click(); $submitted = true; } } if (!$submitted) { $form->submit(); } $this->debugSection('Page', $this->_getCurrentUri()); } /** * Waits up to $timeout seconds for the given element to change. * Element "change" is determined by a callback function which is called repeatedly * until the return value evaluates to true. * * ``` php * <?php * use \Facebook\WebDriver\WebDriverElement * $I->waitForElementChange('#menu', function(WebDriverElement $el) { * return $el->isDisplayed(); * }, 100); * ?> * ``` * * @param $element * @param \Closure $callback * @param int $timeout seconds * @throws \Codeception\Exception\ElementNotFound */ public function waitForElementChange($element, \Closure $callback, $timeout = 30) { $el = $this->matchFirstOrFail($this->baseElement, $element); $checker = function () use ($el, $callback) { return $callback($el); }; $this->webDriver->wait($timeout)->until($checker); } /** * Waits up to $timeout seconds for an element to appear on the page. * If the element doesn't appear, a timeout exception is thrown. * * ``` php * <?php * $I->waitForElement('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElement($element, $timeout = 10) { $condition = WebDriverExpectedCondition::presenceOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to be visible on the page. * If element doesn't appear, a timeout exception is thrown. * * ``` php * <?php * $I->waitForElementVisible('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementVisible($element, $timeout = 10) { $condition = WebDriverExpectedCondition::visibilityOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to become invisible. * If element stays visible, a timeout exception is thrown. * * ``` php * <?php * $I->waitForElementNotVisible('#agree_button', 30); // secs * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementNotVisible($element, $timeout = 10) { $condition = WebDriverExpectedCondition::invisibilityOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given string to appear on the page. * * Can also be passed a selector to search in, be as specific as possible when using selectors. * waitForText() will only watch the first instance of the matching selector / text provided. * If the given text doesn't appear, a timeout exception is thrown. * * ``` php * <?php * $I->waitForText('foo', 30); // secs * $I->waitForText('foo', 30, '.title'); // secs * ?> * ``` * * @param string $text * @param int $timeout seconds * @param null $selector * @throws \Exception */ public function waitForText($text, $timeout = 10, $selector = null) { $message = sprintf( 'Waited for %d secs but text %s still not found', $timeout, Locator::humanReadableString($text) ); if (!$selector) { $condition = WebDriverExpectedCondition::textToBePresentInElement(WebDriverBy::xpath('//body'), $text); $this->webDriver->wait($timeout)->until($condition, $message); return; } $condition = WebDriverExpectedCondition::textToBePresentInElement($this->getLocator($selector), $text); $this->webDriver->wait($timeout)->until($condition, $message); } /** * Wait for $timeout seconds. * * @param int $timeout secs * @throws \Codeception\Exception\TestRuntimeException */ public function wait($timeout) { if ($timeout >= 1000) { throw new TestRuntimeException( " Waiting for more then 1000 seconds: 16.6667 mins\n Please note that wait method accepts number of seconds as parameter." ); } sleep($timeout); } /** * Low-level API method. * If Codeception commands are not enough, this allows you to use Selenium WebDriver methods directly: * * ``` php * $I->executeInSelenium(function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { * $webdriver->get('http://google.com'); * }); * ``` * * This runs in the context of the * [RemoteWebDriver class](https://github.com/facebook/php-webdriver/blob/master/lib/remote/RemoteWebDriver.php). * Try not to use this command on a regular basis. * If Codeception lacks a feature you need, please implement it and submit a patch. * * @param callable $function */ public function executeInSelenium(\Closure $function) { return $function($this->webDriver); } /** * Switch to another window identified by name. * * The window can only be identified by name. If the $name parameter is blank, the parent window will be used. * * Example: * ``` html * <input type="button" value="Open window" onclick="window.open('http://example.com', 'another_window')"> * ``` * * ``` php * <?php * $I->click("Open window"); * # switch to another window * $I->switchToWindow("another_window"); * # switch to parent window * $I->switchToWindow(); * ?> * ``` * * If the window has no name, match it by switching to next active tab using `switchToNextTab` method. * * Or use native Selenium functions to get access to all opened windows: * * ``` php * <?php * $I->executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { * $handles=$webdriver->getWindowHandles(); * $last_window = end($handles); * $webdriver->switchTo()->window($last_window); * }); * ?> * ``` * * @param string|null $name */ public function switchToWindow($name = null) { $this->webDriver->switchTo()->window($name); } /** * Switch to another frame on the page. * * Example: * ``` html * <iframe name="another_frame" src="http://example.com"> * * ``` * * ``` php * <?php * # switch to iframe * $I->switchToIFrame("another_frame"); * # switch to parent page * $I->switchToIFrame(); * * ``` * * @param string|null $name */ public function switchToIFrame($name = null) { if (is_null($name)) { $this->webDriver->switchTo()->defaultContent(); return; } $this->webDriver->switchTo()->frame($name); } /** * Executes JavaScript and waits up to $timeout seconds for it to return true. * * In this example we will wait up to 60 seconds for all jQuery AJAX requests to finish. * * ``` php * <?php * $I->waitForJS("return $.active == 0;", 60); * ?> * ``` * * @param string $script * @param int $timeout seconds */ public function waitForJS($script, $timeout = 5) { $condition = function ($wd) use ($script) { return $wd->executeScript($script); }; $message = sprintf( 'Waited for %d secs but script %s still not executed', $timeout, Locator::humanReadableString($script) ); $this->webDriver->wait($timeout)->until($condition, $message); } /** * Executes custom JavaScript. * * This example uses jQuery to get a value and assigns that value to a PHP variable: * * ```php * <?php * $myVar = $I->executeJS('return $("#myField").val()'); * ?> * ``` * * @param $script * @return mixed */ public function executeJS($script) { return $this->webDriver->executeScript($script); } /** * Maximizes the current window. */ public function maximizeWindow() { $this->webDriver->manage()->window()->maximize(); } /** * Performs a simple mouse drag-and-drop operation. * * ``` php * <?php * $I->dragAndDrop('#drag', '#drop'); * ?> * ``` * * @param string $source (CSS ID or XPath) * @param string $target (CSS ID or XPath) */ public function dragAndDrop($source, $target) { $snodes = $this->matchFirstOrFail($this->baseElement, $source); $tnodes = $this->matchFirstOrFail($this->baseElement, $target); $action = new WebDriverActions($this->webDriver); $action->dragAndDrop($snodes, $tnodes)->perform(); } /** * Move mouse over the first element matched by the given locator. * If the first parameter null then the page is used. * If the second and third parameters are given, * then the mouse is moved to an offset of the element's top-left corner. * Otherwise, the mouse is moved to the center of the element. * * ``` php * <?php * $I->moveMouseOver(['css' => '.checkout']); * $I->moveMouseOver(null, 20, 50); * $I->moveMouseOver(['css' => '.checkout'], 20, 50); * ?> * ``` * * @param string $cssOrXPath css or xpath of the web element * @param int $offsetX * @param int $offsetY * * @throws \Codeception\Exception\ElementNotFound */ public function moveMouseOver($cssOrXPath = null, $offsetX = null, $offsetY = null) { $where = null; if (null !== $cssOrXPath) { $el = $this->matchFirstOrFail($this->baseElement, $cssOrXPath); $where = $el->getCoordinates(); } $this->webDriver->getMouse()->mouseMove($where, $offsetX, $offsetY); } /** * Performs click with the left mouse button on an element. * If the first parameter `null` then the offset is relative to the actual mouse position. * If the second and third parameters are given, * then the mouse is moved to an offset of the element's top-left corner. * Otherwise, the mouse is moved to the center of the element. * * ``` php * <?php * $I->clickWithLeftButton(['css' => '.checkout']); * $I->clickWithLeftButton(null, 20, 50); * $I->clickWithLeftButton(['css' => '.checkout'], 20, 50); * ?> * ``` * * @param string $cssOrXPath css or xpath of the web element (body by default). * @param int $offsetX * @param int $offsetY * * @throws \Codeception\Exception\ElementNotFound */ public function clickWithLeftButton($cssOrXPath = null, $offsetX = null, $offsetY = null) { $this->moveMouseOver($cssOrXPath, $offsetX, $offsetY); $this->webDriver->getMouse()->click(); } /** * Performs contextual click with the right mouse button on an element. * If the first parameter `null` then the offset is relative to the actual mouse position. * If the second and third parameters are given, * then the mouse is moved to an offset of the element's top-left corner. * Otherwise, the mouse is moved to the center of the element. * * ``` php * <?php * $I->clickWithRightButton(['css' => '.checkout']); * $I->clickWithRightButton(null, 20, 50); * $I->clickWithRightButton(['css' => '.checkout'], 20, 50); * ?> * ``` * * @param string $cssOrXPath css or xpath of the web element (body by default). * @param int $offsetX * @param int $offsetY * * @throws \Codeception\Exception\ElementNotFound */ public function clickWithRightButton($cssOrXPath = null, $offsetX = null, $offsetY = null) { $this->moveMouseOver($cssOrXPath, $offsetX, $offsetY); $this->webDriver->getMouse()->contextClick(); } /** * Pauses test execution in debug mode. * To proceed test press "ENTER" in console. * * This method is useful while writing tests, * since it allows you to inspect the current page in the middle of a test case. */ public function pauseExecution() { Debug::pause(); } /** * Performs a double-click on an element matched by CSS or XPath. * * @param $cssOrXPath * @throws \Codeception\Exception\ElementNotFound */ public function doubleClick($cssOrXPath) { $el = $this->matchFirstOrFail($this->baseElement, $cssOrXPath); $this->webDriver->getMouse()->doubleClick($el->getCoordinates()); } /** * @param $page * @param $selector * @param bool $throwMalformed * @return array */ protected function match($page, $selector, $throwMalformed = true) { if (is_array($selector)) { try { return $page->findElements($this->getStrictLocator($selector)); } catch (InvalidSelectorException $e) { throw new MalformedLocatorException(key($selector) . ' => ' . reset($selector), "Strict locator"); } catch (InvalidElementStateException $e) { if ($this->isPhantom() and $e->getResults()['status'] == 12) { throw new MalformedLocatorException( key($selector) . ' => ' . reset($selector), "Strict locator ".$e->getCode() ); } } } if ($selector instanceof WebDriverBy) { try { return $page->findElements($selector); } catch (InvalidSelectorException $e) { throw new MalformedLocatorException( sprintf( "WebDriverBy::%s('%s')", $selector->getMechanism(), $selector->getValue() ), 'WebDriver' ); } } $isValidLocator = false; $nodes = []; try { if (Locator::isID($selector)) { $isValidLocator = true; $nodes = $page->findElements(WebDriverBy::id(substr($selector, 1))); } if (Locator::isClass($selector)) { $isValidLocator = true; $nodes = $page->findElements(WebDriverBy::className(substr($selector, 1))); } if (empty($nodes) and Locator::isCSS($selector)) { $isValidLocator = true; try { $nodes = $page->findElements(WebDriverBy::cssSelector($selector)); } catch (InvalidElementStateException $e) { $nodes = $page->findElements(WebDriverBy::linkText($selector)); } } if (empty($nodes) and Locator::isXPath($selector)) { $isValidLocator = true; $nodes = $page->findElements(WebDriverBy::xpath($selector)); } } catch (InvalidSelectorException $e) { throw new MalformedLocatorException($selector); } if (!$isValidLocator and $throwMalformed) { throw new MalformedLocatorException($selector); } return $nodes; } /** * @param array $by * @return WebDriverBy */ protected function getStrictLocator(array $by) { $type = key($by); $locator = $by[$type]; switch ($type) { case 'id': return WebDriverBy::id($locator); case 'name': return WebDriverBy::name($locator); case 'css': return WebDriverBy::cssSelector($locator); case 'xpath': return WebDriverBy::xpath($locator); case 'link': return WebDriverBy::linkText($locator); case 'class': return WebDriverBy::className($locator); default: throw new MalformedLocatorException( "$by => $locator", "Strict locator can be either xpath, css, id, link, class, name: " ); } } /** * @param $page * @param $selector * @return WebDriverElement * @throws \Codeception\Exception\ElementNotFound */ protected function matchFirstOrFail($page, $selector) { $els = $this->match($page, $selector); if (!count($els)) { throw new ElementNotFound($selector, "CSS or XPath"); } return reset($els); } /** * Presses the given key on the given element. * To specify a character and modifier (e.g. ctrl, alt, shift, meta), pass an array for $char with * the modifier as the first element and the character as the second. * For special keys use key constants from WebDriverKeys class. * * ``` php * <?php * // <input id="page" value="old" /> * $I->pressKey('#page','a'); // => olda * $I->pressKey('#page',array('ctrl','a'),'new'); //=> new * $I->pressKey('#page',array('shift','111'),'1','x'); //=> old!!!1x * $I->pressKey('descendant-or-self::*[@id='page']','u'); //=> oldu * $I->pressKey('#name', array('ctrl', 'a'), \Facebook\WebDriver\WebDriverKeys::DELETE); //=>'' * ?> * ``` * * @param $element * @param $char string|array Can be char or array with modifier. You can provide several chars. * @throws \Codeception\Exception\ElementNotFound */ public function pressKey($element, $char) { $el = $this->matchFirstOrFail($this->baseElement, $element); $args = func_get_args(); array_shift($args); $keys = []; foreach ($args as $key) { $keys[] = $this->convertKeyModifier($key); } $el->sendKeys($keys); } protected function convertKeyModifier($keys) { if (!is_array($keys)) { return $keys; } if (!isset($keys[1])) { return $keys; } list($modifier, $key) = $keys; switch ($modifier) { case 'ctrl': case 'control': return [WebDriverKeys::CONTROL, $key]; case 'alt': return [WebDriverKeys::ALT, $key]; case 'shift': return [WebDriverKeys::SHIFT, $key]; case 'meta': return [WebDriverKeys::META, $key]; } return $keys; } protected function assertNodesContain($text, $nodes, $selector = null) { $this->assertThat($nodes, new WebDriverConstraint($text, $this->_getCurrentUri()), $selector); } protected function assertNodesNotContain($text, $nodes, $selector = null) { $this->assertThat($nodes, new WebDriverConstraintNot($text, $this->_getCurrentUri()), $selector); } protected function assertPageContains($needle, $message = '') { $this->assertThat( htmlspecialchars_decode($this->getVisibleText()), new PageConstraint($needle, $this->_getCurrentUri()), $message ); } protected function assertPageNotContains($needle, $message = '') { $this->assertThatItsNot( htmlspecialchars_decode($this->getVisibleText()), new PageConstraint($needle, $this->_getCurrentUri()), $message ); } protected function assertPageSourceContains($needle, $message = '') { $this->assertThat( $this->webDriver->getPageSource(), new PageConstraint($needle, $this->_getCurrentUri()), $message ); } protected function assertPageSourceNotContains($needle, $message = '') { $this->assertThatItsNot( $this->webDriver->getPageSource(), new PageConstraint($needle, $this->_getCurrentUri()), $message ); } /** * Append the given text to the given element. * Can also add a selection to a select box. * * ``` php * <?php * $I->appendField('#mySelectbox', 'SelectValue'); * $I->appendField('#myTextField', 'appended'); * ?> * ``` * * @param string $field * @param string $value * @throws \Codeception\Exception\ElementNotFound */ public function appendField($field, $value) { $el = $this->findField($field); switch ($el->getTagName()) { //Multiple select case "select": $matched = false; $wdSelect = new WebDriverSelect($el); try { $wdSelect->selectByVisibleText($value); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } try { $wdSelect->selectByValue($value); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } if ($matched) { return; } throw new ElementNotFound(json_encode($value), "Option inside $field matched by name or value"); case "textarea": $el->sendKeys($value); return; case "div": //allows for content editable divs $el->sendKeys(WebDriverKeys::END); $el->sendKeys($value); return; //Text, Checkbox, Radio case "input": $type = $el->getAttribute('type'); if ($type == 'checkbox') { //Find by value or css,id,xpath $field = $this->findCheckable($this->baseElement, $value, true); if (!$field) { throw new ElementNotFound($value, "Checkbox or Radio by Label or CSS or XPath"); } if ($field->isSelected()) { return; } $field->click(); return; } elseif ($type == 'radio') { $this->selectOption($field, $value); return; } else { $el->sendKeys($value); return; } } throw new ElementNotFound($field, "Field by name, label, CSS or XPath"); } /** * @param $selector * @return array */ protected function matchVisible($selector) { $els = $this->match($this->baseElement, $selector); $nodes = array_filter( $els, function (WebDriverElement $el) { return $el->isDisplayed(); } ); return $nodes; } /** * @param $selector * @return WebDriverBy * @throws \InvalidArgumentException */ protected function getLocator($selector) { if ($selector instanceof WebDriverBy) { return $selector; } if (is_array($selector)) { return $this->getStrictLocator($selector); } if (Locator::isID($selector)) { return WebDriverBy::id(substr($selector, 1)); } if (Locator::isCSS($selector)) { return WebDriverBy::cssSelector($selector); } if (Locator::isXPath($selector)) { return WebDriverBy::xpath($selector); } throw new \InvalidArgumentException("Only CSS or XPath allowed"); } /** * @param string $name */ public function saveSessionSnapshot($name) { $this->sessionSnapshots[$name] = []; foreach ($this->webDriver->manage()->getCookies() as $cookie) { if (in_array(trim($cookie['name']), [LocalServer::COVERAGE_COOKIE, LocalServer::COVERAGE_COOKIE_ERROR])) { continue; } if ($this->cookieDomainMatchesConfigUrl($cookie)) { $this->sessionSnapshots[$name][] = $cookie; } } $this->debugSection('Snapshot', "Saved \"$name\" session snapshot"); } /** * @param string $name * @return bool */ public function loadSessionSnapshot($name) { if (!isset($this->sessionSnapshots[$name])) { return false; } foreach ($this->sessionSnapshots[$name] as $cookie) { $this->webDriver->manage()->addCookie($cookie); } $this->debugSection('Snapshot', "Restored \"$name\" session snapshot"); return true; } /** * Check if the cookie domain matches the config URL. * * @param array|Cookie $cookie * @return bool */ private function cookieDomainMatchesConfigUrl($cookie) { if (!array_key_exists('domain', $cookie)) { return true; } $setCookie = new SetCookie(); $setCookie->setDomain($cookie['domain']); return $setCookie->matchesDomain(parse_url($this->config['url'], PHP_URL_HOST)); } /** * @return bool */ protected function isPhantom() { return strpos($this->config['browser'], 'phantom') === 0; } /** * Move to the middle of the given element matched by the given locator. * Extra shift, calculated from the top-left corner of the element, * can be set by passing $offsetX and $offsetY parameters. * * ``` php * <?php * $I->scrollTo(['css' => '.checkout'], 20, 50); * ?> * ``` * * @param $selector * @param int $offsetX * @param int $offsetY */ public function scrollTo($selector, $offsetX = null, $offsetY = null) { $el = $this->matchFirstOrFail($this->baseElement, $selector); $x = $el->getLocation()->getX() + $offsetX; $y = $el->getLocation()->getY() + $offsetY; $this->webDriver->executeScript("window.scrollTo($x, $y)"); } /** * Opens a new browser tab (wherever it is possible) and switches to it. * * ```php * <?php * $I->openNewTab(); * ``` * Tab is opened by using `window.open` javascript in a browser. * Please note, that adblock can restrict creating such tabs. * * Can't be used with PhantomJS * */ public function openNewTab() { $this->executeJS("window.open('about:blank','_blank');"); $this->switchToNextTab(); } /** * Closes current browser tab and switches to previous active tab. * * ```php * <?php * $I->closeTab(); * ``` * * Can't be used with PhantomJS */ public function closeTab() { $prevTab = $this->getRelativeTabHandle(-1); $this->webDriver->close(); $this->webDriver->switchTo()->window($prevTab); } /** * Switches to next browser tab. * An offset can be specified. * * ```php * <?php * // switch to next tab * $I->switchToNextTab(); * // switch to 2nd next tab * $I->switchToNextTab(2); * ``` * * Can't be used with PhantomJS * * @param int $offset 1 */ public function switchToNextTab($offset = 1) { $tab = $this->getRelativeTabHandle($offset); $this->webDriver->switchTo()->window($tab); } /** * Switches to next browser tab. * An offset can be specified. * * ```php * <?php * // switch to previous tab * $I->switchToNextTab(); * // switch to 2nd previous tab * $I->switchToNextTab(-2); * ``` * * Can't be used with PhantomJS * * @param int $offset 1 */ public function switchToPreviousTab($offset = 1) { $this->switchToNextTab(0 - $offset); } protected function getRelativeTabHandle($offset) { if ($this->isPhantom()) { throw new ModuleException($this, "PhantomJS doesn't support tab actions"); } $handle = $this->webDriver->getWindowHandle(); $handles = $this->webDriver->getWindowHandles(); $idx = array_search($handle, $handles); return $handles[($idx + $offset) % count($handles)]; } /** * Waits for element and runs a sequence of actions inside its context. * Actions can be defined with array, callback, or `Codeception\Util\ActionSequence` instance. * * Actions as array are recommended for simple to combine "waitForElement" with assertions; * `waitForElement($el)` and `see('text', $el)` can be simplified to: * * ```php * <?php * $I->performOn($el, ['see' => 'text']); * ``` * * List of actions can be pragmatically build using `Codeception\Util\ActionSequence`: * * ```php * <?php * $I->performOn('.model', ActionSequence::build() * ->see('Warning') * ->see('Are you sure you want to delete this?') * ->click('Yes') * ); * ``` * * Actions executed from array or ActionSequence will print debug output for actions, and adds an action name to * exception on failure. * * Whenever you need to define more actions a callback can be used. A WebDriver module is passed for argument: * * ```php * <?php * $I->performOn('.rememberMe', function (WebDriver $I) { * $I->see('Remember me next time'); * $I->seeElement('#LoginForm_rememberMe'); * $I->dontSee('Login'); * }); * ``` * * In 3rd argument you can set number a seconds to wait for element to appear * * @param $element * @param $actions * @param int $timeout */ public function performOn($element, $actions, $timeout = 10) { $this->waitForElement($element, $timeout); $this->setBaseElement($element); $this->debugSection('InnerText', $this->baseElement->getText()); if (is_callable($actions)) { $actions($this); $this->setBaseElement(); return; } if (is_array($actions)) { $actions = ActionSequence::build()->fromArray($actions); } if (!$actions instanceof ActionSequence) { throw new \InvalidArgumentException("2nd parameter, actions should be callback, ActionSequence or array"); } $actions->run($this); $this->setBaseElement(); } protected function setBaseElement($element = null) { if ($element === null) { $this->baseElement = $this->webDriver; return; } $this->baseElement = $this->matchFirstOrFail($this->webDriver, $element); } } <?php namespace Codeception\Module; use Codeception\Lib\Interfaces\API; use Codeception\Lib\Interfaces\DependsOnModule; use Codeception\Lib\Notification; use Codeception\Module as CodeceptionModule; use Codeception\TestInterface; use Codeception\Exception\ModuleException; use Codeception\Exception\ModuleRequireException; use Codeception\Lib\Framework; use Codeception\Lib\InnerBrowser; use Codeception\Util\Soap as SoapUtils; use Codeception\Util\XmlStructure; /** * Module for testing SOAP WSDL web services. * Send requests and check if response matches the pattern. * * This module can be used either with frameworks or PHPBrowser. * It tries to guess the framework is is attached to. * If a endpoint is a full url then it uses PHPBrowser. * * ### Using Inside Framework * * Please note, that PHP SoapServer::handle method sends additional headers. * This may trigger warning: "Cannot modify header information" * If you use PHP SoapServer with framework, try to block call to this method in testing environment. * * ## Status * * * Maintainer: **davert** * * Stability: **stable** * * Contact: codecept@davert.mail.ua * * ## Configuration * * * endpoint *required* - soap wsdl endpoint * * SOAPAction - replace SOAPAction HTTP header (Set to '' to SOAP 1.2) * * ## Public Properties * * * xmlRequest - last SOAP request (DOMDocument) * * xmlResponse - last SOAP response (DOMDocument) * */ class SOAP extends CodeceptionModule implements DependsOnModule { protected $config = [ 'schema' => "", 'schema_url' => 'http://schemas.xmlsoap.org/soap/envelope/', 'framework_collect_buffer' => true ]; protected $requiredFields = ['endpoint']; protected $dependencyMessage = <<<EOF Example using PhpBrowser as backend for SOAP module. -- modules: enabled: - SOAP: depends: PhpBrowser -- Framework modules can be used as well for functional testing of SOAP API. EOF; /** * @var \Symfony\Component\BrowserKit\Client */ public $client = null; public $isFunctional = false; /** * @var \DOMDocument */ public $xmlRequest = null; /** * @var \DOMDocument */ public $xmlResponse = null; /** * @var XmlStructure */ protected $xmlStructure = null; /** * @var InnerBrowser */ protected $connectionModule; public function _before(TestInterface $test) { $this->client = &$this->connectionModule->client; $this->buildRequest(); $this->xmlResponse = null; $this->xmlStructure = null; } protected function onReconfigure() { $this->buildRequest(); $this->xmlResponse = null; $this->xmlStructure = null; } public function _depends() { return ['Codeception\Lib\InnerBrowser' => $this->dependencyMessage]; } public function _inject(InnerBrowser $connectionModule) { $this->connectionModule = $connectionModule; if ($connectionModule instanceof Framework) { $this->isFunctional = true; } } private function getClient() { if (!$this->client) { throw new ModuleRequireException($this, "Connection client is not available."); } return $this->client; } private function getXmlResponse() { if (!$this->xmlResponse) { throw new ModuleException($this, "No XML response, use `\$I->sendSoapRequest` to receive it"); } return $this->xmlResponse; } private function getXmlStructure() { if (!$this->xmlStructure) { $this->xmlStructure = new XmlStructure($this->getXmlResponse()); } return $this->xmlStructure; } /** * Prepare SOAP header. * Receives header name and parameters as array. * * Example: * * ``` php * <?php * $I->haveSoapHeader('AuthHeader', array('username' => 'davert', 'password' => '123345')); * ``` * * Will produce header: * * ``` * <soapenv:Header> * <SessionHeader> * <AuthHeader> * <username>davert</username> * <password>12345</password> * </AuthHeader> * </soapenv:Header> * ``` * * @param $header * @param array $params */ public function haveSoapHeader($header, $params = []) { $soap_schema_url = $this->config['schema_url']; $xml = $this->xmlRequest; $xmlHeader = $xml->documentElement->getElementsByTagNameNS($soap_schema_url, 'Header')->item(0); $headerEl = $xml->createElement($header); SoapUtils::arrayToXml($xml, $headerEl, $params); $xmlHeader->appendChild($headerEl); } /** * Submits request to endpoint. * * Requires of api function name and parameters. * Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes). * * You are allowed to execute as much requests as you need inside test. * * Example: * * ``` php * $I->sendSoapRequest('UpdateUser', '<user><id>1</id><name>notdavert</name></user>'); * $I->sendSoapRequest('UpdateUser', \Codeception\Utils\Soap::request()->user * ->id->val(1)->parent() * ->name->val('notdavert'); * ``` * * @param $request * @param $body */ public function sendSoapRequest($action, $body = "") { $soap_schema_url = $this->config['schema_url']; $xml = $this->xmlRequest; $call = $xml->createElement('ns:' . $action); if ($body) { $bodyXml = SoapUtils::toXml($body); if ($bodyXml->hasChildNodes()) { foreach ($bodyXml->childNodes as $bodyChildNode) { $bodyNode = $xml->importNode($bodyChildNode, true); $call->appendChild($bodyNode); } } } $xmlBody = $xml->getElementsByTagNameNS($soap_schema_url, 'Body')->item(0); // cleanup if body already set foreach ($xmlBody->childNodes as $node) { $xmlBody->removeChild($node); } $xmlBody->appendChild($call); $this->debugSection("Request", $req = $xml->C14N()); if ($this->isFunctional && $this->config['framework_collect_buffer']) { $response = $this->processInternalRequest($action, $req); } else { $response = $this->processExternalRequest($action, $req); } $this->debugSection("Response", (string) $response); $this->xmlResponse = SoapUtils::toXml($response); $this->xmlStructure = null; } /** * Checks XML response equals provided XML. * Comparison is done by canonicalizing both xml`s. * * Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes). * * Example: * * ``` php * <?php * $I->seeSoapResponseEquals("<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope><SOAP-ENV:Body><result>1</result></SOAP-ENV:Envelope>"); * * $dom = new \DOMDocument(); * $dom->load($file); * $I->seeSoapRequestIncludes($dom); * * ``` * * @param $xml */ public function seeSoapResponseEquals($xml) { $xml = SoapUtils::toXml($xml); $this->assertEquals($xml->C14N(), $this->getXmlResponse()->C14N()); } /** * Checks XML response includes provided XML. * Comparison is done by canonicalizing both xml`s. * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes). * * Example: * * ``` php * <?php * $I->seeSoapResponseIncludes("<result>1</result>"); * $I->seeSoapRequestIncludes(\Codeception\Utils\Soap::response()->result->val(1)); * * $dom = new \DDOMDocument(); * $dom->load('template.xml'); * $I->seeSoapRequestIncludes($dom); * ?> * ``` * * @param $xml */ public function seeSoapResponseIncludes($xml) { $xml = $this->canonicalize($xml); $this->assertContains($xml, $this->getXmlResponse()->C14N(), "found in XML Response"); } /** * Checks XML response equals provided XML. * Comparison is done by canonicalizing both xml`s. * * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes). * * @param $xml */ public function dontSeeSoapResponseEquals($xml) { $xml = SoapUtils::toXml($xml); \PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlString($xml->C14N(), $this->getXmlResponse()->C14N()); } /** * Checks XML response does not include provided XML. * Comparison is done by canonicalizing both xml`s. * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes). * * @param $xml */ public function dontSeeSoapResponseIncludes($xml) { $xml = $this->canonicalize($xml); $this->assertNotContains($xml, $this->getXmlResponse()->C14N(), "found in XML Response"); } /** * Checks XML response contains provided structure. * Response elements will be compared with XML provided. * Only nodeNames are checked to see elements match. * * Example: * * ``` php * <?php * * $I->seeSoapResponseContainsStructure("<query><name></name></query>"); * ?> * ``` * * Use this method to check XML of valid structure is returned. * This method does not use schema for validation. * This method does not require path from root to match the structure. * * @param $xml */ public function seeSoapResponseContainsStructure($xml) { $xml = SoapUtils::toXml($xml); $this->debugSection("Structure", $xml->saveXML()); $this->assertTrue((bool)$this->getXmlStructure()->matchXmlStructure($xml), "this structure is in response"); } /** * Opposite to `seeSoapResponseContainsStructure` * @param $xml */ public function dontSeeSoapResponseContainsStructure($xml) { $xml = SoapUtils::toXml($xml); $this->debugSection("Structure", $xml->saveXML()); $this->assertFalse((bool)$this->getXmlStructure()->matchXmlStructure($xml), "this structure is in response"); } /** * Checks XML response with XPath locator * * ``` php * <?php * $I->seeSoapResponseContainsXPath('//root/user[@id=1]'); * ?> * ``` * * @param $xpath */ public function seeSoapResponseContainsXPath($xpath) { $this->assertTrue($this->getXmlStructure()->matchesXpath($xpath)); } /** * Checks XML response doesn't contain XPath locator * * ``` php * <?php * $I->dontSeeSoapResponseContainsXPath('//root/user[@id=1]'); * ?> * ``` * * @param $xpath */ public function dontSeeSoapResponseContainsXPath($xpath) { $this->assertFalse($this->getXmlStructure()->matchesXpath($xpath)); } /** * Checks response code from server. * * @param $code */ public function seeSoapResponseCodeIs($code) { $this->assertEquals( $code, $this->client->getInternalResponse()->getStatus(), "soap response code matches expected" ); } /** * @deprecated use seeSoapResponseCodeIs instead */ public function seeResponseCodeIs($code) { Notification::deprecate('SOAP::seeResponseCodeIs deprecated in favor of seeSoapResponseCodeIs', 'SOAP Module'); $this->seeSoapResponseCodeIs($code); } /** * Finds and returns text contents of element. * Element is matched by either CSS or XPath * * @version 1.1 * @param $cssOrXPath * @return string */ public function grabTextContentFrom($cssOrXPath) { $el = $this->getXmlStructure()->matchElement($cssOrXPath); return $el->textContent; } /** * Finds and returns attribute of element. * Element is matched by either CSS or XPath * * @version 1.1 * @param $cssOrXPath * @param $attribute * @return string */ public function grabAttributeFrom($cssOrXPath, $attribute) { $el = $this->getXmlStructure()->matchElement($cssOrXPath); if (!$el->hasAttribute($attribute)) { $this->fail("Attribute not found in element matched by '$cssOrXPath'"); } return $el->getAttribute($attribute); } protected function getSchema() { return $this->config['schema']; } protected function canonicalize($xml) { return SoapUtils::toXml($xml)->C14N(); } /** * @return \DOMDocument */ protected function buildRequest() { $soap_schema_url = $this->config['schema_url']; $xml = new \DOMDocument(); $root = $xml->createElement('soapenv:Envelope'); $xml->appendChild($root); $root->setAttribute('xmlns:ns', $this->getSchema()); $root->setAttribute('xmlns:soapenv', $soap_schema_url); $body = $xml->createElementNS($soap_schema_url, 'soapenv:Body'); $header = $xml->createElementNS($soap_schema_url, 'soapenv:Header'); $root->appendChild($header); $root->appendChild($body); $this->xmlRequest = $xml; return $xml; } protected function processRequest($action, $body) { $this->getClient()->request( 'POST', $this->config['endpoint'], [], [], [ 'HTTP_Content-Type' => 'text/xml; charset=UTF-8', 'HTTP_Content-Length' => strlen($body), 'HTTP_SOAPAction' => isset($this->config['SOAPAction']) ? $this->config['SOAPAction'] : $action ], $body ); } protected function processInternalRequest($action, $body) { ob_start(); try { $this->getClient()->setServerParameter('HTTP_HOST', 'localhost'); $this->processRequest($action, $body); } catch (\ErrorException $e) { // Zend_Soap outputs warning as an exception if (strpos($e->getMessage(), 'Warning: Cannot modify header information') === false) { ob_end_clean(); throw $e; } } $response = ob_get_contents(); ob_end_clean(); return $response; } protected function processExternalRequest($action, $body) { $this->processRequest($action, $body); return $this->client->getInternalResponse()->getContent(); } } <?php namespace Codeception\Module; use Codeception\Lib\Connector\Guzzle6; use Codeception\Lib\InnerBrowser; use Codeception\Lib\Interfaces\MultiSession; use Codeception\Lib\Interfaces\Remote; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\TestInterface; use Codeception\Util\Uri; use GuzzleHttp\Client as GuzzleClient; /** * Uses [Guzzle](http://guzzlephp.org/) to interact with your application over CURL. * Module works over CURL and requires **PHP CURL extension** to be enabled. * * Use to perform web acceptance tests with non-javascript browser. * * If test fails stores last shown page in 'output' dir. * * ## Status * * * Maintainer: **davert** * * Stability: **stable** * * Contact: codeception@codeception.com * * Works with [Guzzle](http://guzzlephp.org/) * * *Please review the code of non-stable modules and provide patches if you have issues.* * * ## Configuration * * * url *required* - start url of your app * * handler (default: curl) - Guzzle handler to use. By default curl is used, also possible to pass `stream`, or any valid class name as [Handler](http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html#handlers). * * middleware - Guzzle middlewares to add. An array of valid callables is required. * * curl - curl options * * headers - ... * * cookies - ... * * auth - ... * * verify - ... * * .. those and other [Guzzle Request options](http://docs.guzzlephp.org/en/latest/request-options.html) * * * ### Example (`acceptance.suite.yml`) * * modules: * enabled: * - PhpBrowser: * url: 'http://localhost' * auth: ['admin', '123345'] * curl: * CURLOPT_RETURNTRANSFER: true * cookies: * cookie-1: * Name: userName * Value: john.doe * cookie-2: * Name: authToken * Value: 1abcd2345 * Domain: subdomain.domain.com * Path: /admin/ * Expires: 1292177455 * Secure: true * HttpOnly: false * * * All SSL certification checks are disabled by default. * Use Guzzle request options to configure certifications and others. * * ## Public API * * Those properties and methods are expected to be used in Helper classes: * * Properties: * * * `guzzle` - contains [Guzzle](http://guzzlephp.org/) client instance: `\GuzzleHttp\Client` * * `client` - Symfony BrowserKit instance. * */ class PhpBrowser extends InnerBrowser implements Remote, MultiSession, RequiresPackage { private $isGuzzlePsr7; protected $requiredFields = ['url']; protected $config = [ 'verify' => false, 'expect' => false, 'timeout' => 30, 'curl' => [], 'refresh_max_interval' => 10, 'handler' => 'curl', 'middleware' => null, // required defaults (not recommended to change) 'allow_redirects' => false, 'http_errors' => false, 'cookies' => true, ]; protected $guzzleConfigFields = [ 'headers', 'auth', 'proxy', 'verify', 'cert', 'query', 'ssl_key', 'proxy', 'expect', 'version', 'timeout', 'connect_timeout' ]; /** * @var \Codeception\Lib\Connector\Guzzle6 */ public $client; /** * @var GuzzleClient */ public $guzzle; public function _requires() { return ['GuzzleHttp\Client' => '"guzzlehttp/guzzle": ">=4.1.4 <7.0"']; } public function _initialize() { $this->_initializeSession(); } protected function guessGuzzleConnector() { if (class_exists('GuzzleHttp\Url')) { $this->isGuzzlePsr7 = false; return new \Codeception\Lib\Connector\Guzzle(); } $this->isGuzzlePsr7 = true; return new \Codeception\Lib\Connector\Guzzle6(); } public function _before(TestInterface $test) { if (!$this->client) { $this->client = $this->guessGuzzleConnector(); } $this->_prepareSession(); } public function _getUrl() { return $this->config['url']; } /** * Alias to `haveHttpHeader` * * @param $name * @param $value */ public function setHeader($name, $value) { $this->haveHttpHeader($name, $value); } public function amHttpAuthenticated($username, $password) { $this->client->setAuth($username, $password); } public function amOnUrl($url) { $host = Uri::retrieveHost($url); $this->_reconfigure(['url' => $host]); $page = substr($url, strlen($host)); $this->debugSection('Host', $host); $this->amOnPage($page); } public function amOnSubdomain($subdomain) { $url = $this->config['url']; $url = preg_replace('~(https?:\/\/)(.*\.)(.*\.)~', "$1$3", $url); // removing current subdomain $url = preg_replace('~(https?:\/\/)(.*)~', "$1$subdomain.$2", $url); // inserting new $this->_reconfigure(['url' => $url]); } protected function onReconfigure() { $this->_prepareSession(); } /** * Low-level API method. * If Codeception commands are not enough, use [Guzzle HTTP Client](http://guzzlephp.org/) methods directly * * Example: * * ``` php * <?php * $I->executeInGuzzle(function (\GuzzleHttp\Client $client) { * $client->get('/get', ['query' => ['foo' => 'bar']]); * }); * ?> * ``` * * It is not recommended to use this command on a regular basis. * If Codeception lacks important Guzzle Client methods, implement them and submit patches. * * @param callable $function */ public function executeInGuzzle(\Closure $function) { return $function($this->guzzle); } public function _getResponseCode() { return $this->getResponseStatusCode(); } public function _initializeSession() { // independent sessions need independent cookies $this->client = $this->guessGuzzleConnector(); $this->_prepareSession(); } public function _prepareSession() { $defaults = array_intersect_key($this->config, array_flip($this->guzzleConfigFields)); $curlOptions = []; foreach ($this->config['curl'] as $key => $val) { if (defined($key)) { $curlOptions[constant($key)] = $val; } } $this->setCookiesFromOptions(); if ($this->isGuzzlePsr7) { $defaults['base_uri'] = $this->config['url']; $defaults['curl'] = $curlOptions; $handler = Guzzle6::createHandler($this->config['handler']); if ($handler && is_array($this->config['middleware'])) { foreach ($this->config['middleware'] as $middleware) { $handler->push($middleware); } } $defaults['handler'] = $handler; $this->guzzle = new GuzzleClient($defaults); } else { $defaults['config']['curl'] = $curlOptions; $this->guzzle = new GuzzleClient(['base_url' => $this->config['url'], 'defaults' => $defaults]); $this->client->setBaseUri($this->config['url']); } $this->client->setRefreshMaxInterval($this->config['refresh_max_interval']); $this->client->setClient($this->guzzle); } public function _backupSession() { return [ 'client' => $this->client, 'guzzle' => $this->guzzle, 'crawler' => $this->crawler, 'headers' => $this->headers, ]; } public function _loadSession($session) { foreach ($session as $key => $val) { $this->$key = $val; } } public function _closeSession($session) { unset($session); } } <?php namespace Codeception\Module; use Phalcon\Di; use PDOException; use Phalcon\Mvc\Url; use Phalcon\DiInterface; use Phalcon\Di\Injectable; use Codeception\TestInterface; use Codeception\Configuration; use Codeception\Lib\Framework; use Phalcon\Mvc\RouterInterface; use Phalcon\Mvc\Model as PhalconModel; use Phalcon\Mvc\Router\RouteInterface; use Codeception\Util\ReflectionHelper; use Codeception\Exception\ModuleException; use Codeception\Lib\Interfaces\ActiveRecord; use Codeception\Lib\Interfaces\PartedModule; use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Connector\Phalcon as PhalconConnector; /** * This module provides integration with [Phalcon framework](http://www.phalconphp.com/) (3.x). * Please try it and leave your feedback. * * ## Demo Project * * <https://github.com/Codeception/phalcon-demo> * * ## Status * * * Maintainer: **Serghei Iakovlev** * * Stability: **stable** * * Contact: serghei@phalconphp.com * * ## Config * * The following configurations are required for this module: * * * bootstrap: `string`, default `app/config/bootstrap.php` - relative path to app.php config file * * cleanup: `boolean`, default `true` - all database queries will be run in a transaction, * which will be rolled back at the end of each test * * savepoints: `boolean`, default `true` - use savepoints to emulate nested transactions * * The application bootstrap file must return Application object but not call its handle() method. * * ## API * * * di - `Phalcon\Di\Injectable` instance * * client - `BrowserKit` client * * ## Parts * * By default all available methods are loaded, but you can specify parts to select only needed * actions and avoid conflicts. * * * `orm` - include only `haveRecord/grabRecord/seeRecord/dontSeeRecord` actions. * * `services` - allows to use `grabServiceFromContainer` and `addServiceToContainer`. * * Usage example: * * Sample bootstrap (`app/config/bootstrap.php`): * * ``` php * <?php * $config = include __DIR__ . "/config.php"; * include __DIR__ . "/loader.php"; * $di = new \Phalcon\DI\FactoryDefault(); * include __DIR__ . "/services.php"; * return new \Phalcon\Mvc\Application($di); * ?> * ``` * * ```yaml * actor: AcceptanceTester * modules: * enabled: * - Phalcon: * part: services * bootstrap: 'app/config/bootstrap.php' * cleanup: true * savepoints: true * - WebDriver: * url: http://your-url.com * browser: phantomjs * ``` */ class Phalcon extends Framework implements ActiveRecord, PartedModule { protected $config = [ 'bootstrap' => 'app/config/bootstrap.php', 'cleanup' => true, 'savepoints' => true, ]; /** * Phalcon bootstrap file path */ protected $bootstrapFile = null; /** * Dependency injection container * @var DiInterface */ public $di = null; /** * Phalcon Connector * @var PhalconConnector */ public $client; /** * HOOK: used after configuration is loaded * * @throws ModuleConfigException */ public function _initialize() { $this->bootstrapFile = Configuration::projectDir() . $this->config['bootstrap']; if (!file_exists($this->bootstrapFile)) { throw new ModuleConfigException( __CLASS__, "Bootstrap file does not exist in " . $this->config['bootstrap'] . "\n" . "Please create the bootstrap file that returns Application object\n" . "And specify path to it with 'bootstrap' config\n\n" . "Sample bootstrap: \n\n<?php\n" . '$config = include __DIR__ . "/config.php";' . "\n" . 'include __DIR__ . "/loader.php";' . "\n" . '$di = new \Phalcon\DI\FactoryDefault();' . "\n" . 'include __DIR__ . "/services.php";' . "\n" . 'return new \Phalcon\Mvc\Application($di);' ); } $this->client = new PhalconConnector(); } /** * HOOK: before scenario * * @param TestInterface $test * @throws ModuleException */ public function _before(TestInterface $test) { /** @noinspection PhpIncludeInspection */ $application = require $this->bootstrapFile; if (!$application instanceof Injectable) { throw new ModuleException(__CLASS__, 'Bootstrap must return \Phalcon\Di\Injectable object'); } $this->di = $application->getDI(); Di::reset(); Di::setDefault($this->di); if ($this->di->has('session')) { // Destroy existing sessions of previous tests $this->di['session'] = new PhalconConnector\MemorySession(); } if ($this->di->has('cookies')) { $this->di['cookies']->useEncryption(false); } if ($this->config['cleanup'] && $this->di->has('db')) { if ($this->config['savepoints']) { $this->di['db']->setNestedTransactionsWithSavepoints(true); } $this->di['db']->begin(); } // localize $bootstrap = $this->bootstrapFile; $this->client->setApplication(function () use ($bootstrap) { $currentDi = Di::getDefault(); /** @noinspection PhpIncludeInspection */ $application = require $bootstrap; $di = $application->getDI(); if ($currentDi->has('db')) { $di['db'] = $currentDi['db']; } if ($currentDi->has('session')) { $di['session'] = $currentDi['session']; } if ($di->has('cookies')) { $di['cookies']->useEncryption(false); } return $application; }); } /** * HOOK: after scenario * * @param TestInterface $test */ public function _after(TestInterface $test) { if ($this->config['cleanup'] && isset($this->di['db'])) { while ($this->di['db']->isUnderTransaction()) { $level = $this->di['db']->getTransactionLevel(); try { $this->di['db']->rollback(true); } catch (PDOException $e) { } if ($level == $this->di['db']->getTransactionLevel()) { break; } } $this->di['db']->close(); } $this->di = null; Di::reset(); $_SESSION = $_FILES = $_GET = $_POST = $_COOKIE = $_REQUEST = []; } public function _parts() { return ['orm', 'services']; } /** * Provides access the Phalcon application object. * * @see \Codeception\Lib\Connector\Phalcon::getApplication * @return \Phalcon\Application|\Phalcon\Mvc\Micro */ public function getApplication() { return $this->client->getApplication(); } /** * Sets value to session. Use for authorization. * * @param string $key * @param mixed $val */ public function haveInSession($key, $val) { $this->di->get('session')->set($key, $val); $this->debugSection('Session', json_encode($this->di['session']->toArray())); } /** * Checks that session contains value. * If value is `null` checks that session has key. * * ``` php * <?php * $I->seeInSession('key'); * $I->seeInSession('key', 'value'); * ?> * ``` * * @param string $key * @param mixed $value */ public function seeInSession($key, $value = null) { $this->debugSection('Session', json_encode($this->di['session']->toArray())); if (is_array($key)) { $this->seeSessionHasValues($key); return; } if (!$this->di['session']->has($key)) { $this->fail("No session variable with key '$key'"); } if (is_null($value)) { $this->assertTrue($this->di['session']->has($key)); } else { $this->assertEquals($value, $this->di['session']->get($key)); } } /** * Assert that the session has a given list of values. * * ``` php * <?php * $I->seeSessionHasValues(['key1', 'key2']); * $I->seeSessionHasValues(['key1' => 'value1', 'key2' => 'value2']); * ?> * ``` * * @param array $bindings * @return void */ public function seeSessionHasValues(array $bindings) { foreach ($bindings as $key => $value) { if (is_int($key)) { $this->seeInSession($value); } else { $this->seeInSession($key, $value); } } } /** * Inserts record into the database. * * ``` php * <?php * $user_id = $I->haveRecord('App\Models\Users', ['name' => 'Phalcon']); * $I->haveRecord('App\Models\Categories', ['name' => 'Testing']'); * ?> * ``` * * @param string $model Model name * @param array $attributes Model attributes * @return mixed * @part orm */ public function haveRecord($model, $attributes = []) { $record = $this->getModelRecord($model); $res = $record->save($attributes); $field = function ($field) { if (is_array($field)) { return implode(', ', $field); } return $field; }; if (!$res) { $messages = $record->getMessages(); $errors = []; foreach ($messages as $message) { /** @var \Phalcon\Mvc\Model\MessageInterface $message */ $errors[] = sprintf( '[%s] %s: %s', $message->getType(), $field($message->getField()), $message->getMessage() ); } $this->fail(sprintf("Record %s was not saved. Messages: \n%s", $model, implode(PHP_EOL, $errors))); return null; } $this->debugSection($model, json_encode($record)); return $this->getModelIdentity($record); } /** * Checks that record exists in database. * * ``` php * <?php * $I->seeRecord('App\Models\Categories', ['name' => 'Testing']); * ?> * ``` * * @param string $model Model name * @param array $attributes Model attributes * @part orm */ public function seeRecord($model, $attributes = []) { $record = $this->findRecord($model, $attributes); if (!$record) { $this->fail("Couldn't find $model with " . json_encode($attributes)); } $this->debugSection($model, json_encode($record)); } /** * Checks that record does not exist in database. * * ``` php * <?php * $I->dontSeeRecord('App\Models\Categories', ['name' => 'Testing']); * ?> * ``` * * @param string $model Model name * @param array $attributes Model attributes * @part orm */ public function dontSeeRecord($model, $attributes = []) { $record = $this->findRecord($model, $attributes); $this->debugSection($model, json_encode($record)); if ($record) { $this->fail("Unexpectedly managed to find $model with " . json_encode($attributes)); } } /** * Retrieves record from database * * ``` php * <?php * $category = $I->grabRecord('App\Models\Categories', ['name' => 'Testing']); * ?> * ``` * * @param string $model Model name * @param array $attributes Model attributes * @return mixed * @part orm */ public function grabRecord($model, $attributes = []) { return $this->findRecord($model, $attributes); } /** * Resolves the service based on its configuration from Phalcon's DI container * Recommended to use for unit testing. * * @param string $service Service name * @param array $parameters Parameters [Optional] * @return mixed * @part services */ public function grabServiceFromContainer($service, array $parameters = []) { if (!$this->di->has($service)) { $this->fail("Service $service is not available in container"); } return $this->di->get($service, $parameters); } /** * Alias for `grabServiceFromContainer`. * * Note: Deprecated. Will be removed in Codeception 2.3. * * @param string $service Service name * @param array $parameters Parameters [Optional] * @return mixed * @part services */ public function grabServiceFromDi($service, array $parameters = []) { return $this->grabServiceFromContainer($service, $parameters); } /** * Registers a service in the services container and resolve it. This record will be erased after the test. * Recommended to use for unit testing. * * ``` php * <?php * $filter = $I->addServiceToContainer('filter', ['className' => '\Phalcon\Filter']); * $filter = $I->addServiceToContainer('answer', function () { * return rand(0, 1) ? 'Yes' : 'No'; * }, true); * ?> * ``` * * @param string $name * @param mixed $definition * @param boolean $shared * @return mixed|null * @part services */ public function addServiceToContainer($name, $definition, $shared = false) { try { $service = $this->di->set($name, $definition, $shared); return $service->resolve(); } catch (\Exception $e) { $this->fail($e->getMessage()); return null; } } /** * Alias for `addServiceToContainer`. * * Note: Deprecated. Will be removed in Codeception 2.3. * * @param string $name * @param mixed $definition * @param boolean $shared * @return mixed|null * @part services */ public function haveServiceInDi($name, $definition, $shared = false) { return $this->addServiceToContainer($name, $definition, $shared); } /** * Opens web page using route name and parameters. * * ``` php * <?php * $I->amOnRoute('posts.create'); * ?> * ``` * * @param string $routeName * @param array $params */ public function amOnRoute($routeName, array $params = []) { if (!$this->di->has('url')) { $this->fail('Unable to resolve "url" service.'); } /** @var Url $url */ $url = $this->di->getShared('url'); $urlParams = ['for' => $routeName]; if ($params) { $urlParams += $params; } $this->amOnPage($url->get($urlParams, null, true)); } /** * Checks that current url matches route * * ``` php * <?php * $I->seeCurrentRouteIs('posts.index'); * ?> * ``` * @param string $routeName */ public function seeCurrentRouteIs($routeName) { if (!$this->di->has('url')) { $this->fail('Unable to resolve "url" service.'); } /** @var Url $url */ $url = $this->di->getShared('url'); $this->seeCurrentUrlEquals($url->get(['for' => $routeName], null, true)); } /** * Allows to query the first record that match the specified conditions * * @param string $model Model name * @param array $attributes Model attributes * * @return \Phalcon\Mvc\Model */ protected function findRecord($model, $attributes = []) { $this->getModelRecord($model); $query = []; foreach ($attributes as $key => $value) { $query[] = "$key = '$value'"; } $squery = implode(' AND ', $query); $this->debugSection('Query', $squery); return call_user_func_array([$model, 'findFirst'], [$squery]); } /** * Get Model Record * * @param $model * * @return \Phalcon\Mvc\Model * @throws ModuleException */ protected function getModelRecord($model) { if (!class_exists($model)) { throw new ModuleException(__CLASS__, "Model $model does not exist"); } $record = new $model; if (!$record instanceof PhalconModel) { throw new ModuleException(__CLASS__, "Model $model is not instance of \\Phalcon\\Mvc\\Model"); } return $record; } /** * Get identity. * * @param \Phalcon\Mvc\Model $model * @return mixed */ protected function getModelIdentity(PhalconModel $model) { if (property_exists($model, 'id')) { return $model->id; } if (!$this->di->has('modelsMetadata')) { return null; } $primaryKeys = $this->di->get('modelsMetadata')->getPrimaryKeyAttributes($model); switch (count($primaryKeys)) { case 0: return null; case 1: return $model->{$primaryKeys[0]}; default: return array_intersect_key(get_object_vars($model), array_flip($primaryKeys)); } } /** * Returns a list of recognized domain names * * @return array */ protected function getInternalDomains() { $internalDomains = [$this->getApplicationDomainRegex()]; /** @var RouterInterface $router */ $router = $this->di->get('router'); if ($router instanceof RouterInterface) { /** @var RouteInterface[] $routes */ $routes = $router->getRoutes(); foreach ($routes as $route) { if ($route instanceof RouteInterface) { $hostName = $route->getHostname(); if (!empty($hostName)) { $internalDomains[] = '/^' . str_replace('.', '\.', $route->getHostname()) . '$/'; } } } } return array_unique($internalDomains); } /** * @return string */ private function getApplicationDomainRegex() { $server = ReflectionHelper::readPrivateProperty($this->client, 'server'); $domain = $server['HTTP_HOST']; return '/^' . str_replace('.', '\.', $domain) . '$/'; } } <?php namespace Codeception\Module; use Codeception\Lib\Interfaces\DataMapper; use Codeception\Module as CodeceptionModule; use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Interfaces\DependsOnModule; use Codeception\Lib\Interfaces\DoctrineProvider; use Codeception\TestInterface; use Codeception\Util\Stub; /** * Access the database using [Doctrine2 ORM](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/). * * When used with Zend Framework 2 or Symfony2, Doctrine's Entity Manager is automatically retrieved from Service Locator. * Set up your `functional.suite.yml` like this: * * ``` * modules: * enabled: * - Symfony # 'ZF2' or 'Symfony' * - Doctrine2: * depends: Symfony * cleanup: true # All doctrine queries will be wrapped in a transaction, which will be rolled back at the end of each test * ``` * * If you don't use Symfony or Zend Framework, you need to specify a callback function to retrieve the Entity Manager: * * ``` * modules: * enabled: * - Doctrine2: * connection_callback: ['MyDb', 'createEntityManager'] * cleanup: true # All doctrine queries will be wrapped in a transaction, which will be rolled back at the end of each test * * ``` * * This will use static method of `MyDb::createEntityManager()` to establish the Entity Manager. * * By default, the module will wrap everything into a transaction for each test and roll it back afterwards. By doing this * tests will run much faster and will be isolated from each other. * * ## Status * * * Maintainer: **davert** * * Stability: **stable** * * Contact: codecept@davert.mail.ua * * ## Config * * ## Public Properties * * * `em` - Entity Manager */ class Doctrine2 extends CodeceptionModule implements DependsOnModule, DataMapper { protected $config = [ 'cleanup' => true, 'connection_callback' => false, 'depends' => null ]; protected $dependencyMessage = <<<EOF Provide connection_callback function to establish database connection and get Entity Manager: modules: enabled: - Doctrine2: connection_callback: [My\ConnectionClass, getEntityManager] Or set a dependent module, which can be either Symfony or ZF2 to get EM from service locator: modules: enabled: - Doctrine2: depends: Symfony EOF; /** * @var \Doctrine\ORM\EntityManagerInterface */ public $em = null; /** * @var \Codeception\Lib\Interfaces\DoctrineProvider */ private $dependentModule; public function _depends() { if ($this->config['connection_callback']) { return []; } return ['Codeception\Lib\Interfaces\DoctrineProvider' => $this->dependencyMessage]; } public function _inject(DoctrineProvider $dependentModule = null) { $this->dependentModule = $dependentModule; } public function _beforeSuite($settings = []) { $this->retrieveEntityManager(); } public function _before(TestInterface $test) { $this->retrieveEntityManager(); if ($this->config['cleanup']) { $this->em->getConnection()->beginTransaction(); } } protected function retrieveEntityManager() { if ($this->dependentModule) { $this->em = $this->dependentModule->_getEntityManager(); } else { if (is_callable($this->config['connection_callback'])) { $this->em = call_user_func($this->config['connection_callback']); } } if (!$this->em) { throw new ModuleConfigException( __CLASS__, "EntityManager can't be obtained.\n \n" . "Please specify either `connection_callback` config option\n" . "with callable which will return instance of EntityManager or\n" . "pass a dependent module which are Symfony or ZF2\n" . "to connect to Doctrine using Dependency Injection Container" ); } if (!($this->em instanceof \Doctrine\ORM\EntityManagerInterface)) { throw new ModuleConfigException( __CLASS__, "Connection object is not an instance of \\Doctrine\\ORM\\EntityManagerInterface.\n" . "Use `connection_callback` or dependent framework modules to specify one" ); } $this->em->getConnection()->connect(); } public function _after(TestInterface $test) { if (!$this->em instanceof \Doctrine\ORM\EntityManagerInterface) { return; } if ($this->config['cleanup'] && $this->em->getConnection()->isTransactionActive()) { try { $this->em->getConnection()->rollback(); } catch (\PDOException $e) { } } $this->clean(); $this->em->getConnection()->close(); } protected function clean() { $em = $this->em; $reflectedEm = new \ReflectionClass($em); if ($reflectedEm->hasProperty('repositories')) { $property = $reflectedEm->getProperty('repositories'); $property->setAccessible(true); $property->setValue($em, []); } $this->em->clear(); } /** * Performs $em->flush(); */ public function flushToDatabase() { $this->em->flush(); } /** * Adds entity to repository and flushes. You can redefine it's properties with the second parameter. * * Example: * * ``` php * <?php * $I->persistEntity(new \Entity\User, array('name' => 'Miles')); * $I->persistEntity($user, array('name' => 'Miles')); * ``` * * @param $obj * @param array $values */ public function persistEntity($obj, $values = []) { if ($values) { $reflectedObj = new \ReflectionClass($obj); foreach ($values as $key => $val) { $property = $reflectedObj->getProperty($key); $property->setAccessible(true); $property->setValue($obj, $val); } } $this->em->persist($obj); $this->em->flush(); } /** * Mocks the repository. * * With this action you can redefine any method of any repository. * Please, note: this fake repositories will be accessible through entity manager till the end of test. * * Example: * * ``` php * <?php * * $I->haveFakeRepository('Entity\User', array('findByUsername' => function($username) { return null; })); * * ``` * * This creates a stub class for Entity\User repository with redefined method findByUsername, * which will always return the NULL value. * * @param $classname * @param array $methods */ public function haveFakeRepository($classname, $methods = []) { $em = $this->em; $metadata = $em->getMetadataFactory()->getMetadataFor($classname); $customRepositoryClassName = $metadata->customRepositoryClassName; if (!$customRepositoryClassName) { $customRepositoryClassName = '\Doctrine\ORM\EntityRepository'; } $mock = Stub::make( $customRepositoryClassName, array_merge( [ '_entityName' => $metadata->name, '_em' => $em, '_class' => $metadata ], $methods ) ); $em->clear(); $reflectedEm = new \ReflectionClass($em); if ($reflectedEm->hasProperty('repositories')) { //Support doctrine versions before 2.4.0 $property = $reflectedEm->getProperty('repositories'); $property->setAccessible(true); $property->setValue($em, array_merge($property->getValue($em), [$classname => $mock])); } elseif ($reflectedEm->hasProperty('repositoryFactory')) { //For doctrine 2.4.0+ versions $repositoryFactoryProperty = $reflectedEm->getProperty('repositoryFactory'); $repositoryFactoryProperty->setAccessible(true); $repositoryFactory = $repositoryFactoryProperty->getValue($em); $reflectedRepositoryFactory = new \ReflectionClass($repositoryFactory); if ($reflectedRepositoryFactory->hasProperty('repositoryList')) { $repositoryListProperty = $reflectedRepositoryFactory->getProperty('repositoryList'); $repositoryListProperty->setAccessible(true); $repositoryListProperty->setValue( $repositoryFactory, [$classname => $mock] ); $repositoryFactoryProperty->setValue($em, $repositoryFactory); } else { $this->debugSection( 'Warning', 'Repository can\'t be mocked, the EventManager\'s repositoryFactory doesn\'t have "repositoryList" property' ); } } else { $this->debugSection( 'Warning', 'Repository can\'t be mocked, the EventManager class doesn\'t have "repositoryFactory" or "repositories" property' ); } } /** * Persists record into repository. * This method crates an entity, and sets its properties directly (via reflection). * Setters of entity won't be executed, but you can create almost any entity and save it to database. * Returns id using `getId` of newly created entity. * * ```php * $I->haveInRepository('Entity\User', array('name' => 'davert')); * ``` */ public function haveInRepository($entity, array $data) { $reflectedEntity = new \ReflectionClass($entity); $entityObject = $reflectedEntity->newInstance(); foreach ($reflectedEntity->getProperties() as $property) { /** @var $property \ReflectionProperty */ if (!isset($data[$property->name])) { continue; } $property->setAccessible(true); $property->setValue($entityObject, $data[$property->name]); } $this->em->persist($entityObject); $this->em->flush(); if (method_exists($entityObject, 'getId')) { $id = $entityObject->getId(); $this->debug("$entity entity created with id:$id"); return $id; } } /** * Flushes changes to database, and executes a query with parameters defined in an array. * You can use entity associations to build complex queries. * * Example: * * ``` php * <?php * $I->seeInRepository('AppBundle:User', array('name' => 'davert')); * $I->seeInRepository('User', array('name' => 'davert', 'Company' => array('name' => 'Codegyre'))); * $I->seeInRepository('Client', array('User' => array('Company' => array('name' => 'Codegyre'))); * ?> * ``` * * Fails if record for given criteria can\'t be found, * * @param $entity * @param array $params */ public function seeInRepository($entity, $params = []) { $res = $this->proceedSeeInRepository($entity, $params); $this->assert($res); } /** * Flushes changes to database and performs `findOneBy()` call for current repository. * * @param $entity * @param array $params */ public function dontSeeInRepository($entity, $params = []) { $res = $this->proceedSeeInRepository($entity, $params); $this->assertNot($res); } protected function proceedSeeInRepository($entity, $params = []) { // we need to store to database... $this->em->flush(); $data = $this->em->getClassMetadata($entity); $qb = $this->em->getRepository($entity)->createQueryBuilder('s'); $this->buildAssociationQuery($qb, $entity, 's', $params); $this->debug($qb->getDQL()); $res = $qb->getQuery()->getArrayResult(); return ['True', (count($res) > 0), "$entity with " . json_encode($params)]; } /** * Selects field value from repository. * It builds query based on array of parameters. * You can use entity associations to build complex queries. * * Example: * * ``` php * <?php * $email = $I->grabFromRepository('User', 'email', array('name' => 'davert')); * ?> * ``` * * @version 1.1 * @param $entity * @param $field * @param array $params * @return array */ public function grabFromRepository($entity, $field, $params = []) { // we need to store to database... $this->em->flush(); $data = $this->em->getClassMetadata($entity); $qb = $this->em->getRepository($entity)->createQueryBuilder('s'); $qb->select('s.' . $field); $this->buildAssociationQuery($qb, $entity, 's', $params); $this->debug($qb->getDQL()); return $qb->getQuery()->getSingleScalarResult(); } /** * Selects entities from repository. * It builds query based on array of parameters. * You can use entity associations to build complex queries. * * Example: * * ``` php * <?php * $users = $I->grabEntitiesFromRepository('AppBundle:User', array('name' => 'davert')); * ?> * ``` * * @version 1.1 * @param $entity * @param array $params * @return array */ public function grabEntitiesFromRepository($entity, $params = []) { // we need to store to database... $this->em->flush(); $data = $this->em->getClassMetadata($entity); $qb = $this->em->getRepository($entity)->createQueryBuilder('s'); $qb->select('s'); $this->buildAssociationQuery($qb, $entity, 's', $params); $this->debug($qb->getDQL()); return $qb->getQuery()->getResult(); } /** * Selects a single entity from repository. * It builds query based on array of parameters. * You can use entity associations to build complex queries. * * Example: * * ``` php * <?php * $user = $I->grabEntityFromRepository('User', array('id' => '1234')); * ?> * ``` * * @version 1.1 * @param $entity * @param array $params * @return array */ public function grabEntityFromRepository($entity, $params = []) { // we need to store to database... $this->em->flush(); $data = $this->em->getClassMetadata($entity); $qb = $this->em->getRepository($entity)->createQueryBuilder('s'); $qb->select('s'); $this->buildAssociationQuery($qb, $entity, 's', $params); $this->debug($qb->getDQL()); return $qb->getQuery()->getSingleResult(); } /** * It's Fuckin Recursive! * * @param $qb * @param $assoc * @param $alias * @param $params */ protected function buildAssociationQuery($qb, $assoc, $alias, $params) { $data = $this->em->getClassMetadata($assoc); foreach ($params as $key => $val) { if (isset($data->associationMappings)) { if ($map = array_key_exists($key, $data->associationMappings)) { if (is_array($val)) { $qb->innerJoin("$alias.$key", "_$key"); foreach ($val as $column => $v) { if (is_array($v)) { $this->buildAssociationQuery($qb, $map['targetEntity'], $column, $v); continue; } $paramname = "_$key" . '__' . $column; $qb->andWhere("_$key.$column = :$paramname"); $qb->setParameter($paramname, $v); } continue; } } } if ($val === null) { $qb->andWhere("s.$key IS NULL"); } else { $paramname = str_replace(".", "", "s_$key"); $qb->andWhere("s.$key = :$paramname"); $qb->setParameter($paramname, $val); } } } public function _getEntityManager() { return $this->em; } } <?php namespace Codeception\Module; use Codeception\Step; use Codeception\TestInterface; use Facebook\WebDriver\WebDriverBy; /** * Module for AngularJS testing, based on [WebDriver module](http://codeception.com/docs/modules/WebDriver) and [Protractor](http://angular.github.io/protractor/). * * Performs **synchronization to ensure that page content is fully rendered**. * Uses Angular's and Protractor internals methods to synchronize with the page. * * ## Configurarion * * The same as for [WebDriver](http://codeception.com/docs/modules/WebDriver#Configuration), but few new options added: * * * `el` - element where Angular application is defined (default: `body`) * * `script_timeout` - for how long in seconds to wait for angular operations to finish (default: 5) * * ### Example (`acceptance.suite.yml`) * * modules: * enabled: * - AngularJS: * url: 'http://localhost/' * browser: firefox * script_timeout: 10 * * * ### Additional Features * * Can perform matching elements by model. In this case you should provide a strict locator with `model` set. * * Example: * * ```php * $I->selectOption(['model' => 'customerId'], '3'); * ``` */ class AngularJS extends WebDriver { protected $insideApplication = true; protected $defaultAngularConfig = [ 'script_timeout' => 5, 'el' => 'body', ]; protected $waitForAngular = <<<EOF var rootSelector = arguments[0]; var callback = arguments[1]; var el = document.querySelector(rootSelector); try { if (window.getAngularTestability) { window.getAngularTestability(el).whenStable(callback); return; } if (!window.angular) { throw new Error('window.angular is undefined. This could be either ' + 'because this is a non-angular page or because your test involves ' + 'client-side navigation, which can interfere with Protractor\'s ' + 'bootstrapping. See http://git.io/v4gXM for details'); } if (angular.getTestability) { angular.getTestability(el).whenStable(callback); } else { if (!angular.element(el).injector()) { throw new Error('root element (' + rootSelector + ') has no injector.' + ' this may mean it is not inside ng-app.'); } angular.element(el).injector().get('\$browser'). notifyWhenNoOutstandingRequests(callback); } } catch (err) { callback(err.message); } EOF; public function _setConfig($config) { parent::_setConfig(array_merge($this->defaultAngularConfig, $config)); } public function _before(TestInterface $test) { parent::_before($test); $this->webDriver->manage()->timeouts()->setScriptTimeout($this->config['script_timeout']); } /** * Enables Angular mode (enabled by default). * Waits for Angular to finish rendering after each action. */ public function amInsideAngularApp() { $this->insideApplication = true; } /** * Disabled Angular mode. * * Falls back to original WebDriver, in case web page does not contain Angular app. */ public function amOutsideAngularApp() { $this->insideApplication = false; } public function _afterStep(Step $step) { if (!$this->insideApplication) { return; } $actions = [ 'amOnPage', 'click', 'fillField', 'selectOption', 'checkOption', 'uncheckOption', 'unselectOption', 'doubleClick', 'appendField', 'clickWithRightButton', 'dragAndDrop' ]; if (in_array($step->getAction(), $actions)) { $this->webDriver->executeAsyncScript($this->waitForAngular, [$this->config['el']]); } } protected function getStrictLocator(array $by) { $type = key($by); $value = $by[$type]; if ($type === 'model') { return WebDriverBy::cssSelector(sprintf('[ng-model="%s"]', $value)); } return parent::getStrictLocator($by); } } <?php namespace Codeception\Module; use Codeception\Configuration; use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleException; use Codeception\Lib\Connector\Yii2 as Yii2Connector; use Codeception\Lib\Framework; use Codeception\Lib\Interfaces\ActiveRecord; use Codeception\Lib\Interfaces\PartedModule; use Codeception\Lib\Notification; use Codeception\TestInterface; use Yii; use yii\db\ActiveRecordInterface; /** * This module provides integration with [Yii framework](http://www.yiiframework.com/) (2.0). * It initializes Yii framework in test environment and provides actions for functional testing. * * ## Config * * * `configFile` *required* - the path to the application config file. File should be configured for test environment and return configuration array. * * `entryUrl` - initial application url (default: http://localhost/index-test.php). * * `entryScript` - front script title (like: index-test.php). If not set - taken from entryUrl. * * `cleanup` - (default: true) wrap all database connection inside a transaction and roll it back after the test. Should be disabled for acceptance testing.. * * You can use this module by setting params in your functional.suite.yml: * * ```yaml * actor: FunctionalTester * modules: * enabled: * - Yii2: * configFile: '/path/to/config.php' * ``` * * ### Parts * * By default all available methods are loaded, but you can specify parts to select only needed actions and avoid conflicts. * * * `init` - use module only for initialization (for acceptance tests). * * `orm` - include only `haveRecord/grabRecord/seeRecord/dontSeeRecord` actions. * * `fixtures` - use fixtures inside tests with `haveFixtures/grabFixture/grabFixtures` actions. * * `email` - include email actions `seeEmailsIsSent/grabLastSentEmail/...` * * ### Example (`functional.suite.yml`) * * ```yaml * actor: FunctionalTester * modules: * enabled: * - Yii2: * configFile: 'config/test.php' * ``` * * ### Example (`unit.suite.yml`) * * ```yaml * actor: UnitTester * modules: * enabled: * - Asserts * - Yii2: * configFile: 'config/test.php' * part: init * ``` * * ### Example (`acceptance.suite.yml`) * * ```yaml * actor: AcceptanceTester * modules: * enabled: * - WebDriver: * url: http://127.0.0.1:8080/ * browser: firefox * - Yii2: * configFile: 'config/test.php' * part: ORM # allow to use AR methods * cleanup: false # don't wrap test in transaction * entryScript: index-test.php * ``` * * ## Fixtures * * This module allows to use [fixtures](http://www.yiiframework.com/doc-2.0/guide-test-fixtures.html) inside a test. There are two options for that. * Fixtures can be loaded using [haveFixtures](#haveFixtures) method inside a test: * * ```php * <?php * $I->haveFixtures(['posts' => PostsFixture::className()]); * ``` * * or, if you need to load fixtures before the test (probably before the cleanup transaction is started), you * can specify fixtures with `_fixtures` method of a testcase: * * ```php * <?php * // inside Cest file or Codeception\TestCase\Unit * public function _fixtures() * { * return ['posts' => PostsFixture::className()] * } * ``` * * ## URL * This module provide to use native URL formats of Yii2 for all codeception commands that use url for work. * This commands allows input like: * * ```php * <?php * $I->amOnPage(['site/view','page'=>'about']); * $I->amOnPage('index-test.php?site/index'); * $I->amOnPage('http://localhost/index-test.php?site/index'); * $I->sendAjaxPostRequest(['/user/update', 'id' => 1], ['UserForm[name]' => 'G.Hopper'); * ``` * * ## Status * * Maintainer: **samdark** * Stability: **stable** * */ class Yii2 extends Framework implements ActiveRecord, PartedModule { const TEST_FIXTURES_METHOD = '_fixtures'; /** * Application config file must be set. * @var array */ protected $config = [ 'cleanup' => true, 'entryScript' => '', 'entryUrl' => 'http://localhost/index-test.php', ]; protected $requiredFields = ['configFile']; protected $transaction; /** * @var \yii\base\Application */ public $app; /** * @var Yii2Connector\FixturesStore[] */ public $loadedFixtures = []; public function _initialize() { if (!is_file(Configuration::projectDir() . $this->config['configFile'])) { throw new ModuleConfigException( __CLASS__, "The application config file does not exist: " . Configuration::projectDir() . $this->config['configFile'] ); } $this->defineConstants(); } public function _before(TestInterface $test) { $entryUrl = $this->config['entryUrl']; $entryFile = $this->config['entryScript'] ?: basename($entryUrl); $entryScript = $this->config['entryScript'] ?: parse_url($entryUrl, PHP_URL_PATH); $this->client = new Yii2Connector(); $this->client->defaultServerVars = [ 'SCRIPT_FILENAME' => $entryFile, 'SCRIPT_NAME' => $entryScript, 'SERVER_NAME' => parse_url($entryUrl, PHP_URL_HOST), 'SERVER_PORT' => parse_url($entryUrl, PHP_URL_PORT) ?: '80', ]; $this->client->defaultServerVars['HTTPS'] = parse_url($entryUrl, PHP_URL_SCHEME) === 'https'; $this->client->restoreServerVars(); $this->client->configFile = Configuration::projectDir() . $this->config['configFile']; $this->app = $this->client->getApplication(); // load fixtures before db transaction if ($test instanceof \Codeception\Test\Cest) { $this->loadFixtures($test->getTestClass()); } else { $this->loadFixtures($test); } if ($this->config['cleanup'] && $this->app->has('db') && $this->app->db instanceof \yii\db\Connection ) { $this->transaction = $this->app->db->beginTransaction(); } } /** * load fixtures before db transaction * * @param mixed $test instance of test class */ private function loadFixtures($test) { if (empty($this->loadedFixtures) && method_exists($test, self::TEST_FIXTURES_METHOD) ) { $this->haveFixtures(call_user_func([$test, self::TEST_FIXTURES_METHOD])); } } public function _after(TestInterface $test) { $_SESSION = []; $_FILES = []; $_GET = []; $_POST = []; $_COOKIE = []; $_REQUEST = []; if ($this->config['cleanup']) { foreach ($this->loadedFixtures as $fixture) { $fixture->unloadFixtures(); } $this->loadedFixtures = []; if ($this->transaction) { $this->transaction->rollback(); } } if ($this->client) { $this->client->resetPersistentVars(); } if (isset(\Yii::$app) && \Yii::$app->has('session', true)) { \Yii::$app->session->close(); } // Close connections if exists if (isset(\Yii::$app) && \Yii::$app->has('db', true)) { \Yii::$app->db->close(); } parent::_after($test); } public function _parts() { return ['orm', 'init', 'fixtures', 'email']; } /** * Authorizes user on a site without submitting login form. * Use it for fast pragmatic authorization in functional tests. * * ```php * <?php * // User is found by id * $I->amLoggedInAs(1); * * // User object is passed as parameter * $admin = \app\models\User::findByUsername('admin'); * $I->amLoggedInAs($admin); * ``` * Requires `user` component to be enabled and configured. * * @param $user * @throws ModuleException */ public function amLoggedInAs($user) { if (!Yii::$app->has('user')) { throw new ModuleException($this, 'User component is not loaded'); } if ($user instanceof \yii\web\IdentityInterface) { $identity = $user; } else { // class name implementing IdentityInterface $identityClass = Yii::$app->user->identityClass; $identity = call_user_func([$identityClass, 'findIdentity'], $user); } Yii::$app->user->login($identity); } /** * Creates and loads fixtures from a config. * Signature is the same as for `fixtures()` method of `yii\test\FixtureTrait` * * ```php * <?php * $I->haveFixtures([ * 'posts' => PostsFixture::className(), * 'user' => [ * 'class' => UserFixture::className(), * 'dataFile' => '@tests/_data/models/user.php', * ], * ]); * ``` * * Note: if you need to load fixtures before the test (probably before the cleanup transaction is started; * `cleanup` options is `true` by default), you can specify fixtures with _fixtures method of a testcase * ```php * <?php * // inside Cest file or Codeception\TestCase\Unit * public function _fixtures(){ * return [ * 'user' => [ * 'class' => UserFixture::className(), * 'dataFile' => codecept_data_dir() . 'user.php' * ] * ]; * } * ``` * instead of defining `haveFixtures` in Cest `_before` * * @param $fixtures * @part fixtures */ public function haveFixtures($fixtures) { if (empty($fixtures)) { return; } $fixturesStore = new Yii2Connector\FixturesStore($fixtures); $fixturesStore->unloadFixtures(); $fixturesStore->loadFixtures(); $this->loadedFixtures[] = $fixturesStore; } /** * Returns all loaded fixtures. * Array of fixture instances * * @part fixtures * @return array */ public function grabFixtures() { return call_user_func_array( 'array_merge', array_map( // merge all fixtures from all fixture stores function ($fixturesStore) { return $fixturesStore->getFixtures(); }, $this->loadedFixtures ) ); } /** * Gets a fixture by name. * Returns a Fixture instance. If a fixture is an instance of `\yii\test\BaseActiveFixture` a second parameter * can be used to return a specific model: * * ```php * <?php * $I->haveFixtures(['users' => UserFixture::className()]); * * $users = $I->grabFixture('users'); * * // get first user by key, if a fixture is instance of ActiveFixture * $user = $I->grabFixture('users', 'user1'); * ``` * * @param $name * @return mixed * @throws ModuleException if a fixture is not found * @part fixtures */ public function grabFixture($name, $index = null) { $fixtures = $this->grabFixtures(); if (!isset($fixtures[$name])) { throw new ModuleException($this, "Fixture $name is not loaded"); } $fixture = $fixtures[$name]; if ($index === null) { return $fixture; } if ($fixture instanceof \yii\test\BaseActiveFixture) { return $fixture->getModel($index); } throw new ModuleException($this, "Fixture $name is not an instance of ActiveFixture and can't be loaded with scond parameter"); } /** * Inserts record into the database. * * ``` php * <?php * $user_id = $I->haveRecord('app\models\User', array('name' => 'Davert')); * ?> * ``` * * @param $model * @param array $attributes * @return mixed * @part orm */ public function haveRecord($model, $attributes = []) { /** @var $record \yii\db\ActiveRecord * */ $record = $this->getModelRecord($model); $record->setAttributes($attributes, false); $res = $record->save(false); if (!$res) { $this->fail("Record $model was not saved"); } return $record->primaryKey; } /** * Checks that record exists in database. * * ``` php * $I->seeRecord('app\models\User', array('name' => 'davert')); * ``` * * @param $model * @param array $attributes * @part orm */ public function seeRecord($model, $attributes = []) { $record = $this->findRecord($model, $attributes); if (!$record) { $this->fail("Couldn't find $model with " . json_encode($attributes)); } $this->debugSection($model, json_encode($record)); } /** * Checks that record does not exist in database. * * ``` php * $I->dontSeeRecord('app\models\User', array('name' => 'davert')); * ``` * * @param $model * @param array $attributes * @part orm */ public function dontSeeRecord($model, $attributes = []) { $record = $this->findRecord($model, $attributes); $this->debugSection($model, json_encode($record)); if ($record) { $this->fail("Unexpectedly managed to find $model with " . json_encode($attributes)); } } /** * Retrieves record from database * * ``` php * $category = $I->grabRecord('app\models\User', array('name' => 'davert')); * ``` * * @param $model * @param array $attributes * @return mixed * @part orm */ public function grabRecord($model, $attributes = []) { return $this->findRecord($model, $attributes); } protected function findRecord($model, $attributes = []) { $this->getModelRecord($model); return call_user_func([$model, 'find']) ->where($attributes) ->one(); } protected function getModelRecord($model) { if (!class_exists($model)) { throw new \RuntimeException("Model $model does not exist"); } $record = new $model; if (!$record instanceof ActiveRecordInterface) { throw new \RuntimeException("Model $model is not implement interface \\yii\\db\\ActiveRecordInterface"); } return $record; } /** * Similar to amOnPage but accepts route as first argument and params as second * * ``` * $I->amOnRoute('site/view', ['page' => 'about']); * ``` * */ public function amOnRoute($route, array $params = []) { array_unshift($params, $route); $this->amOnPage($params); } /** * To support to use the behavior of urlManager component * for the methods like this: amOnPage(), sendAjaxRequest() and etc. * @param $method * @param $uri * @param array $parameters * @param array $files * @param array $server * @param null $content * @param bool $changeHistory * @return mixed */ protected function clientRequest($method, $uri, array $parameters = [], array $files = [], array $server = [], $content = null, $changeHistory = true) { if (is_array($uri)) { $uri = Yii::$app->getUrlManager()->createUrl($uri); } return parent::clientRequest($method, $uri, $parameters, $files, $server, $content, $changeHistory); } /** * Gets a component from Yii container. Throws exception if component is not available * * ```php * <?php * $mailer = $I->grabComponent('mailer'); * ``` * * @param $component * @return mixed * @throws ModuleException */ public function grabComponent($component) { if (!Yii::$app->has($component)) { throw new ModuleException($this, "Component $component is not avilable in current application"); } return Yii::$app->get($component); } /** * Checks that email is sent. * * ```php * <?php * // check that at least 1 email was sent * $I->seeEmailIsSent(); * * // check that only 3 emails were sent * $I->seeEmailIsSent(3); * ``` * * @param int $num * @throws ModuleException * @part email */ public function seeEmailIsSent($num = null) { if ($num === null) { $this->assertNotEmpty($this->grabSentEmails(), 'emails were sent'); return; } $this->assertEquals($num, count($this->grabSentEmails()), 'number of sent emails is equal to ' . $num); } /** * Checks that no email was sent * * @part email */ public function dontSeeEmailIsSent() { $this->seeEmailIsSent(0); } /** * Returns array of all sent email messages. * Each message implements `yii\mail\Message` interface. * Useful to perform additional checks using `Asserts` module: * * ```php * <?php * $I->seeEmailIsSent(); * $messages = $I->grabSentEmails(); * $I->assertEquals('admin@site,com', $messages[0]->getTo()); * ``` * * @part email * @return array * @throws ModuleException */ public function grabSentEmails() { $mailer = $this->grabComponent('mailer'); if (!$mailer instanceof Yii2Connector\TestMailer) { throw new ModuleException($this, "Mailer module is not mocked, can't test emails"); } return $mailer->getSentMessages(); } /** * Returns last sent email: * * ```php * <?php * $I->seeEmailIsSent(); * $message = $I->grabLastSentEmail(); * $I->assertEquals('admin@site,com', $message->getTo()); * ``` * @part email */ public function grabLastSentEmail() { $this->seeEmailIsSent(); $messages = $this->grabSentEmails(); return end($messages); } /** * Getting domain regex from rule host template * * @param string $template * @return string */ private function getDomainRegex($template) { if (preg_match('#https?://(.*)#', $template, $matches)) { $template = $matches[1]; } $parameters = []; if (strpos($template, '<') !== false) { $template = preg_replace_callback( '/<(?:\w+):?([^>]+)?>/u', function ($matches) use (&$parameters) { $key = '#' . count($parameters) . '#'; $parameters[$key] = isset($matches[1]) ? $matches[1] : '\w+'; return $key; }, $template ); } $template = preg_quote($template); $template = strtr($template, $parameters); return '/^' . $template . '$/u'; } /** * Returns a list of regex patterns for recognized domain names * * @return array */ public function getInternalDomains() { $domains = [$this->getDomainRegex(Yii::$app->urlManager->hostInfo)]; if (Yii::$app->urlManager->enablePrettyUrl) { foreach (Yii::$app->urlManager->rules as $rule) { /** @var \yii\web\UrlRule $rule */ if (isset($rule->host)) { $domains[] = $this->getDomainRegex($rule->host); } } } return array_unique($domains); } private function defineConstants() { defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'test'); defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', false); } } <?php namespace Codeception\Module; use Codeception\Exception\ModuleException; use Codeception\Lib\Interfaces\ConflictsWithModule; use Codeception\Module as CodeceptionModule; use Codeception\PHPUnit\Constraint\JsonContains; use Codeception\PHPUnit\Constraint\JsonType as JsonTypeConstraint; use Codeception\TestInterface; use Codeception\Lib\Interfaces\API; use Codeception\Lib\Framework; use Codeception\Lib\InnerBrowser; use Codeception\Lib\Interfaces\DependsOnModule; use Codeception\Lib\Interfaces\PartedModule; use Codeception\Util\JsonArray; use Codeception\Util\JsonType; use Codeception\Util\XmlStructure; use Codeception\Util\Soap as XmlUtils; /** * Module for testing REST WebService. * * This module can be used either with frameworks or PHPBrowser. * If a framework module is connected, the testing will occur in the application directly. * Otherwise, a PHPBrowser should be specified as a dependency to send requests and receive responses from a server. * * ## Configuration * * * url *optional* - the url of api * * This module requires PHPBrowser or any of Framework modules enabled. * * ### Example * * modules: * enabled: * - REST: * depends: PhpBrowser * url: 'http://serviceapp/api/v1/' * * ## Public Properties * * * headers - array of headers going to be sent. * * params - array of sent data * * response - last response (string) * * ## Parts * * * Json - actions for validating Json responses (no Xml responses) * * Xml - actions for validating XML responses (no Json responses) * * ## Conflicts * * Conflicts with SOAP module * */ class REST extends CodeceptionModule implements DependsOnModule, PartedModule, API, ConflictsWithModule { protected $config = [ 'url' => '' ]; protected $dependencyMessage = <<<EOF Example configuring PhpBrowser as backend for REST module. -- modules: enabled: - REST: depends: PhpBrowser url: http://localhost/api/ -- Framework modules can be used for testing of API as well. EOF; /** * @var \Symfony\Component\HttpKernel\Client|\Symfony\Component\BrowserKit\Client */ public $client = null; public $isFunctional = false; /** * @var InnerBrowser */ protected $connectionModule; public $params = []; public $response = ""; public function _before(TestInterface $test) { $this->client = &$this->connectionModule->client; $this->resetVariables(); } protected function resetVariables() { $this->params = []; $this->response = ""; $this->connectionModule->headers = []; } public function _conflicts() { return 'Codeception\Lib\Interfaces\API'; } public function _depends() { return ['Codeception\Lib\InnerBrowser' => $this->dependencyMessage]; } public function _parts() { return ['xml', 'json']; } public function _inject(InnerBrowser $connection) { $this->connectionModule = $connection; if ($this->connectionModule instanceof Framework) { $this->isFunctional = true; } if ($this->connectionModule instanceof PhpBrowser) { if (!$this->connectionModule->_getConfig('url')) { $this->connectionModule->_setConfig(['url' => $this->config['url']]); } } } protected function getRunningClient() { if ($this->client->getInternalRequest() === null) { throw new ModuleException($this, "Response is empty. Use `\$I->sendXXX()` methods to send HTTP request"); } return $this->client; } /** * Sets HTTP header valid for all next requests. Use `deleteHeader` to unset it * * ```php * <?php * $I->haveHttpHeader('Content-Type', 'application/json'); * // all next requests will contain this header * ?> * ``` * * @param $name * @param $value * @part json * @part xml */ public function haveHttpHeader($name, $value) { $this->connectionModule->haveHttpHeader($name, $value); } /** * Deletes the header with the passed name. Subsequent requests * will not have the deleted header in its request. * * Example: * ```php * <?php * $I->haveHttpHeader('X-Requested-With', 'Codeception'); * $I->sendGET('test-headers.php'); * // ... * $I->deleteHeader('X-Requested-With'); * $I->sendPOST('some-other-page.php'); * ?> * ``` * * @param string $name the name of the header to delete. * @part json * @part xml */ public function deleteHeader($name) { $this->connectionModule->deleteHeader($name); } /** * Checks over the given HTTP header and (optionally) * its value, asserting that are there * * @param $name * @param $value * @part json * @part xml */ public function seeHttpHeader($name, $value = null) { if ($value !== null) { $this->assertEquals( $value, $this->getRunningClient()->getInternalResponse()->getHeader($name) ); return; } $this->assertNotNull($this->getRunningClient()->getInternalResponse()->getHeader($name)); } /** * Checks over the given HTTP header and (optionally) * its value, asserting that are not there * * @param $name * @param $value * @part json * @part xml */ public function dontSeeHttpHeader($name, $value = null) { if ($value !== null) { $this->assertNotEquals( $value, $this->getRunningClient()->getInternalResponse()->getHeader($name) ); return; } $this->assertNull($this->getRunningClient()->getInternalResponse()->getHeader($name)); } /** * Checks that http response header is received only once. * HTTP RFC2616 allows multiple response headers with the same name. * You can check that you didn't accidentally sent the same header twice. * * ``` php * <?php * $I->seeHttpHeaderOnce('Cache-Control'); * ?>> * ``` * * @param $name * @part json * @part xml */ public function seeHttpHeaderOnce($name) { $headers = $this->getRunningClient()->getInternalResponse()->getHeader($name, false); $this->assertEquals(1, count($headers)); } /** * Returns the value of the specified header name * * @param $name * @param Boolean $first Whether to return the first value or all header values * * @return string|array The first header value if $first is true, an array of values otherwise * @part json * @part xml */ public function grabHttpHeader($name, $first = true) { return $this->getRunningClient()->getInternalResponse()->getHeader($name, $first); } /** * Adds HTTP authentication via username/password. * * @param $username * @param $password * @part json * @part xml */ public function amHttpAuthenticated($username, $password) { if ($this->isFunctional) { $this->client->setServerParameter('PHP_AUTH_USER', $username); $this->client->setServerParameter('PHP_AUTH_PW', $password); } else { $this->client->setAuth($username, $password); } } /** * Adds Digest authentication via username/password. * * @param $username * @param $password * @part json * @part xml */ public function amDigestAuthenticated($username, $password) { $this->client->setAuth($username, $password, 'digest'); } /** * Adds Bearer authentication via access token. * * @param $accessToken * @part json * @part xml */ public function amBearerAuthenticated($accessToken) { $this->haveHttpHeader('Authorization', 'Bearer ' . $accessToken); } /** * Sends a POST request to given uri. Parameters and files can be provided separately. * * Example: * ```php * <?php * //simple POST call * $I->sendPOST('/message', ['subject' => 'Read this!', 'to' => 'johndoe@example.com']); * //simple upload method * $I->sendPOST('/message/24', ['inline' => 0], ['attachmentFile' => codecept_data_dir('sample_file.pdf')]); * //uploading a file with a custom name and mime-type. This is also useful to simulate upload errors. * $I->sendPOST('/message/24', ['inline' => 0], [ * 'attachmentFile' => [ * 'name' => 'document.pdf', * 'type' => 'application/pdf', * 'error' => UPLOAD_ERR_OK, * 'size' => filesize(codecept_data_dir('sample_file.pdf')), * 'tmp_name' => codecept_data_dir('sample_file.pdf') * ] * ]); * ``` * * @param $url * @param array|\JsonSerializable $params * @param array $files A list of filenames or "mocks" of $_FILES (each entry being an array with the following * keys: name, type, error, size, tmp_name (pointing to the real file path). Each key works * as the "name" attribute of a file input field. * * @see http://php.net/manual/en/features.file-upload.post-method.php * @see codecept_data_dir() * @part json * @part xml */ public function sendPOST($url, $params = [], $files = []) { $this->execute('POST', $url, $params, $files); } /** * Sends a HEAD request to given uri. * * @param $url * @param array $params * @part json * @part xml */ public function sendHEAD($url, $params = []) { $this->execute('HEAD', $url, $params); } /** * Sends an OPTIONS request to given uri. * * @param $url * @param array $params * @part json * @part xml */ public function sendOPTIONS($url, $params = []) { $this->execute('OPTIONS', $url, $params); } /** * Sends a GET request to given uri. * * @param $url * @param array $params * @part json * @part xml */ public function sendGET($url, $params = []) { $this->execute('GET', $url, $params); } /** * Sends PUT request to given uri. * * @param $url * @param array $params * @param array $files * @part json * @part xml */ public function sendPUT($url, $params = [], $files = []) { $this->execute('PUT', $url, $params, $files); } /** * Sends PATCH request to given uri. * * @param $url * @param array $params * @param array $files * @part json * @part xml */ public function sendPATCH($url, $params = [], $files = []) { $this->execute('PATCH', $url, $params, $files); } /** * Sends DELETE request to given uri. * * @param $url * @param array $params * @param array $files * @part json * @part xml */ public function sendDELETE($url, $params = [], $files = []) { $this->execute('DELETE', $url, $params, $files); } /** * Sets Headers "Link" as one header "Link" based on linkEntries * * @param array $linkEntries (entry is array with keys "uri" and "link-param") * * @link http://tools.ietf.org/html/rfc2068#section-19.6.2.4 * * @author samva.ua@gmail.com */ private function setHeaderLink(array $linkEntries) { $values = []; foreach ($linkEntries as $linkEntry) { \PHPUnit_Framework_Assert::assertArrayHasKey( 'uri', $linkEntry, 'linkEntry should contain property "uri"' ); \PHPUnit_Framework_Assert::assertArrayHasKey( 'link-param', $linkEntry, 'linkEntry should contain property "link-param"' ); $values[] = $linkEntry['uri'] . '; ' . $linkEntry['link-param']; } $this->haveHttpHeader('Link', implode(', ', $values)); } /** * Sends LINK request to given uri. * * @param $url * @param array $linkEntries (entry is array with keys "uri" and "link-param") * * @link http://tools.ietf.org/html/rfc2068#section-19.6.2.4 * * @author samva.ua@gmail.com * @part json * @part xml */ public function sendLINK($url, array $linkEntries) { $this->setHeaderLink($linkEntries); $this->execute('LINK', $url); } /** * Sends UNLINK request to given uri. * * @param $url * @param array $linkEntries (entry is array with keys "uri" and "link-param") * @link http://tools.ietf.org/html/rfc2068#section-19.6.2.4 * @author samva.ua@gmail.com * @part json * @part xml */ public function sendUNLINK($url, array $linkEntries) { $this->setHeaderLink($linkEntries); $this->execute('UNLINK', $url); } protected function execute($method, $url, $parameters = [], $files = []) { // allow full url to be requested if (strpos($url, '://') === false) { $url = $this->config['url'] . $url; } $this->params = $parameters; $parameters = $this->encodeApplicationJson($method, $parameters); if (is_array($parameters) || $method === 'GET') { if (!empty($parameters) && $method === 'GET') { if (strpos($url, '?') !== false) { $url .= '&'; } else { $url .= '?'; } $url .= http_build_query($parameters); } if ($method == 'GET') { $this->debugSection("Request", "$method $url"); $files = []; } else { $this->debugSection("Request", "$method $url " . json_encode($parameters)); $files = $this->formatFilesArray($files); } $this->response = (string)$this->connectionModule->_request($method, $url, $parameters, $files); } else { $requestData = $parameters; if ($this->isBinaryData($requestData)) { $requestData = $this->binaryToDebugString($requestData); } $this->debugSection("Request", "$method $url " . $requestData); $this->response = (string)$this->connectionModule->_request($method, $url, [], $files, [], $parameters); } $printedResponse = $this->response; if ($this->isBinaryData($printedResponse)) { $printedResponse = $this->binaryToDebugString($printedResponse); } $this->debugSection("Response", $printedResponse); } /** * Check if data has non-printable bytes and it is not a valid unicode string * * @param string $data the text or binary data string * @return boolean */ protected function isBinaryData($data) { return !ctype_print($data) && false === mb_detect_encoding($data, mb_detect_order(), true); } /** * Format a binary string for debug printing * * @param string $data the binary data string * @return string the debug string */ protected function binaryToDebugString($data) { return '[binary-data length:' . strlen($data) . ' md5:' . md5($data) . ']'; } protected function encodeApplicationJson($method, $parameters) { if ($method !== 'GET' && array_key_exists('Content-Type', $this->connectionModule->headers) && ($this->connectionModule->headers['Content-Type'] === 'application/json' || preg_match('!^application/.+\+json$!', $this->connectionModule->headers['Content-Type']) ) ) { if ($parameters instanceof \JsonSerializable) { return json_encode($parameters); } if (is_array($parameters) || $parameters instanceof \ArrayAccess) { $parameters = $this->scalarizeArray($parameters); return json_encode($parameters); } } return $parameters; } private function formatFilesArray(array $files) { foreach ($files as $name => $value) { if (is_string($value)) { $this->checkFileBeforeUpload($value); $files[$name] = [ 'name' => basename($value), 'tmp_name' => $value, 'size' => filesize($value), 'type' => $this->getFileType($value), 'error' => 0, ]; continue; } elseif (is_array($value)) { if (isset($value['tmp_name'])) { $this->checkFileBeforeUpload($value['tmp_name']); if (!isset($value['name'])) { $value['name'] = basename($value); } if (!isset($value['size'])) { $value['size'] = filesize($value); } if (!isset($value['type'])) { $value['type'] = $this->getFileType($value); } if (!isset($value['error'])) { $value['error'] = 0; } } else { $files[$name] = $this->formatFilesArray($value); } } elseif (is_object($value)) { /** * do nothing, probably the user knows what he is doing * @issue https://github.com/Codeception/Codeception/issues/3298 */ } else { throw new ModuleException(__CLASS__, "Invalid value of key $name in files array"); } } return $files; } private function getFileType($file) { if (function_exists('mime_content_type') && mime_content_type($file)) { return mime_content_type($file); } return 'application/octet-stream'; } private function checkFileBeforeUpload($file) { if (!file_exists($file)) { throw new ModuleException(__CLASS__, "File $file does not exist"); } if (!is_readable($file)) { throw new ModuleException(__CLASS__, "File $file is not readable"); } if (!is_file($file)) { throw new ModuleException(__CLASS__, "File $file is not a regular file"); } } /** * Checks whether last response was valid JSON. * This is done with json_last_error function. * * @part json */ public function seeResponseIsJson() { $responseContent = $this->connectionModule->_getResponseContent(); \PHPUnit_Framework_Assert::assertNotEquals('', $responseContent, 'response is empty'); json_decode($responseContent); $errorCode = json_last_error(); $errorMessage = json_last_error_msg(); \PHPUnit_Framework_Assert::assertEquals( JSON_ERROR_NONE, $errorCode, sprintf( "Invalid json: %s. System message: %s.", $responseContent, $errorMessage ) ); } /** * Checks whether the last response contains text. * * @param $text * @part json * @part xml */ public function seeResponseContains($text) { $this->assertContains($text, $this->connectionModule->_getResponseContent(), "REST response contains"); } /** * Checks whether last response do not contain text. * * @param $text * @part json * @part xml */ public function dontSeeResponseContains($text) { $this->assertNotContains($text, $this->connectionModule->_getResponseContent(), "REST response contains"); } /** * Checks whether the last JSON response contains provided array. * The response is converted to array with json_decode($response, true) * Thus, JSON is represented by associative array. * This method matches that response array contains provided array. * * Examples: * * ``` php * <?php * // response: {name: john, email: john@gmail.com} * $I->seeResponseContainsJson(array('name' => 'john')); * * // response {user: john, profile: { email: john@gmail.com }} * $I->seeResponseContainsJson(array('email' => 'john@gmail.com')); * * ?> * ``` * * This method recursively checks if one array can be found inside of another. * * @param array $json * @part json */ public function seeResponseContainsJson($json = []) { \PHPUnit_Framework_Assert::assertThat( $this->connectionModule->_getResponseContent(), new JsonContains($json) ); } /** * Returns current response so that it can be used in next scenario steps. * * Example: * * ``` php * <?php * $user_id = $I->grabResponse(); * $I->sendPUT('/user', array('id' => $user_id, 'name' => 'davert')); * ?> * ``` * * @version 1.1 * @return string * @part json * @part xml */ public function grabResponse() { return $this->connectionModule->_getResponseContent(); } /** * Returns data from the current JSON response using [JSONPath](http://goessner.net/articles/JsonPath/) as selector. * JsonPath is XPath equivalent for querying Json structures. * Try your JsonPath expressions [online](http://jsonpath.curiousconcept.com/). * Even for a single value an array is returned. * * This method **require [`flow/jsonpath` > 0.2](https://github.com/FlowCommunications/JSONPath/) library to be installed**. * * Example: * * ``` php * <?php * // match the first `user.id` in json * $firstUserId = $I->grabDataFromResponseByJsonPath('$..users[0].id'); * $I->sendPUT('/user', array('id' => $firstUserId[0], 'name' => 'davert')); * ?> * ``` * * @param string $jsonPath * @return array Array of matching items * @version 2.0.9 * @throws \Exception * @part json */ public function grabDataFromResponseByJsonPath($jsonPath) { return (new JsonArray($this->connectionModule->_getResponseContent()))->filterByJsonPath($jsonPath); } /** * Checks if json structure in response matches the xpath provided. * JSON is not supposed to be checked against XPath, yet it can be converted to xml and used with XPath. * This assertion allows you to check the structure of response json. * * * ```json * { "store": { * "book": [ * { "category": "reference", * "author": "Nigel Rees", * "title": "Sayings of the Century", * "price": 8.95 * }, * { "category": "fiction", * "author": "Evelyn Waugh", * "title": "Sword of Honour", * "price": 12.99 * } * ], * "bicycle": { * "color": "red", * "price": 19.95 * } * } * } * ``` * * ```php * <?php * // at least one book in store has author * $I->seeResponseJsonMatchesXpath('//store/book/author'); * // first book in store has author * $I->seeResponseJsonMatchesXpath('//store/book[1]/author'); * // at least one item in store has price * $I->seeResponseJsonMatchesXpath('/store//price'); * ?> * ``` * @param string $xpath * @part json * @version 2.0.9 */ public function seeResponseJsonMatchesXpath($xpath) { $response = $this->connectionModule->_getResponseContent(); $this->assertGreaterThan( 0, (new JsonArray($response))->filterByXPath($xpath)->length, "Received JSON did not match the XPath `$xpath`.\nJson Response: \n" . $response ); } /** * Opposite to seeResponseJsonMatchesXpath * * @param string $xpath * @part json */ public function dontSeeResponseJsonMatchesXpath($xpath) { $response = $this->connectionModule->_getResponseContent(); $this->assertEquals( 0, (new JsonArray($response))->filterByXPath($xpath)->length, "Received JSON matched the XPath `$xpath`.\nJson Response: \n" . $response ); } /** * Checks if json structure in response matches [JsonPath](http://goessner.net/articles/JsonPath/). * JsonPath is XPath equivalent for querying Json structures. * Try your JsonPath expressions [online](http://jsonpath.curiousconcept.com/). * This assertion allows you to check the structure of response json. * * This method **require [`flow/jsonpath` > 0.2](https://github.com/FlowCommunications/JSONPath/) library to be installed**. * * ```json * { "store": { * "book": [ * { "category": "reference", * "author": "Nigel Rees", * "title": "Sayings of the Century", * "price": 8.95 * }, * { "category": "fiction", * "author": "Evelyn Waugh", * "title": "Sword of Honour", * "price": 12.99 * } * ], * "bicycle": { * "color": "red", * "price": 19.95 * } * } * } * ``` * * ```php * <?php * // at least one book in store has author * $I->seeResponseJsonMatchesJsonPath('$.store.book[*].author'); * // first book in store has author * $I->seeResponseJsonMatchesJsonPath('$.store.book[0].author'); * // at least one item in store has price * $I->seeResponseJsonMatchesJsonPath('$.store..price'); * ?> * ``` * * @param string $jsonPath * @part json * @version 2.0.9 */ public function seeResponseJsonMatchesJsonPath($jsonPath) { $response = $this->connectionModule->_getResponseContent(); $this->assertNotEmpty( (new JsonArray($response))->filterByJsonPath($jsonPath), "Received JSON did not match the JsonPath `$jsonPath`.\nJson Response: \n" . $response ); } /** * Opposite to seeResponseJsonMatchesJsonPath * * @param string $jsonPath * @part json */ public function dontSeeResponseJsonMatchesJsonPath($jsonPath) { $response = $this->connectionModule->_getResponseContent(); $this->assertEmpty( (new JsonArray($response))->filterByJsonPath($jsonPath), "Received JSON matched the JsonPath `$jsonPath`.\nJson Response: \n" . $response ); } /** * Opposite to seeResponseContainsJson * * @part json * @param array $json */ public function dontSeeResponseContainsJson($json = []) { $jsonResponseArray = new JsonArray($this->connectionModule->_getResponseContent()); $this->assertFalse( $jsonResponseArray->containsArray($json), "Response JSON contains provided JSON\n" . "- <info>" . var_export($json, true) . "</info>\n" . "+ " . var_export($jsonResponseArray->toArray(), true) ); } /** * Checks that Json matches provided types. * In case you don't know the actual values of JSON data returned you can match them by type. * Starts check with a root element. If JSON data is array it will check the first element of an array. * You can specify the path in the json which should be checked with JsonPath * * Basic example: * * ```php * <?php * // {'user_id': 1, 'name': 'davert', 'is_active': false} * $I->seeResponseMatchesJsonType([ * 'user_id' => 'integer', * 'name' => 'string|null', * 'is_active' => 'boolean' * ]); * * // narrow down matching with JsonPath: * // {"users": [{ "name": "davert"}, {"id": 1}]} * $I->seeResponseMatchesJsonType(['name' => 'string'], '$.users[0]'); * ?> * ``` * * In this case you can match that record contains fields with data types you expected. * The list of possible data types: * * * string * * integer * * float * * array (json object is array as well) * * boolean * * You can also use nested data type structures: * * ```php * <?php * // {'user_id': 1, 'name': 'davert', 'company': {'name': 'Codegyre'}} * $I->seeResponseMatchesJsonType([ * 'user_id' => 'integer|string', // multiple types * 'company' => ['name' => 'string'] * ]); * ?> * ``` * * You can also apply filters to check values. Filter can be applied with `:` char after the type declatation. * * Here is the list of possible filters: * * * `integer:>{val}` - checks that integer is greater than {val} (works with float and string types too). * * `integer:<{val}` - checks that integer is lower than {val} (works with float and string types too). * * `string:url` - checks that value is valid url. * * `string:date` - checks that value is date in JavaScript format: https://weblog.west-wind.com/posts/2014/Jan/06/JavaScript-JSON-Date-Parsing-and-real-Dates * * `string:email` - checks that value is a valid email according to http://emailregex.com/ * * `string:regex({val})` - checks that string matches a regex provided with {val} * * This is how filters can be used: * * ```php * <?php * // {'user_id': 1, 'email' => 'davert@codeception.com'} * $I->seeResponseMatchesJsonType([ * 'user_id' => 'string:>0:<1000', // multiple filters can be used * 'email' => 'string:regex(~\@~)' // we just check that @ char is included * ]); * * // {'user_id': '1'} * $I->seeResponseMatchesJsonType([ * 'user_id' => 'string:>0', // works with strings as well * } * ?> * ``` * * You can also add custom filters y accessing `JsonType::addCustomFilter` method. * See [JsonType reference](http://codeception.com/docs/reference/JsonType). * * @part json * @version 2.1.3 * @param array $jsonType * @param string $jsonPath */ public function seeResponseMatchesJsonType(array $jsonType, $jsonPath = null) { $jsonArray = new JsonArray($this->connectionModule->_getResponseContent()); if ($jsonPath) { $jsonArray = $jsonArray->filterByJsonPath($jsonPath); } \PHPUnit_Framework_Assert::assertThat($jsonArray, new JsonTypeConstraint($jsonType)); } /** * Opposite to `seeResponseMatchesJsonType`. * * @part json * @see seeResponseMatchesJsonType * @param $jsonType jsonType structure * @param null $jsonPath optionally set specific path to structure with JsonPath * @version 2.1.3 */ public function dontSeeResponseMatchesJsonType($jsonType, $jsonPath = null) { $jsonArray = new JsonArray($this->connectionModule->_getResponseContent()); if ($jsonPath) { $jsonArray = $jsonArray->filterByJsonPath($jsonPath); } \PHPUnit_Framework_Assert::assertThat($jsonArray, new JsonTypeConstraint($jsonType, false)); } /** * Checks if response is exactly the same as provided. * * @part json * @part xml * @param $response */ public function seeResponseEquals($expected) { $this->assertEquals($expected, $this->connectionModule->_getResponseContent()); } /** * Checks response code equals to provided value. * * ```php * <?php * $I->seeResponseCodeIs(200); * * // preferred to use \Codeception\Util\HttpCode * $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); * ``` * * @part json * @part xml * @param $code */ public function seeResponseCodeIs($code) { $this->connectionModule->seeResponseCodeIs($code); } /** * Checks that response code is not equal to provided value. * * ```php * <?php * $I->dontSeeResponseCodeIs(200); * * // preferred to use \Codeception\Util\HttpCode * $I->dontSeeResponseCodeIs(\Codeception\Util\HttpCode::OK); * ``` * * @part json * @part xml * @param $code */ public function dontSeeResponseCodeIs($code) { $this->connectionModule->dontSeeResponseCodeIs($code); } /** * Checks whether last response was valid XML. * This is done with libxml_get_last_error function. * * @part xml */ public function seeResponseIsXml() { libxml_use_internal_errors(true); $doc = simplexml_load_string($this->connectionModule->_getResponseContent()); $num = ""; $title = ""; if ($doc === false) { $error = libxml_get_last_error(); $num = $error->code; $title = trim($error->message); libxml_clear_errors(); } libxml_use_internal_errors(false); \PHPUnit_Framework_Assert::assertNotSame( false, $doc, "xml decoding error #$num with message \"$title\", see http://www.xmlsoft.org/html/libxml-xmlerror.html" ); } /** * Checks wheather XML response matches XPath * * ```php * <?php * $I->seeXmlResponseMatchesXpath('//root/user[@id=1]'); * ``` * @part xml * @param $xpath */ public function seeXmlResponseMatchesXpath($xpath) { $structure = new XmlStructure($this->connectionModule->_getResponseContent()); $this->assertTrue($structure->matchesXpath($xpath), 'xpath not matched'); } /** * Checks wheather XML response does not match XPath * * ```php * <?php * $I->dontSeeXmlResponseMatchesXpath('//root/user[@id=1]'); * ``` * @part xml * @param $xpath */ public function dontSeeXmlResponseMatchesXpath($xpath) { $structure = new XmlStructure($this->connectionModule->_getResponseContent()); $this->assertFalse($structure->matchesXpath($xpath), 'accidentally matched xpath'); } /** * Finds and returns text contents of element. * Element is matched by either CSS or XPath * * @param $cssOrXPath * @return string * @part xml */ public function grabTextContentFromXmlElement($cssOrXPath) { $el = (new XmlStructure($this->connectionModule->_getResponseContent()))->matchElement($cssOrXPath); return $el->textContent; } /** * Finds and returns attribute of element. * Element is matched by either CSS or XPath * * @param $cssOrXPath * @param $attribute * @return string * @part xml */ public function grabAttributeFromXmlElement($cssOrXPath, $attribute) { $el = (new XmlStructure($this->connectionModule->_getResponseContent()))->matchElement($cssOrXPath); if (!$el->hasAttribute($attribute)) { $this->fail("Attribute not found in element matched by '$cssOrXPath'"); } return $el->getAttribute($attribute); } /** * Checks XML response equals provided XML. * Comparison is done by canonicalizing both xml`s. * * Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes). * * @param $xml * @part xml */ public function seeXmlResponseEquals($xml) { \PHPUnit_Framework_Assert::assertXmlStringEqualsXmlString($this->connectionModule->_getResponseContent(), $xml); } /** * Checks XML response does not equal to provided XML. * Comparison is done by canonicalizing both xml`s. * * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes). * * @param $xml * @part xml */ public function dontSeeXmlResponseEquals($xml) { \PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlString( $this->connectionModule->_getResponseContent(), $xml ); } /** * Checks XML response includes provided XML. * Comparison is done by canonicalizing both xml`s. * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes). * * Example: * * ``` php * <?php * $I->seeXmlResponseIncludes("<result>1</result>"); * ?> * ``` * * @param $xml * @part xml */ public function seeXmlResponseIncludes($xml) { $this->assertContains( XmlUtils::toXml($xml)->C14N(), XmlUtils::toXml($this->connectionModule->_getResponseContent())->C14N(), "found in XML Response" ); } /** * Checks XML response does not include provided XML. * Comparison is done by canonicalizing both xml`s. * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes). * * @param $xml * @part xml */ public function dontSeeXmlResponseIncludes($xml) { $this->assertNotContains( XmlUtils::toXml($xml)->C14N(), XmlUtils::toXml($this->connectionModule->_getResponseContent())->C14N(), "found in XML Response" ); } /** * Checks if the hash of a binary response is exactly the same as provided. * Parameter can be passed as any hash string supported by hash(), with an * optional second parameter to specify the hash type, which defaults to md5. * * Example: Using md5 hash key * * ```php * <?php * $I->seeBinaryResponseEquals("8c90748342f19b195b9c6b4eff742ded"); * ?> * ``` * * Example: Using md5 for a file contents * * ```php * <?php * $fileData = file_get_contents("test_file.jpg"); * $I->seeBinaryResponseEquals(md5($fileData)); * ?> * ``` * Example: Using sha256 hsah * * ```php * <?php * $fileData = '/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k='; // very small jpeg * $I->seeBinaryResponseEquals(hash("sha256", base64_decode($fileData)), 'sha256'); * ?> * ``` * * @param $hash the hashed data response expected * @param $algo the hash algorithm to use. Default md5. * @part json * @part xml */ public function seeBinaryResponseEquals($hash, $algo = 'md5') { $responseHash = hash($algo, $this->connectionModule->_getResponseContent()); $this->assertEquals($hash, $responseHash); } /** * Checks if the hash of a binary response is not the same as provided. * * ```php * <?php * $I->dontSeeBinaryResponseEquals("8c90748342f19b195b9c6b4eff742ded"); * ?> * ``` * Opposite to `seeBinaryResponseEquals` * * @param $hash the hashed data response expected * @param $algo the hash algorithm to use. Default md5. * @part json * @part xml */ public function dontSeeBinaryResponseEquals($hash, $algo = 'md5') { $responseHash = hash($algo, $this->connectionModule->_getResponseContent()); $this->assertNotEquals($hash, $responseHash); } /** * Deprecated since 2.0.9 and removed since 2.1.0 * * @param $path * @throws ModuleException * @deprecated */ public function grabDataFromJsonResponse($path) { throw new ModuleException( $this, "This action was deprecated in Codeception 2.0.9 and removed in 2.1. " . "Please use `grabDataFromResponseByJsonPath` instead" ); } /** * Prevents automatic redirects to be followed by the client * * ```php * <?php * $I->stopFollowingRedirects(); * ``` * * @part xml * @part json */ public function stopFollowingRedirects() { $this->client->followRedirects(false); } /** * Enables automatic redirects to be followed by the client * * ```php * <?php * $I->startFollowingRedirects(); * ``` * * @part xml * @part json */ public function startFollowingRedirects() { $this->client->followRedirects(true); } } <?php namespace Codeception\Module; use Codeception\Module as CodeceptionModule; use Codeception\TestInterface; /** * Wrapper for basic shell commands and shell output * * ## Responsibility * * Maintainer: **davert** * * Status: **stable** * * Contact: codecept@davert.mail.ua * * *Please review the code of non-stable modules and provide patches if you have issues.* */ class Cli extends CodeceptionModule { public $output = ''; public $result = null; public function _before(TestInterface $test) { $this->output = ''; } /** * Executes a shell command. * Fails If exit code is > 0. You can disable this by setting second parameter to false * * ```php * <?php * $I->runShellCommand('phpunit'); * * // do not fail test when command fails * $I->runShellCommand('phpunit', false); * ``` * * @param $command * @param bool $failNonZero */ public function runShellCommand($command, $failNonZero = true) { $data = []; exec("$command", $data, $resultCode); $this->result = $resultCode; $this->output = implode("\n", $data); if ($this->output === null) { \PHPUnit_Framework_Assert::fail("$command can't be executed"); } if ($resultCode !== 0 && $failNonZero) { \PHPUnit_Framework_Assert::fail("Result code was $resultCode.\n\n" . $this->output); } $this->debug(preg_replace('~s/\e\[\d+(?>(;\d+)*)m//g~', '', $this->output)); } /** * Checks that output from last executed command contains text * * @param $text */ public function seeInShellOutput($text) { \PHPUnit_Framework_Assert::assertContains($text, $this->output); } /** * Checks that output from latest command doesn't contain text * * @param $text * */ public function dontSeeInShellOutput($text) { $this->debug($this->output); \PHPUnit_Framework_Assert::assertNotContains($text, $this->output); } /** * @param $regex */ public function seeShellOutputMatches($regex) { \PHPUnit_Framework_Assert::assertRegExp($regex, $this->output); } /** * Checks result code * * ```php * <?php * $I->seeResultCodeIs(0); * ``` * * @param $code */ public function seeResultCodeIs($code) { $this->assertEquals($this->result, $code, "result code is $code"); } /** * Checks result code * * ```php * <?php * $I->seeResultCodeIsNot(0); * ``` * * @param $code */ public function seeResultCodeIsNot($code) { $this->assertNotEquals($this->result, $code, "result code is $code"); } } <?php namespace Codeception\Module; use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleException; use Codeception\Lib\Connector\Laravel5 as LaravelConnector; use Codeception\Lib\Framework; use Codeception\Lib\Interfaces\ActiveRecord; use Codeception\Lib\Interfaces\PartedModule; use Codeception\Lib\Shared\LaravelCommon; use Codeception\Lib\ModuleContainer; use Codeception\Subscriber\ErrorHandler; use Codeception\Util\ReflectionHelper; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model as EloquentModel; use Illuminate\Support\Collection; /** * * This module allows you to run functional tests for Laravel 5. * It should **not** be used for acceptance tests. * See the Acceptance tests section below for more details. * * As of Codeception 2.2 this module only works for Laravel 5.1 and later releases. * If you want to test a Laravel 5.0 application you have to use Codeception 2.1. * You can also upgrade your Laravel application to 5.1, for more details check the Laravel Upgrade Guide * at <https://laravel.com/docs/master/upgrade>. * * ## Demo project * <https://github.com/janhenkgerritsen/codeception-laravel5-sample> * * ## Status * * * Maintainer: **Jan-Henk Gerritsen** * * Stability: **stable** * * ## Example * * modules: * enabled: * - Laravel5: * environment_file: .env.testing * * ## Config * * * cleanup: `boolean`, default `true` - all database queries will be run in a transaction, * which will be rolled back at the end of each test. * * run_database_migrations: `boolean`, default `false` - run database migrations before each test. * * database_migrations_path: `string`, default `null` - path to the database migrations, relative to the root of the application. * * run_database_seeder: `boolean`, default `false` - run database seeder before each test. * * database_seeder_class: `string`, default `` - database seeder class name. * * environment_file: `string`, default `.env` - the environment file to load for the tests. * * bootstrap: `string`, default `bootstrap/app.php` - relative path to app.php config file. * * root: `string`, default `` - root path of the application. * * packages: `string`, default `workbench` - root path of application packages (if any). * * vendor_dir: `string`, default `vendor` - optional relative path to vendor directory. * * disable_exception_handling: `boolean`, default `true` - disable Laravel exception handling. * * disable_middleware: `boolean`, default `false` - disable all middleware. * * disable_events: `boolean`, default `false` - disable events (does not disable model events). * * disable_model_events: `boolean`, default `false` - disable model events. * * url: `string`, default `` - the application URL. * * ## API * * * app - `Illuminate\Foundation\Application` * * config - `array` * * ## Parts * * * ORM - only include the database methods of this module: * * have * * haveMultiple * * haveRecord * * grabRecord * * seeRecord * * dontSeeRecord * * ## Acceptance tests * * You should not use this module for acceptance tests. * If you want to use Laravel functionality with your acceptance tests, * for example to do test setup, you can initialize the Laravel functionality * by adding the following lines of code to the `_bootstrap.php` file of your test suite: * * require 'bootstrap/autoload.php'; * $app = require 'bootstrap/app.php'; * $app->loadEnvironmentFrom('.env.testing'); * $app->instance('request', new \Illuminate\Http\Request); * $app->make('Illuminate\Contracts\Http\Kernel')->bootstrap(); * * */ class Laravel5 extends Framework implements ActiveRecord, PartedModule { use LaravelCommon; /** * @var \Illuminate\Foundation\Application */ public $app; /** * @var array */ public $config = []; /** * Constructor. * * @param ModuleContainer $container * @param array|null $config */ public function __construct(ModuleContainer $container, $config = null) { $this->config = array_merge( [ 'cleanup' => true, 'run_database_migrations' => false, 'database_migrations_path' => null, 'run_database_seeder' => false, 'database_seeder_class' => '', 'environment_file' => '.env', 'bootstrap' => 'bootstrap' . DIRECTORY_SEPARATOR . 'app.php', 'root' => '', 'packages' => 'workbench', 'vendor_dir' => 'vendor', 'disable_exception_handling' => true, 'disable_middleware' => false, 'disable_events' => false, 'disable_model_events' => false, ], (array)$config ); $projectDir = explode($this->config['packages'], \Codeception\Configuration::projectDir())[0]; $projectDir .= $this->config['root']; $this->config['project_dir'] = $projectDir; $this->config['bootstrap_file'] = $projectDir . $this->config['bootstrap']; parent::__construct($container); } /** * @return array */ public function _parts() { return ['orm']; } /** * Initialize hook. */ public function _initialize() { $this->checkBootstrapFileExists(); $this->registerAutoloaders(); $this->revertErrorHandler(); } /** * Before hook. * * @param \Codeception\TestInterface $test */ public function _before(\Codeception\TestInterface $test) { $this->client = new LaravelConnector($this); // Database migrations should run before database cleanup transaction starts if ($this->config['run_database_migrations']) { $this->callArtisan('migrate', ['--path' => $this->config['database_migrations_path']]); } if ($this->applicationUsesDatabase() && $this->config['cleanup']) { $this->app['db']->beginTransaction(); } if ($this->config['run_database_seeder']) { $this->callArtisan('db:seed', ['--class' => $this->config['database_seeder_class']]); } } /** * After hook. * * @param \Codeception\TestInterface $test */ public function _after(\Codeception\TestInterface $test) { if ($this->applicationUsesDatabase()) { $db = $this->app['db']; if ($db instanceof \Illuminate\Database\DatabaseManager) { if ($this->config['cleanup']) { $db->rollback(); } /** * Close all DB connections in order to prevent "Too many connections" issue * * @var \Illuminate\Database\Connection $connection */ foreach ($db->getConnections() as $connection) { $connection->disconnect(); } } } } /** * Does the application use the database? * * @return bool */ private function applicationUsesDatabase() { return ! empty($this->app['config']['database.default']); } /** * Make sure the Laravel bootstrap file exists. * * @throws ModuleConfig */ protected function checkBootstrapFileExists() { $bootstrapFile = $this->config['bootstrap_file']; if (!file_exists($bootstrapFile)) { throw new ModuleConfigException( $this, "Laravel bootstrap file not found in $bootstrapFile.\n" . "Please provide a valid path to it using 'bootstrap' config param. " ); } } /** * Register Laravel autoloaders. */ protected function registerAutoloaders() { require $this->config['project_dir'] . $this->config['vendor_dir'] . DIRECTORY_SEPARATOR . 'autoload.php'; } /** * Revert back to the Codeception error handler, * becauses Laravel registers it's own error handler. */ protected function revertErrorHandler() { $handler = new ErrorHandler(); set_error_handler(array($handler, 'errorHandler')); } /** * Provides access the Laravel application object. * * @return \Illuminate\Foundation\Application */ public function getApplication() { return $this->app; } /** * @param $app */ public function setApplication($app) { $this->app = $app; } /** * Enable Laravel exception handling. * * ``` php * <?php * $I->enableExceptionHandling(); * ?> * ``` */ public function enableExceptionHandling() { $this->client->enableExceptionHandling(); } /** * Disable Laravel exception handling. * * ``` php * <?php * $I->disableExceptionHandling(); * ?> * ``` */ public function disableExceptionHandling() { $this->client->disableExceptionHandling(); } /** * Disable middleware for the next requests. * * ``` php * <?php * $I->disableMiddleware(); * ?> * ``` */ public function disableMiddleware() { $this->client->disableMiddleware(); } /** * Disable events for the next requests. * This method does not disable model events. * To disable model events you have to use the disableModelEvents() method. * * ``` php * <?php * $I->disableEvents(); * ?> * ``` */ public function disableEvents() { $this->client->disableEvents(); } /** * Disable model events for the next requests. * * ``` php * <?php * $I->disableModelEvents(); * ?> * ``` */ public function disableModelEvents() { $this->client->disableModelEvents(); } /** * Make sure events fired during the test. * * ``` php * <?php * $I->seeEventTriggered('App\MyEvent'); * $I->seeEventTriggered(new App\Events\MyEvent()); * $I->seeEventTriggered('App\MyEvent', 'App\MyOtherEvent'); * $I->seeEventTriggered(['App\MyEvent', 'App\MyOtherEvent']); * ?> * ``` * @param $events */ public function seeEventTriggered($events) { $events = is_array($events) ? $events : func_get_args(); foreach ($events as $event) { if (!$this->client->eventTriggered($event)) { if (is_object($event)) { $event = get_class($event); } $this->fail("The '$event' event did not trigger"); } } } /** * Make sure events did not fire during the test. * * ``` php * <?php * $I->dontSeeEventTriggered('App\MyEvent'); * $I->dontSeeEventTriggered(new App\Events\MyEvent()); * $I->dontSeeEventTriggered('App\MyEvent', 'App\MyOtherEvent'); * $I->dontSeeEventTriggered(['App\MyEvent', 'App\MyOtherEvent']); * ?> * ``` * @param $events */ public function dontSeeEventTriggered($events) { $events = is_array($events) ? $events : func_get_args(); foreach ($events as $event) { if ($this->client->eventTriggered($event)) { if (is_object($event)) { $event = get_class($event); } $this->fail("The '$event' event triggered"); } } } /** * Call an Artisan command. * * ``` php * <?php * $I->callArtisan('command:name'); * $I->callArtisan('command:name', ['parameter' => 'value']); * ?> * ``` * @param string $command * @param array $parameters */ public function callArtisan($command, $parameters = []) { $console = $this->app->make('Illuminate\Contracts\Console\Kernel'); $console->call($command, $parameters); return trim($console->output()); } /** * Opens web page using route name and parameters. * * ``` php * <?php * $I->amOnRoute('posts.create'); * ?> * ``` * * @param $routeName * @param array $params */ public function amOnRoute($routeName, $params = []) { $route = $this->getRouteByName($routeName); $absolute = !is_null($route->domain()); $url = $this->app['url']->route($routeName, $params, $absolute); $this->amOnPage($url); } /** * Checks that current url matches route * * ``` php * <?php * $I->seeCurrentRouteIs('posts.index'); * ?> * ``` * @param $routeName */ public function seeCurrentRouteIs($routeName) { $this->getRouteByName($routeName); // Fails if route does not exists $currentRoute = $this->app->request->route(); $currentRouteName = $currentRoute ? $currentRoute->getName() : ''; if ($currentRouteName != $routeName) { $message = empty($currentRouteName) ? "Current route has no name" : "Current route is \"$currentRouteName\""; $this->fail($message); } } /** * Opens web page by action name * * ``` php * <?php * $I->amOnAction('PostsController@index'); * ?> * ``` * * @param $action * @param array $params */ public function amOnAction($action, $params = []) { $route = $this->getRouteByAction($action); $absolute = !is_null($route->domain()); $url = $this->app['url']->action($action, $params, $absolute); $this->amOnPage($url); } /** * Checks that current url matches action * * ``` php * <?php * $I->seeCurrentActionIs('PostsController@index'); * ?> * ``` * * @param $action */ public function seeCurrentActionIs($action) { $this->getRouteByAction($action); // Fails if route does not exists $currentRoute = $this->app->request->route(); $currentAction = $currentRoute ? $currentRoute->getActionName() : ''; $currentAction = ltrim(str_replace($this->getRootControllerNamespace(), "", $currentAction), '\\'); if ($currentAction != $action) { $this->fail("Current action is \"$currentAction\""); } } /** * @param $routeName * @return mixed */ protected function getRouteByName($routeName) { if (!$route = $this->app['routes']->getByName($routeName)) { $this->fail("Route with name '$routeName' does not exist"); } return $route; } /** * @param string $action * @return \Illuminate\Routing\Route */ protected function getRouteByAction($action) { $namespacedAction = $this->actionWithNamespace($action); if (!$route = $this->app['routes']->getByAction($namespacedAction)) { $this->fail("Action '$action' does not exist"); } return $route; } /** * Normalize an action to full namespaced action. * * @param string $action * @return string */ protected function actionWithNamespace($action) { $rootNamespace = $this->getRootControllerNamespace(); if ($rootNamespace && !(strpos($action, '\\') === 0)) { return $rootNamespace . '\\' . $action; } else { return trim($action, '\\'); } } /** * Get the root controller namespace for the application. * * @return string */ protected function getRootControllerNamespace() { $urlGenerator = $this->app['url']; $reflection = new \ReflectionClass($urlGenerator); $property = $reflection->getProperty('rootNamespace'); $property->setAccessible(true); return $property->getValue($urlGenerator); } /** * Assert that a session variable exists. * * ``` php * <?php * $I->seeInSession('key'); * $I->seeInSession('key', 'value'); * ?> * ``` * * @param string|array $key * @param mixed|null $value * @return void */ public function seeInSession($key, $value = null) { if (is_array($key)) { $this->seeSessionHasValues($key); return; } if (! $this->app['session']->has($key)) { $this->fail("No session variable with key '$key'"); } if (! is_null($value)) { $this->assertEquals($value, $this->app['session']->get($key)); } } /** * Assert that the session has a given list of values. * * ``` php * <?php * $I->seeSessionHasValues(['key1', 'key2']); * $I->seeSessionHasValues(['key1' => 'value1', 'key2' => 'value2']); * ?> * ``` * * @param array $bindings * @return void */ public function seeSessionHasValues(array $bindings) { foreach ($bindings as $key => $value) { if (is_int($key)) { $this->seeInSession($value); } else { $this->seeInSession($key, $value); } } } /** * Assert that form errors are bound to the View. * * ``` php * <?php * $I->seeFormHasErrors(); * ?> * ``` * * @return bool */ public function seeFormHasErrors() { $viewErrorBag = $this->app->make('view')->shared('errors'); if (count($viewErrorBag) == 0) { $this->fail("There are no form errors"); } } /** * Assert that there are no form errors bound to the View. * * ``` php * <?php * $I->dontSeeFormErrors(); * ?> * ``` * * @return bool */ public function dontSeeFormErrors() { $viewErrorBag = $this->app->make('view')->shared('errors'); if (count($viewErrorBag) > 0) { $this->fail("Found the following form errors: \n\n" . $viewErrorBag->toJson(JSON_PRETTY_PRINT)); } } /** * Assert that specific form error messages are set in the view. * * This method calls `seeFormErrorMessage` for each entry in the `$bindings` array. * * ``` php * <?php * $I->seeFormErrorMessages([ * 'username' => 'Invalid Username', * 'password' => null, * ]); * ?> * ``` * @param array $bindings */ public function seeFormErrorMessages(array $bindings) { foreach ($bindings as $key => $value) { $this->seeFormErrorMessage($key, $value); } } /** * Assert that a specific form error message is set in the view. * * If you want to assert that there is a form error message for a specific key * but don't care about the actual error message you can omit `$expectedErrorMessage`. * * If you do pass `$expectedErrorMessage`, this method checks if the actual error message for a key * contains `$expectedErrorMessage`. * * ``` php * <?php * $I->seeFormErrorMessage('username'); * $I->seeFormErrorMessage('username', 'Invalid Username'); * ?> * ``` * @param string $key * @param string|null $expectedErrorMessage */ public function seeFormErrorMessage($key, $expectedErrorMessage = null) { $viewErrorBag = $this->app['view']->shared('errors'); if (!($viewErrorBag->has($key))) { $this->fail("No form error message for key '$key'\n"); } if (! is_null($expectedErrorMessage)) { $this->assertContains($expectedErrorMessage, $viewErrorBag->first($key)); } } /** * Set the currently logged in user for the application. * Takes either an object that implements the User interface or * an array of credentials. * * ``` php * <?php * // provide array of credentials * $I->amLoggedAs(['username' => 'jane@example.com', 'password' => 'password']); * * // provide User object * $I->amLoggedAs( new User ); * * // can be verified with $I->seeAuthentication(); * ?> * ``` * @param \Illuminate\Contracts\Auth\User|array $user * @param string|null $driver The authentication driver for Laravel <= 5.1.*, guard name for Laravel >= 5.2 * @return void */ public function amLoggedAs($user, $driver = null) { $guard = $auth = $this->app['auth']; if (method_exists($auth, 'driver')) { $guard = $auth->driver($driver); } if (method_exists($auth, 'guard')) { $guard = $auth->guard($driver); } if ($user instanceof Authenticatable) { $guard->login($user); return; } if (! $guard->attempt($user)) { $this->fail("Failed to login with credentials " . json_encode($user)); } } /** * Logout user. */ public function logout() { $this->app['auth']->logout(); } /** * Checks that a user is authenticated. * You can specify the guard that should be use for Laravel >= 5.2. * @param string|null $guard */ public function seeAuthentication($guard = null) { $auth = $this->app['auth']; if (method_exists($auth, 'guard')) { $auth = $auth->guard($guard); } if (! $auth->check()) { $this->fail("There is no authenticated user"); } } /** * Check that user is not authenticated. * You can specify the guard that should be use for Laravel >= 5.2. * @param string|null $guard */ public function dontSeeAuthentication($guard = null) { $auth = $this->app['auth']; if (method_exists($auth, 'guard')) { $auth = $auth->guard($guard); } if ($auth->check()) { $this->fail("There is an authenticated user"); } } /** * Return an instance of a class from the Laravel service container. * (https://laravel.com/docs/master/container) * * ``` php * <?php * // In Laravel * App::bind('foo', function($app) * { * return new FooBar; * }); * * // Then in test * $service = $I->grabService('foo'); * * // Will return an instance of FooBar, also works for singletons. * ?> * ``` * * @param string $class * @return mixed */ public function grabService($class) { return $this->app[$class]; } /** * Inserts record into the database. * If you pass the name of a database table as the first argument, this method returns an integer ID. * You can also pass the class name of an Eloquent model, in that case this method returns an Eloquent model. * * ``` php * <?php * $user_id = $I->haveRecord('users', array('name' => 'Davert')); // returns integer * $user = $I->haveRecord('App\User', array('name' => 'Davert')); // returns Eloquent model * ?> * ``` * * @param string $table * @param array $attributes * @return integer|EloquentModel * @part orm */ public function haveRecord($table, $attributes = []) { if (class_exists($table)) { $model = new $table; if (! $model instanceof EloquentModel) { throw new \RuntimeException("Class $table is not an Eloquent model"); } $model->fill($attributes)->save(); return $model; } try { return $this->app['db']->table($table)->insertGetId($attributes); } catch (\Exception $e) { $this->fail("Could not insert record into table '$table':\n\n" . $e->getMessage()); } } /** * Checks that record exists in database. * You can pass the name of a database table or the class name of an Eloquent model as the first argument. * * ``` php * <?php * $I->seeRecord('users', array('name' => 'davert')); * $I->seeRecord('App\User', array('name' => 'davert')); * ?> * ``` * * @param string $table * @param array $attributes * @part orm */ public function seeRecord($table, $attributes = []) { if (class_exists($table)) { if (! $this->findModel($table, $attributes)) { $this->fail("Could not find $table with " . json_encode($attributes)); } } elseif (! $this->findRecord($table, $attributes)) { $this->fail("Could not find matching record in table '$table'"); } } /** * Checks that record does not exist in database. * You can pass the name of a database table or the class name of an Eloquent model as the first argument. * * ``` php * <?php * $I->dontSeeRecord('users', array('name' => 'davert')); * $I->dontSeeRecord('App\User', array('name' => 'davert')); * ?> * ``` * * @param string $table * @param array $attributes * @part orm */ public function dontSeeRecord($table, $attributes = []) { if (class_exists($table)) { if ($this->findModel($table, $attributes)) { $this->fail("Unexpectedly found matching $table with " . json_encode($attributes)); } } elseif ($this->findRecord($table, $attributes)) { $this->fail("Unexpectedly found matching record in table '$table'"); } } /** * Retrieves record from database * If you pass the name of a database table as the first argument, this method returns an array. * You can also pass the class name of an Eloquent model, in that case this method returns an Eloquent model. * * ``` php * <?php * $record = $I->grabRecord('users', array('name' => 'davert')); // returns array * $record = $I->grabRecord('App\User', array('name' => 'davert')); // returns Eloquent model * ?> * ``` * * @param string $table * @param array $attributes * @return array|EloquentModel * @part orm */ public function grabRecord($table, $attributes = []) { if (class_exists($table)) { if (! $model = $this->findModel($table, $attributes)) { $this->fail("Could not find $table with " . json_encode($attributes)); } return $model; } if (! $record = $this->findRecord($table, $attributes)) { $this->fail("Could not find matching record in table '$table'"); } return $record; } /** * Checks that number of given records were found in database. * You can pass the name of a database table or the class name of an Eloquent model as the first argument. * * ``` php * <?php * $I->seeNumRecords(1, 'users', array('name' => 'davert')); * $I->seeNumRecords(1, 'App\User', array('name' => 'davert')); * ?> * ``` * * @param integer $expectedNum * @param string $table * @param array $attributes * @part orm */ public function seeNumRecords($expectedNum, $table, $attributes = []) { if (class_exists($table)) { $currentNum = $this->countModels($table, $attributes); if ($currentNum != $expectedNum) { $this->fail("The number of found $table ($currentNum) does not match expected number $expectedNum with " . json_encode($attributes)); } } else { $currentNum = $this->countRecords($table, $attributes); if ($currentNum != $expectedNum) { $this->fail("The number of found records ($currentNum) does not match expected number $expectedNum in table $table with " . json_encode($attributes)); } } } /** * Retrieves number of records from database * You can pass the name of a database table or the class name of an Eloquent model as the first argument. * * ``` php * <?php * $I->grabNumRecords('users', array('name' => 'davert')); * $I->grabNumRecords('App\User', array('name' => 'davert')); * ?> * ``` * * @param string $table * @param array $attributes * @return integer * @part orm */ public function grabNumRecords($table, $attributes = []) { return class_exists($table)? $this->countModels($table, $attributes) : $this->countRecords($table, $attributes); } /** * @param string $modelClass * @param array $attributes * * @return EloquentModel */ protected function findModel($modelClass, $attributes = []) { $query = $this->getQueryBuilderFromModel($modelClass); foreach ($attributes as $key => $value) { $query->where($key, $value); } return $query->first(); } /** * @param string $table * @param array $attributes * @return array */ protected function findRecord($table, $attributes = []) { $query = $this->getQueryBuilderFromTable($table); foreach ($attributes as $key => $value) { $query->where($key, $value); } return (array) $query->first(); } /** * @param string $modelClass * @param array $attributes * @return integer */ protected function countModels($modelClass, $attributes = []) { $query = $this->getQueryBuilderFromModel($modelClass); foreach ($attributes as $key => $value) { $query->where($key, $value); } return $query->count(); } /** * @param string $table * @param array $attributes * @return integer */ protected function countRecords($table, $attributes = []) { $query = $this->getQueryBuilderFromTable($table); foreach ($attributes as $key => $value) { $query->where($key, $value); } return $query->count(); } /** * @param string $modelClass * * @return EloquentModel */ protected function getQueryBuilderFromModel($modelClass) { $model = new $modelClass; if (!$model instanceof EloquentModel) { throw new \RuntimeException("Class $modelClass is not an Eloquent model"); } return $model->newQuery(); } /** * @param string $table * * @return EloquentModel */ protected function getQueryBuilderFromTable($table) { return $this->app['db']->table($table); } /** * Use Laravel's model factory to create a model. * Can only be used with Laravel 5.1 and later. * * ``` php * <?php * $I->have('App\User'); * $I->have('App\User', ['name' => 'John Doe']); * $I->have('App\User', [], 'admin'); * ?> * ``` * * @see http://laravel.com/docs/5.1/testing#model-factories * @param string $model * @param array $attributes * @param string $name * @return mixed * @part orm */ public function have($model, $attributes = [], $name = 'default') { try { $result = $this->modelFactory($model, $name)->create($attributes); // Since Laravel 5.4 the model factory returns a collection instead of a single object if ($result instanceof Collection) { $result = $result[0]; } return $result; } catch (\Exception $e) { $this->fail("Could not create model: \n\n" . get_class($e) . "\n\n" . $e->getMessage()); } } /** * Use Laravel's model factory to create multiple models. * Can only be used with Laravel 5.1 and later. * * ``` php * <?php * $I->haveMultiple('App\User', 10); * $I->haveMultiple('App\User', 10, ['name' => 'John Doe']); * $I->haveMultiple('App\User', 10, [], 'admin'); * ?> * ``` * * @see http://laravel.com/docs/5.1/testing#model-factories * @param string $model * @param int $times * @param array $attributes * @param string $name * @return mixed * @part orm */ public function haveMultiple($model, $times, $attributes = [], $name = 'default') { try { return $this->modelFactory($model, $name, $times)->create($attributes); } catch (\Exception $e) { $this->fail("Could not create model: \n\n" . get_class($e) . "\n\n" . $e->getMessage()); } } /** * @param string $model * @param string $name * @param int $times * @return \Illuminate\Database\Eloquent\FactoryBuilder * @throws ModuleException */ protected function modelFactory($model, $name, $times = 1) { if (! function_exists('factory')) { throw new ModuleException($this, 'The factory() method does not exist. ' . 'This functionality relies on Laravel model factories, which were introduced in Laravel 5.1.'); } return factory($model, $name, $times); } /** * Returns a list of recognized domain names. * This elements of this list are regular expressions. * * @return array */ protected function getInternalDomains() { $internalDomains = [$this->getApplicationDomainRegex()]; foreach ($this->app['routes'] as $route) { if (!is_null($route->domain())) { $internalDomains[] = $this->getDomainRegex($route); } } return array_unique($internalDomains); } /** * @return string */ private function getApplicationDomainRegex() { $server = ReflectionHelper::readPrivateProperty($this->client, 'server'); $domain = $server['HTTP_HOST']; return '/^' . str_replace('.', '\.', $domain) . '$/'; } /** * Get the regex for matching the domain part of this route. * * @param \Illuminate\Routing\Route $route * @return string */ private function getDomainRegex($route) { ReflectionHelper::invokePrivateMethod($route, 'compileRoute'); $compiledRoute = ReflectionHelper::readPrivateProperty($route, 'compiled'); return $compiledRoute->getHostRegex(); } } <?php namespace Codeception\Module; use Codeception\Lib\Interfaces\DataMapper; use Codeception\Lib\Interfaces\DependsOnModule; use Codeception\Lib\Interfaces\ORM; use Codeception\Exception\ModuleException; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\TestInterface; use League\FactoryMuffin\FactoryMuffin; use League\FactoryMuffin\Stores\RepositoryStore; /** * DataFactory allows you to easily generate and create test data using [**FactoryMuffin**](https://github.com/thephpleague/factory-muffin). * DataFactory uses an ORM of your application to define, save and cleanup data. Thus, should be used with ORM or Framework modules. * * This module requires packages installed: * * ```json * { * "league/factory-muffin": "^3.0", * "league/factory-muffin-faker": "^1.0" * } * ``` * * Generation rules can be defined in a factories file. You will need to create `factories.php` (it is recommended to store it in `_support` dir) * Follow [FactoryMuffin documentation](https://github.com/thephpleague/factory-muffin) to set valid rules. * Random data provided by [Faker](https://github.com/fzaninotto/Faker) library. * * ```php * <?php * use League\FactoryMuffin\Faker\Facade as Faker; * * $fm->define(User::class)->setDefinitions([ * 'name' => Faker::name(), * * // generate email * 'email' => Faker::email(), * 'body' => Faker::text(), * * // generate a profile and return its Id * 'profile_id' => 'factory|Profile' *]); * ``` * * Configure this module to load factory definitions from a directory. * You should also specify a module with an ORM as a dependency. * * ```yaml * modules: * enabled: * - Yii2: * configFile: path/to/config.php * - DataFactory: * factories: tests/_support/factories * depends: Yii2 * ``` * * (you can also use Laravel5 and Phalcon). * * In this example factories are loaded from `tests/_support/factories` directory. Please note that this directory is relative from the codeception.yml file (so for Yii2 it would be codeception/_support/factories). gst * You should create this directory manually and create PHP files in it with factories definitions following [official documentation](https://github.com/thephpleague/factory-muffin#usage). * * In cases you want to use data from database inside your factory definitions you can define them in Helper. * For instance, if you use Doctrine, this allows you to access `EntityManager` inside a definition. * * To proceed you should create Factories helper via `generate:helper` command and enable it: * * ``` * modules: * enabled: * - DataFactory: * depends: Doctrine2 * - \Helper\Factories * * ``` * * In this case you can define factories from a Helper class with `_define` method. * * ```php * <?php * public function _beforeSuite() * { * $factory = $this->getModule('DataFactory'); * // let us get EntityManager from Doctrine * $em = $this->getModule('Doctrine2')->_getEntityManager(); * * $factory->_define(User::class, [ * * // generate random user name * // use League\FactoryMuffin\Faker\Facade as Faker; * 'name' => Faker::name(), * * // get real company from database * 'company' => $em->getRepository(Company::class)->find(), * * // let's generate a profile for each created user * // receive an entity and set it via `setProfile` method * // UserProfile factory should be defined as well * 'profile' => 'entity|'.UserProfile::class * ]); * } * ``` * * Factory Definitions are described in official [Factory Muffin Documentation](https://github.com/thephpleague/factory-muffin) * * ### Related Models Generators * * If your module relies on other model you can generate them both. * To create a related module you can use either `factory` or `entity` prefix, depending on ORM you use. * * In case your ORM expects an Id of a related record (Eloquent) to be set use `factory` prefix: * * ```php * 'user_id' => 'factory|User' * ``` * * In case your ORM expects a related record itself (Doctrine) then you should use `entity` prefix: * * ```php * 'user' => 'entity|User' * ``` */ class DataFactory extends \Codeception\Module implements DependsOnModule, RequiresPackage { protected $dependencyMessage = <<<EOF ORM module (like Doctrine2) or Framework module with ActiveRecord support is required: -- modules: enabled: - DataFactory: depends: Doctrine2 -- EOF; /** * ORM module on which we we depend on. * * @var ORM */ public $ormModule; /** * @var FactoryMuffin */ public $factoryMuffin; protected $config = ['factories' => null]; public function _requires() { return [ 'League\FactoryMuffin\FactoryMuffin' => '"league/factory-muffin": "^3.0"', 'League\FactoryMuffin\Faker\Facade' => '"league/factory-muffin-faker": "^1.0"' ]; } public function _beforeSuite($settings = []) { $store = $this->getStore(); $this->factoryMuffin = new FactoryMuffin($store); if ($this->config['factories']) { foreach ((array) $this->config['factories'] as $factoryPath) { $realpath = realpath(codecept_root_dir().$factoryPath); if ($realpath === false) { throw new ModuleException($this, 'The path to one of your factories is not correct. Please specify the directory relative to the codeception.yml file (ie. _support/factories).'); } $this->factoryMuffin->loadFactories($realpath); } } } /** * @return StoreInterface|null */ protected function getStore() { return $this->ormModule instanceof DataMapper ? new RepositoryStore($this->ormModule->_getEntityManager()) // for Doctrine : null; } public function _inject(ORM $orm) { $this->ormModule = $orm; } public function _after(TestInterface $test) { $skipCleanup = array_key_exists('cleanup', $this->config) && $this->config['cleanup'] === false; if ($skipCleanup || $this->ormModule->_getConfig('cleanup')) { return; } $this->factoryMuffin->deleteSaved(); } public function _depends() { return ['Codeception\Lib\Interfaces\ORM' => $this->dependencyMessage]; } /** * Creates a model definition. This can be used from a helper:. * * ```php * $this->getModule('{{MODULE_NAME}}')->_define('User', [ * 'name' => $faker->name, * 'email' => $faker->email * ]); * * ``` * * @param $model * @param $fields * * @return \League\FactoryMuffin\Definition * * @throws \League\FactoryMuffin\Exceptions\DefinitionAlreadyDefinedException */ public function _define($model, $fields) { return $this->factoryMuffin->define($model)->setDefinitions($fields); } /** * Generates and saves a record,. * * ```php * $I->have('User'); // creates user * $I->have('User', ['is_active' => true]); // creates active user * ``` * * Returns an instance of created user. * * @param $name * @param array $extraAttrs * * @return object */ public function have($name, array $extraAttrs = []) { return $this->factoryMuffin->create($name, $extraAttrs); } /** * Generates and saves a record multiple times. * * ```php * $I->haveMultiple('User', 10); // create 10 users * $I->haveMultiple('User', 10, ['is_active' => true]); // create 10 active users * ``` * * @param $name * @param $times * @param array $extraAttrs * * @return \object[] */ public function haveMultiple($name, $times, array $extraAttrs = []) { return $this->factoryMuffin->seed($times, $name, $extraAttrs); } } <?php namespace Codeception\Module; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\Module as CodeceptionModule; use Codeception\Exception\ModuleException; use Codeception\TestInterface; use Predis\Client as RedisDriver; /** * This module uses the [Predis](https://github.com/nrk/predis) library * to interact with a Redis server. * * ## Status * * * Stability: **beta** * * ## Configuration * * * **`host`** (`string`, default `'127.0.0.1'`) - The Redis host * * **`port`** (`int`, default `6379`) - The Redis port * * **`database`** (`int`, no default) - The Redis database. Needs to be specified. * * **`cleanupBefore`**: (`string`, default `'never'`) - Whether/when to flush the database: * * `suite`: at the beginning of every suite * * `test`: at the beginning of every test * * Any other value: never * * ### Example (`unit.suite.yml`) * * ```yaml * modules: * - Redis: * host: '127.0.0.1' * port: 6379 * database: 0 * cleanupBefore: 'never' * ``` * * ## Public Properties * * * **driver** - Contains the Predis client/driver * * @author Marc Verney <marc@marcverney.net> */ class Redis extends CodeceptionModule implements RequiresPackage { /** * {@inheritdoc} * * No default value is set for the database, using this parameter. */ protected $config = [ 'host' => '127.0.0.1', 'port' => 6379, 'cleanupBefore' => 'never' ]; /** * {@inheritdoc} */ protected $requiredFields = [ 'database' ]; /** * The Redis driver * * @var RedisDriver */ public $driver; public function _requires() { return ['Predis\Client' => '"predis/predis": "^1.0"']; } /** * Instructions to run after configuration is loaded * * @throws ModuleException */ public function _initialize() { try { $this->driver = new RedisDriver([ 'host' => $this->config['host'], 'port' => $this->config['port'], 'database' => $this->config['database'] ]); } catch (\Exception $e) { throw new ModuleException( __CLASS__, $e->getMessage() ); } } /** * Code to run before each suite * * @param array $settings */ public function _beforeSuite($settings = []) { if ($this->config['cleanupBefore'] === 'suite') { $this->cleanup(); } } /** * Code to run before each test * * @param TestInterface $test */ public function _before(TestInterface $test) { if ($this->config['cleanupBefore'] === 'test') { $this->cleanup(); } } /** * Delete all the keys in the Redis database * * @throws ModuleException */ public function cleanup() { try { $this->driver->flushdb(); } catch (\Exception $e) { throw new ModuleException( __CLASS__, $e->getMessage() ); } } /** * Returns the value of a given key * * Examples: * * ``` php * <?php * // Strings * $I->grabFromRedis('string'); * * // Lists: get all members * $I->grabFromRedis('example:list'); * * // Lists: get a specific member * $I->grabFromRedis('example:list', 2); * * // Lists: get a range of elements * $I->grabFromRedis('example:list', 2, 4); * * // Sets: get all members * $I->grabFromRedis('example:set'); * * // ZSets: get all members * $I->grabFromRedis('example:zset'); * * // ZSets: get a range of members * $I->grabFromRedis('example:zset', 3, 12); * * // Hashes: get all fields of a key * $I->grabFromRedis('example:hash'); * * // Hashes: get a specific field of a key * $I->grabFromRedis('example:hash', 'foo'); * ``` * * @param string $key The key name * * @return mixed * * @throws ModuleException if the key does not exist */ public function grabFromRedis($key) { $args = func_get_args(); switch ($this->driver->type($key)) { case 'none': throw new ModuleException( $this, "Cannot grab key \"$key\" as it does not exist" ); break; case 'string': $reply = $this->driver->get($key); break; case 'list': if (count($args) === 2) { $reply = $this->driver->lindex($key, $args[1]); } else { $reply = $this->driver->lrange( $key, isset($args[1]) ? $args[1] : 0, isset($args[2]) ? $args[2] : -1 ); } break; case 'set': $reply = $this->driver->smembers($key); break; case 'zset': if (count($args) === 2) { throw new ModuleException( $this, "The method grabFromRedis(), when used with sorted " . "sets, expects either one argument or three" ); } $reply = $this->driver->zrange( $key, isset($args[2]) ? $args[1] : 0, isset($args[2]) ? $args[2] : -1, 'WITHSCORES' ); break; case 'hash': $reply = isset($args[1]) ? $this->driver->hget($key, $args[1]) : $this->driver->hgetall($key); break; default: $reply = null; } return $reply; } /** * Creates or modifies keys * * If $key already exists: * * - Strings: its value will be overwritten with $value * - Other types: $value items will be appended to its value * * Examples: * * ``` php * <?php * // Strings: $value must be a scalar * $I->haveInRedis('string', 'Obladi Oblada'); * * // Lists: $value can be a scalar or an array * $I->haveInRedis('list', ['riri', 'fifi', 'loulou']); * * // Sets: $value can be a scalar or an array * $I->haveInRedis('set', ['riri', 'fifi', 'loulou']); * * // ZSets: $value must be an associative array with scores * $I->haveInRedis('zset', ['riri' => 1, 'fifi' => 2, 'loulou' => 3]); * * // Hashes: $value must be an associative array * $I->haveInRedis('hash', ['obladi' => 'oblada']); * ``` * * @param string $type The type of the key * @param string $key The key name * @param mixed $value The value * * @throws ModuleException */ public function haveInRedis($type, $key, $value) { switch (strtolower($type)) { case 'string': if (!is_scalar($value)) { throw new ModuleException( $this, 'If second argument of haveInRedis() method is "string", ' . 'third argument must be a scalar' ); } $this->driver->set($key, $value); break; case 'list': $this->driver->rpush($key, $value); break; case 'set': $this->driver->sadd($key, $value); break; case 'zset': if (!is_array($value)) { throw new ModuleException( $this, 'If second argument of haveInRedis() method is "zset", ' . 'third argument must be an (associative) array' ); } $this->driver->zadd($key, $value); break; case 'hash': if (!is_array($value)) { throw new ModuleException( $this, 'If second argument of haveInRedis() method is "hash", ' . 'third argument must be an array' ); } $this->driver->hmset($key, $value); break; default: throw new ModuleException( $this, "Unknown type \"$type\" for key \"$key\". Allowed types are " . '"string", "list", "set", "zset", "hash"' ); } } /** * Asserts that a key does not exist or, optionaly, that it doesn't have the * provided $value * * Examples: * * ``` php * <?php * // With only one argument, only checks the key does not exist * $I->dontSeeInRedis('example:string'); * * // Checks a String does not exist or its value is not the one provided * $I->dontSeeInRedis('example:string', 'life'); * * // Checks a List does not exist or its value is not the one provided (order of elements is compared). * $I->dontSeeInRedis('example:list', ['riri', 'fifi', 'loulou']); * * // Checks a Set does not exist or its value is not the one provided (order of members is ignored). * $I->dontSeeInRedis('example:set', ['riri', 'fifi', 'loulou']); * * // Checks a ZSet does not exist or its value is not the one provided (scores are required, order of members is compared) * $I->dontSeeInRedis('example:zset', ['riri' => 1, 'fifi' => 2, 'loulou' => 3]); * * // Checks a Hash does not exist or its value is not the one provided (order of members is ignored). * $I->dontSeeInRedis('example:hash', ['riri' => true, 'fifi' => 'Dewey', 'loulou' => 2]); * ``` * * @param string $key The key name * @param mixed $value Optional. If specified, also checks the key has this * value. Booleans will be converted to 1 and 0 (even inside arrays) */ public function dontSeeInRedis($key, $value = null) { $this->assertFalse( (bool) $this->checkKeyExists($key, $value), "The key \"$key\" exists" . ($value ? ' and its value matches the one provided' : '') ); } /** * Asserts that a given key does not contain a given item * * Examples: * * ``` php * <?php * // Strings: performs a substring search * $I->dontSeeRedisKeyContains('string', 'bar'); * * // Lists * $I->dontSeeRedisKeyContains('example:list', 'poney'); * * // Sets * $I->dontSeeRedisKeyContains('example:set', 'cat'); * * // ZSets: check whether the zset has this member * $I->dontSeeRedisKeyContains('example:zset', 'jordan'); * * // ZSets: check whether the zset has this member with this score * $I->dontSeeRedisKeyContains('example:zset', 'jordan', 23); * * // Hashes: check whether the hash has this field * $I->dontSeeRedisKeyContains('example:hash', 'magic'); * * // Hashes: check whether the hash has this field with this value * $I->dontSeeRedisKeyContains('example:hash', 'magic', 32); * ``` * * @param string $key The key * @param mixed $item The item * @param null $itemValue Optional and only used for zsets and hashes. If * specified, the method will also check that the $item has this value/score * * @return bool */ public function dontSeeRedisKeyContains($key, $item, $itemValue = null) { $this->assertFalse( (bool) $this->checkKeyContains($key, $item, $itemValue), "The key \"$key\" contains " . ( is_null($itemValue) ? "\"$item\"" : "[\"$item\" => \"$itemValue\"]" ) ); } /** * Asserts that a key exists, and optionally that it has the provided $value * * Examples: * * ``` php * <?php * // With only one argument, only checks the key exists * $I->seeInRedis('example:string'); * * // Checks a String exists and has the value "life" * $I->seeInRedis('example:string', 'life'); * * // Checks the value of a List. Order of elements is compared. * $I->seeInRedis('example:list', ['riri', 'fifi', 'loulou']); * * // Checks the value of a Set. Order of members is ignored. * $I->seeInRedis('example:set', ['riri', 'fifi', 'loulou']); * * // Checks the value of a ZSet. Scores are required. Order of members is compared. * $I->seeInRedis('example:zset', ['riri' => 1, 'fifi' => 2, 'loulou' => 3]); * * // Checks the value of a Hash. Order of members is ignored. * $I->seeInRedis('example:hash', ['riri' => true, 'fifi' => 'Dewey', 'loulou' => 2]); * ``` * * @param string $key The key name * @param mixed $value Optional. If specified, also checks the key has this * value. Booleans will be converted to 1 and 0 (even inside arrays) */ public function seeInRedis($key, $value = null) { $this->assertTrue( (bool) $this->checkKeyExists($key, $value), "Cannot find key \"$key\"" . ($value ? ' with the provided value' : '') ); } /** * Sends a command directly to the Redis driver. See documentation at * https://github.com/nrk/predis * Every argument that follows the $command name will be passed to it. * * Examples: * * ``` php * <?php * $I->sendCommandToRedis('incr', 'example:string'); * $I->sendCommandToRedis('strLen', 'example:string'); * $I->sendCommandToRedis('lPop', 'example:list'); * $I->sendCommandToRedis('zRangeByScore', 'example:set', '-inf', '+inf', ['withscores' => true, 'limit' => [1, 2]]); * $I->sendCommandToRedis('flushdb'); * ``` * * @param string $command The command name * * @return mixed */ public function sendCommandToRedis($command) { return call_user_func_array( [$this->driver, $command], array_slice(func_get_args(), 1) ); } /** * Asserts that a given key contains a given item * * Examples: * * ``` php * <?php * // Strings: performs a substring search * $I->seeRedisKeyContains('example:string', 'bar'); * * // Lists * $I->seeRedisKeyContains('example:list', 'poney'); * * // Sets * $I->seeRedisKeyContains('example:set', 'cat'); * * // ZSets: check whether the zset has this member * $I->seeRedisKeyContains('example:zset', 'jordan'); * * // ZSets: check whether the zset has this member with this score * $I->seeRedisKeyContains('example:zset', 'jordan', 23); * * // Hashes: check whether the hash has this field * $I->seeRedisKeyContains('example:hash', 'magic'); * * // Hashes: check whether the hash has this field with this value * $I->seeRedisKeyContains('example:hash', 'magic', 32); * ``` * * @param string $key The key * @param mixed $item The item * @param null $itemValue Optional and only used for zsets and hashes. If * specified, the method will also check that the $item has this value/score * * @return bool */ public function seeRedisKeyContains($key, $item, $itemValue = null) { $this->assertTrue( (bool) $this->checkKeyContains($key, $item, $itemValue), "The key \"$key\" does not contain " . ( is_null($itemValue) ? "\"$item\"" : "[\"$item\" => \"$itemValue\"]" ) ); } /** * Converts boolean values to "0" and "1" * * @param mixed $var The variable * * @return mixed */ private function boolToString($var) { $copy = is_array($var) ? $var : [$var]; foreach ($copy as $key => $value) { if (is_bool($value)) { $copy[$key] = $value ? '1' : '0'; } } return is_array($var) ? $copy : $copy[0]; } /** * Checks whether a key contains a given item * * @param string $key The key * @param mixed $item The item * @param null $itemValue Optional and only used for zsets and hashes. If * specified, the method will also check that the $item has this value/score * * @return bool * * @throws ModuleException */ private function checkKeyContains($key, $item, $itemValue = null) { $result = null; if (!is_scalar($item)) { throw new ModuleException( $this, "All arguments of [dont]seeRedisKeyContains() must be scalars" ); } switch ($this->driver->type($key)) { case 'string': $reply = $this->driver->get($key); $result = strpos($reply, $item) !== false; break; case 'list': $reply = $this->driver->lrange($key, 0, -1); $result = in_array($item, $reply); break; case 'set': $result = $this->driver->sismember($key, $item); break; case 'zset': $reply = $this->driver->zscore($key, $item); if (is_null($reply)) { $result = false; } elseif (!is_null($itemValue)) { $result = (float) $reply === (float) $itemValue; } else { $result = true; } break; case 'hash': $reply = $this->driver->hget($key, $item); $result = is_null($itemValue) ? !is_null($reply) : (string) $reply === (string) $itemValue; break; case 'none': throw new ModuleException( $this, "Key \"$key\" does not exist" ); break; } return $result; } /** * Checks whether a key exists and, optionally, whether it has a given $value * * @param string $key The key name * @param mixed $value Optional. If specified, also checks the key has this * value. Booleans will be converted to 1 and 0 (even inside arrays) * * @return bool */ private function checkKeyExists($key, $value = null) { $type = $this->driver->type($key); if (is_null($value)) { return $type != 'none'; } $value = $this->boolToString($value); switch ($type) { case 'string': $reply = $this->driver->get($key); // Allow non strict equality (2 equals '2') $result = $reply == $value; break; case 'list': $reply = $this->driver->lrange($key, 0, -1); // Check both arrays have the same key/value pairs + same order $result = $reply === $value; break; case 'set': $reply = $this->driver->smembers($key); // Only check both arrays have the same values sort($reply); sort($value); $result = $reply === $value; break; case 'zset': $reply = $this->driver->zrange($key, 0, -1, 'WITHSCORES'); // Check both arrays have the same key/value pairs + same order $reply = $this->scoresToFloat($reply); $value = $this->scoresToFloat($value); $result = $reply === $value; break; case 'hash': $reply = $this->driver->hgetall($key); // Only check both arrays have the same key/value pairs (==) $result = $reply == $value; break; default: $result = false; } return $result; } /** * Explicitly cast the scores of a Zset associative array as float/double * * @param array $arr The ZSet associative array * * @return array */ private function scoresToFloat(array $arr) { foreach ($arr as $member => $score) { $arr[$member] = (float) $score; } return $arr; } } <?php namespace Codeception\Module; use Codeception\Lib\Framework; use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Interfaces\PartedModule; use Codeception\TestInterface; use Codeception\Lib\Connector\Yii1 as Yii1Connector; use Codeception\Util\ReflectionHelper; use Yii; /** * This module provides integration with [Yii Framework 1.1](http://www.yiiframework.com/doc/guide/). * * The following configurations are available for this module: * * * `appPath` - full path to the application, include index.php</li> * * `url` - full url to the index.php entry script</li> * * In your index.php you must return an array with correct configuration for the application: * * For the simple created yii application index.php will be like this: * * ```php * <?php * // change the following paths if necessary * $yii=dirname(__FILE__).'/../yii/framework/yii.php'; * $config=dirname(__FILE__).'/protected/config/main.php'; * * // remove the following lines when in production mode * defined('YII_DEBUG') or define('YII_DEBUG',true); * // specify how many levels of call stack should be shown in each log message * defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3); * require_once($yii); * return array( * 'class' => 'CWebApplication', * 'config' => $config, * ); * ``` * * You can use this module by setting params in your `functional.suite.yml`: * * ```yaml * actor: FunctionalTester * modules: * enabled: * - Yii1: * appPath: '/path/to/index.php' * url: 'http://localhost/path/to/index.php' * - \Helper\Functional * ``` * * You will also need to install [Codeception-Yii Bridge](https://github.com/Codeception/YiiBridge) * which include component wrappers for testing. * * When you are done, you can test this module by creating new empty Yii application and creating this Cept scenario: * * ``` * php codecept.phar g:cept functional IndexCept * ``` * * and write it as in example: * * ```php * <?php * $I = new FunctionalTester($scenario); * $I->wantTo('Test index page'); * $I->amOnPage('/index.php'); * $I->see('My Web Application','#header #logo'); * $I->click('Login'); * $I->see('Login','h1'); * $I->see('Username'); * $I->fillField('#LoginForm_username','demo'); * $I->fillField('#LoginForm_password','demo'); * $I->click('#login-form input[type="submit"]'); * $I->seeLink('Logout (demo)'); * $I->click('Logout (demo)'); * $I->seeLink('Login'); * ``` * * Then run codeception: php codecept.phar --steps run functional * You must see "OK" and that all steps are marked with asterisk (*). * Do not forget that after adding module in your functional.suite.yml you must run codeception "build" command. * * ### Public Properties * * `client`: instance of `\Codeception\Lib\Connector\Yii1` * * ### Parts * * If you ever encounter error message: * * ``` * Yii1 module conflicts with WebDriver * ``` * * you should include Yii module partially, with `init` part only * * * `init`: only initializes module and not provides any actions from it. Can be used for unit/acceptance tests to avoid conflicts. * * ### Acceptance Testing Example: * * In `acceptance.suite.yml`: * * ```yaml * class_name: AcceptanceTester * modules: * enabled: * - WebDriver: * browser: firefox * url: http://localhost * - Yii1: * appPath: '/path/to/index.php' * url: 'http://localhost/path/to/index.php' * part: init # to not conflict with WebDriver * - \Helper\Acceptance * ``` */ class Yii1 extends Framework implements PartedModule { /** * Application path and url must be set always * @var array */ protected $requiredFields = ['appPath', 'url']; /** * Application settings array('class'=>'YourAppClass','config'=>'YourAppArrayConfig'); * @var array */ private $appSettings; private $_appConfig; public function _initialize() { if (!file_exists($this->config['appPath'])) { throw new ModuleConfigException( __CLASS__, "Couldn't load application config file {$this->config['appPath']}\n" . "Please provide application bootstrap file configured for testing" ); } $this->appSettings = include($this->config['appPath']); //get application settings in the entry script // get configuration from array or file if (is_array($this->appSettings['config'])) { $this->_appConfig = $this->appSettings['config']; } else { if (!file_exists($this->appSettings['config'])) { throw new ModuleConfigException( __CLASS__, "Couldn't load configuration file from Yii app file: {$this->appSettings['config']}\n" . "Please provide valid 'config' parameter" ); } $this->_appConfig = include($this->appSettings['config']); } if (!defined('YII_ENABLE_EXCEPTION_HANDLER')) { define('YII_ENABLE_EXCEPTION_HANDLER', false); } if (!defined('YII_ENABLE_ERROR_HANDLER')) { define('YII_ENABLE_ERROR_HANDLER', false); } $_SERVER['SCRIPT_NAME'] = parse_url($this->config['url'], PHP_URL_PATH); $_SERVER['SCRIPT_FILENAME'] = $this->config['appPath']; if (!function_exists('launch_codeception_yii_bridge')) { throw new ModuleConfigException( __CLASS__, "Codeception-Yii Bridge is not launched. In order to run tests you need to install " . "https://github.com/Codeception/YiiBridge Implement function 'launch_codeception_yii_bridge' to " . "load all Codeception overrides" ); } launch_codeception_yii_bridge(); Yii::$enableIncludePath = false; Yii::setApplication(null); Yii::createApplication($this->appSettings['class'], $this->_appConfig); } /* * Create the client connector. Called before each test */ public function _createClient() { $this->client = new Yii1Connector(); $this->client->setServerParameter("HTTP_HOST", parse_url($this->config['url'], PHP_URL_HOST)); $this->client->appPath = $this->config['appPath']; $this->client->url = $this->config['url']; $this->client->appSettings = [ 'class' => $this->appSettings['class'], 'config' => $this->_appConfig, ]; } public function _before(TestInterface $test) { $this->_createClient(); } public function _after(TestInterface $test) { $_SESSION = []; $_GET = []; $_POST = []; $_COOKIE = []; $_REQUEST = []; Yii::app()->session->close(); parent::_after($test); } /** * Getting domain regex from rule template and parameters * * @param string $template * @param array $parameters * @return string */ private function getDomainRegex($template, $parameters = []) { if ($host = parse_url($template, PHP_URL_HOST)) { $template = $host; } if (strpos($template, '<') !== false) { $template = str_replace(['<', '>'], '#', $template); } $template = preg_quote($template); foreach ($parameters as $name => $value) { $template = str_replace("#$name#", $value, $template); } return '/^' . $template . '$/u'; } /** * Returns a list of regex patterns for recognized domain names * * @return array */ public function getInternalDomains() { $domains = [$this->getDomainRegex(Yii::app()->request->getHostInfo())]; if (Yii::app()->urlManager->urlFormat === 'path') { $parent = Yii::app()->urlManager instanceof \CUrlManager ? '\CUrlManager' : null; $rules = ReflectionHelper::readPrivateProperty(Yii::app()->urlManager, '_rules', $parent); foreach ($rules as $rule) { if ($rule->hasHostInfo === true) { $domains[] = $this->getDomainRegex($rule->template, $rule->params); } } } return array_unique($domains); } public function _parts() { return ['init', 'initialize']; } } <?php namespace Codeception\Module; use Codeception\Configuration; use Codeception\Exception\ModuleException; use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Connector\Lumen as LumenConnector; use Codeception\Lib\Framework; use Codeception\Lib\Interfaces\ActiveRecord; use Codeception\Lib\Interfaces\PartedModule; use Codeception\Lib\Shared\LaravelCommon; use Codeception\Lib\ModuleContainer; use Codeception\TestInterface; use Codeception\Util\ReflectionHelper; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model as EloquentModel; /** * * This module allows you to run functional tests for Lumen. * Please try it and leave your feedback. * * ## Demo project * <https://github.com/janhenkgerritsen/codeception-lumen-sample> * * ## Status * * * Maintainer: **Jan-Henk Gerritsen** * * Stability: **dev** * * Contact: janhenkgerritsen@gmail.com * * ## Config * * * cleanup: `boolean`, default `true` - all database queries will be run in a transaction, * which will be rolled back at the end of each test. * * bootstrap: `string`, default `bootstrap/app.php` - relative path to app.php config file. * * root: `string`, default `` - root path of the application. * * packages: `string`, default `workbench` - root path of application packages (if any). * * url: `string`, default `http://localhost` - the application URL * * ## API * * * app - `\Laravel\Lumen\Application` * * config - `array` * * ## Parts * * * ORM - only include the database methods of this module: * * have * * haveMultiple * * haveRecord * * grabRecord * * seeRecord * * dontSeeRecord */ class Lumen extends Framework implements ActiveRecord, PartedModule { use LaravelCommon; /** * @var \Laravel\Lumen\Application */ public $app; /** * @var array */ public $config = []; /** * Constructor. * * @param ModuleContainer $container * @param array|null $config */ public function __construct(ModuleContainer $container, $config = null) { $this->config = array_merge( array( 'cleanup' => true, 'bootstrap' => 'bootstrap' . DIRECTORY_SEPARATOR . 'app.php', 'root' => '', 'packages' => 'workbench', 'url' => 'http://localhost', ), (array)$config ); $projectDir = explode($this->config['packages'], Configuration::projectDir())[0]; $projectDir .= $this->config['root']; $this->config['project_dir'] = $projectDir; $this->config['bootstrap_file'] = $projectDir . $this->config['bootstrap']; parent::__construct($container); } /** * @return array */ public function _parts() { return ['orm']; } /** * Initialize hook. */ public function _initialize() { $this->checkBootstrapFileExists(); $this->registerAutoloaders(); } /** * Before hook. * * @param \Codeception\TestInterface $test * @throws ModuleConfigException */ public function _before(TestInterface $test) { $this->client = new LumenConnector($this); if ($this->app['db'] && $this->config['cleanup']) { $this->app['db']->beginTransaction(); } } /** * After hook. * * @param \Codeception\TestInterface $test */ public function _after(TestInterface $test) { if ($this->app['db'] && $this->config['cleanup']) { $this->app['db']->rollback(); } // disconnect from DB to prevent "Too many connections" issue if ($this->app['db']) { $this->app['db']->disconnect(); } } /** * Make sure the Lumen bootstrap file exists. * * @throws ModuleConfigException */ protected function checkBootstrapFileExists() { $bootstrapFile = $this->config['bootstrap_file']; if (!file_exists($bootstrapFile)) { throw new ModuleConfigException( $this->module, "Lumen bootstrap file not found in $bootstrapFile.\n" . "Please provide a valid path using the 'bootstrap' config param. " ); } } /** * Register autoloaders. */ protected function registerAutoloaders() { require $this->config['project_dir'] . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; } /** * Provides access the Lumen application object. * * @return \Laravel\Lumen\Application */ public function getApplication() { return $this->app; } /** * @param \Laravel\Lumen\Application $app */ public function setApplication($app) { $this->app = $app; } /** * Opens web page using route name and parameters. * * ```php * <?php * $I->amOnRoute('homepage'); * ?> * ``` * * @param $routeName * @param array $params */ public function amOnRoute($routeName, $params = []) { $route = $this->getRouteByName($routeName); if (!$route) { $this->fail("Could not find route with name '$routeName'"); } $url = $this->generateUrlForRoute($route, $params); $this->amOnPage($url); } /** * Get the route for a route name. * * @param string $routeName * @return array|null */ private function getRouteByName($routeName) { foreach ($this->app->getRoutes() as $route) { if ($route['method'] != 'GET') { return; } if (isset($route['action']['as']) && $route['action']['as'] == $routeName) { return $route; } } return null; } /** * Generate the URL for a route specification. * Replaces the route parameters from left to right with the parameters * passed in the $params array. * * @param array $route * @param array $params * @return string */ private function generateUrlForRoute($route, $params) { $url = $route['uri']; while (count($params) > 0) { $param = array_shift($params); $url = preg_replace('/{.+?}/', $param, $url, 1); } return $url; } /** * Set the authenticated user for the next request. * This will not persist between multiple requests. * * @param \Illuminate\Contracts\Auth\Authenticatable * @param string|null $driver The authentication driver for Lumen <= 5.1.*, guard name for Lumen >= 5.2 * @return void */ public function amLoggedAs($user, $driver = null) { if (!$user instanceof Authenticatable) { $this->fail( 'The user passed to amLoggedAs() should be an instance of \\Illuminate\\Contracts\\Auth\\Authenticable' ); } $guard = $auth = $this->app['auth']; if (method_exists($auth, 'driver')) { $guard = $auth->driver($driver); } if (method_exists($auth, 'guard')) { $guard = $auth->guard($driver); } $guard->setUser($user); } /** * Checks that user is authenticated. */ public function seeAuthentication() { $this->assertTrue($this->app['auth']->check(), 'User is not logged in'); } /** * Check that user is not authenticated. */ public function dontSeeAuthentication() { $this->assertFalse($this->app['auth']->check(), 'User is logged in'); } /** * Return an instance of a class from the IoC Container. * * Example * ``` php * <?php * // In Lumen * App::bind('foo', function($app) * { * return new FooBar; * }); * * // Then in test * $service = $I->grabService('foo'); * * // Will return an instance of FooBar, also works for singletons. * ?> * ``` * * @param string $class * @return mixed */ public function grabService($class) { return $this->app[$class]; } /** * Inserts record into the database. * If you pass the name of a database table as the first argument, this method returns an integer ID. * You can also pass the class name of an Eloquent model, in that case this method returns an Eloquent model. * * ``` php * <?php * $user_id = $I->haveRecord('users', array('name' => 'Davert')); // returns integer * $user = $I->haveRecord('App\User', array('name' => 'Davert')); // returns Eloquent model * ?> * ``` * * @param string $table * @param array $attributes * @return integer|EloquentModel * @part orm */ public function haveRecord($table, $attributes = []) { if (class_exists($table)) { $model = new $table; if (!$model instanceof EloquentModel) { throw new \RuntimeException("Class $table is not an Eloquent model"); } $model->fill($attributes)->save(); return $model; } try { return $this->app['db']->table($table)->insertGetId($attributes); } catch (\Exception $e) { $this->fail("Could not insert record into table '$table':\n\n" . $e->getMessage()); } } /** * Checks that record exists in database. * You can pass the name of a database table or the class name of an Eloquent model as the first argument. * * ``` php * <?php * $I->seeRecord('users', array('name' => 'davert')); * $I->seeRecord('App\User', array('name' => 'davert')); * ?> * ``` * * @param string $table * @param array $attributes * @part orm */ public function seeRecord($table, $attributes = []) { if (class_exists($table)) { if (!$this->findModel($table, $attributes)) { $this->fail("Could not find $table with " . json_encode($attributes)); } } elseif (!$this->findRecord($table, $attributes)) { $this->fail("Could not find matching record in table '$table'"); } } /** * Checks that record does not exist in database. * You can pass the name of a database table or the class name of an Eloquent model as the first argument. * * ``` php * <?php * $I->dontSeeRecord('users', array('name' => 'davert')); * $I->dontSeeRecord('App\User', array('name' => 'davert')); * ?> * ``` * * @param string $table * @param array $attributes * @part orm */ public function dontSeeRecord($table, $attributes = []) { if (class_exists($table)) { if ($this->findModel($table, $attributes)) { $this->fail("Unexpectedly found matching $table with " . json_encode($attributes)); } } elseif ($this->findRecord($table, $attributes)) { $this->fail("Unexpectedly found matching record in table '$table'"); } } /** * Retrieves record from database * If you pass the name of a database table as the first argument, this method returns an array. * You can also pass the class name of an Eloquent model, in that case this method returns an Eloquent model. * * ``` php * <?php * $record = $I->grabRecord('users', array('name' => 'davert')); // returns array * $record = $I->grabRecord('App\User', array('name' => 'davert')); // returns Eloquent model * ?> * ``` * * @param string $table * @param array $attributes * @return array|EloquentModel * @part orm */ public function grabRecord($table, $attributes = []) { if (class_exists($table)) { if (!$model = $this->findModel($table, $attributes)) { $this->fail("Could not find $table with " . json_encode($attributes)); } return $model; } if (!$record = $this->findRecord($table, $attributes)) { $this->fail("Could not find matching record in table '$table'"); } return $record; } /** * @param string $modelClass * @param array $attributes * * @return EloquentModel */ protected function findModel($modelClass, $attributes = []) { $model = new $modelClass; if (!$model instanceof EloquentModel) { throw new \RuntimeException("Class $modelClass is not an Eloquent model"); } $query = $model->newQuery(); foreach ($attributes as $key => $value) { $query->where($key, $value); } return $query->first(); } /** * @param string $table * @param array $attributes * @return array */ protected function findRecord($table, $attributes = []) { $query = $this->app['db']->table($table); foreach ($attributes as $key => $value) { $query->where($key, $value); } return (array)$query->first(); } /** * Use Lumen's model factory to create a model. * Can only be used with Lumen 5.1 and later. * * ``` php * <?php * $I->have('App\User'); * $I->have('App\User', ['name' => 'John Doe']); * $I->have('App\User', [], 'admin'); * ?> * ``` * * @see https://lumen.laravel.com/docs/master/testing#model-factories * @param string $model * @param array $attributes * @param string $name * @return mixed * @part orm */ public function have($model, $attributes = [], $name = 'default') { try { return $this->modelFactory($model, $name)->create($attributes); } catch (\Exception $e) { $this->fail("Could not create model: \n\n" . get_class($e) . "\n\n" . $e->getMessage()); } } /** * Use Laravel's model factory to create multiple models. * Can only be used with Lumen 5.1 and later. * * ``` php * <?php * $I->haveMultiple('App\User', 10); * $I->haveMultiple('App\User', 10, ['name' => 'John Doe']); * $I->haveMultiple('App\User', 10, [], 'admin'); * ?> * ``` * * @see https://lumen.laravel.com/docs/master/testing#model-factories * @param string $model * @param int $times * @param array $attributes * @param string $name * @return mixed * @part orm */ public function haveMultiple($model, $times, $attributes = [], $name = 'default') { try { return $this->modelFactory($model, $name, $times)->create($attributes); } catch (\Exception $e) { $this->fail("Could not create model: \n\n" . get_class($e) . "\n\n" . $e->getMessage()); } } /** * @param string $model * @param string $name * @param int $times * @return \Illuminate\Database\Eloquent\FactoryBuilder * @throws ModuleException */ protected function modelFactory($model, $name, $times = 1) { if (!function_exists('factory')) { throw new ModuleException($this, 'The factory() method does not exist. ' . 'This functionality relies on Lumen model factories, which were introduced in Lumen 5.1.'); } return factory($model, $name, $times); } /** * Returns a list of recognized domain names. * This elements of this list are regular expressions. * * @return array */ protected function getInternalDomains() { $server = ReflectionHelper::readPrivateProperty($this->client, 'server'); return ['/^' . str_replace('.', '\.', $server['HTTP_HOST']) . '$/']; } } <?php namespace Codeception\Module; use Codeception\Configuration; use Codeception\Lib\Framework; use Codeception\TestInterface; use Codeception\Exception\ModuleException; use Codeception\Util\ReflectionHelper; use Codeception\Lib\Connector\ZF1 as ZF1Connector; use Zend_Controller_Router_Route_Hostname; use Zend_Controller_Router_Route_Chain; /** * This module allows you to run tests inside Zend Framework. * It acts just like ControllerTestCase, but with usage of Codeception syntax. * * It assumes, you have standard structure with __APPLICATION_PATH__ set to './application' * and LIBRARY_PATH set to './library'. If it's not then set the appropriate path in the Config. * * [Tutorial](http://codeception.com/01-27-2012/bdd-with-zend-framework.html) * * ## Status * * * Maintainer: **davert** * * Stability: **stable** * * Contact: codecept@davert.mail.ua * * ## Config * * * env - environment used for testing ('testing' by default). * * config - relative path to your application config ('application/configs/application.ini' by default). * * app_path - relative path to your application folder ('application' by default). * * lib_path - relative path to your library folder ('library' by default). * * ## API * * * client - BrowserKit client * * db - current instance of Zend_Db_Adapter * * bootstrap - current bootstrap file. * * ## Cleaning up * * Unfortunately Zend_Db doesn't support nested transactions, * thus, for cleaning your database you should either use standard Db module or * [implement nested transactions yourself](http://blog.ekini.net/2010/03/05/zend-framework-how-to-use-nested-transactions-with-zend_db-and-mysql/). * * If your database supports nested transactions (MySQL doesn't) * or you implemented them you can put all your code inside a transaction. * Use a generated helper TestHelper. Use this code inside of it. * * ``` php * <?php * namespace Codeception\Module; * class TestHelper extends \Codeception\Module { * function _before($test) { * $this->getModule('ZF1')->db->beginTransaction(); * } * * function _after($test) { * $this->getModule('ZF1')->db->rollback(); * } * } * ?> * ``` * * This will make your functional tests run super-fast. * */ class ZF1 extends Framework { protected $config = [ 'env' => 'testing', 'config' => 'application/configs/application.ini', 'app_path' => 'application', 'lib_path' => 'library' ]; /** * @var \Zend_Application */ public $bootstrap; /** * @var \Zend_Db_Adapter_Abstract */ public $db; /** * @var \Codeception\Lib\Connector\ZF1 */ public $client; protected $queries = 0; protected $time = 0; /** * @var array Used to collect domains while recursively traversing route tree */ private $domainCollector = []; public function _initialize() { defined('APPLICATION_ENV') || define('APPLICATION_ENV', $this->config['env']); defined('APPLICATION_PATH') || define( 'APPLICATION_PATH', Configuration::projectDir() . $this->config['app_path'] ); defined('LIBRARY_PATH') || define('LIBRARY_PATH', Configuration::projectDir() . $this->config['lib_path']); // Ensure library/ is on include_path set_include_path( implode( PATH_SEPARATOR, [ LIBRARY_PATH, get_include_path(), ] ) ); require_once 'Zend/Loader/Autoloader.php'; \Zend_Loader_Autoloader::getInstance(); } public function _before(TestInterface $test) { $this->client = new ZF1Connector(); \Zend_Session::$_unitTestEnabled = true; try { $this->bootstrap = new \Zend_Application( $this->config['env'], Configuration::projectDir() . $this->config['config'] ); } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } $this->bootstrap->bootstrap(); $this->client->setBootstrap($this->bootstrap); $db = $this->bootstrap->getBootstrap()->getResource('db'); if ($db instanceof \Zend_Db_Adapter_Abstract) { $this->db = $db; $this->db->getProfiler()->setEnabled(true); $this->db->getProfiler()->clear(); } } public function _after(TestInterface $test) { $_SESSION = []; $_GET = []; $_POST = []; $_COOKIE = []; if ($this->bootstrap) { $fc = $this->bootstrap->getBootstrap()->getResource('frontcontroller'); if ($fc) { $fc->resetInstance(); } } \Zend_Layout::resetMvcInstance(); \Zend_Controller_Action_HelperBroker::resetHelpers(); \Zend_Session::$_unitTestEnabled = true; \Zend_Registry::_unsetInstance(); $this->queries = 0; $this->time = 0; parent::_after($test); } /** * @param $url */ protected function debugResponse($url) { parent::debugResponse($url); $this->debugSection('Session', json_encode($_COOKIE)); if ($this->db) { $profiler = $this->db->getProfiler(); $queries = $profiler->getTotalNumQueries() - $this->queries; $time = $profiler->getTotalElapsedSecs() - $this->time; $this->debugSection('Db', $queries . ' queries'); $this->debugSection('Time', round($time, 2) . ' secs taken'); $this->time = $profiler->getTotalElapsedSecs(); $this->queries = $profiler->getTotalNumQueries(); } } /** * Opens web page using route name and parameters. * * ``` php * <?php * $I->amOnRoute('posts.create'); * $I->amOnRoute('posts.show', array('id' => 34)); * ?> * ``` * * @param $routeName * @param array $params */ public function amOnRoute($routeName, array $params = []) { $router = $this->bootstrap->getBootstrap()->getResource('frontcontroller')->getRouter(); $url = $router->assemble($params, $routeName); $this->amOnPage($url); } /** * Checks that current url matches route. * * ``` php * <?php * $I->seeCurrentRouteIs('posts.index'); * $I->seeCurrentRouteIs('posts.show', ['id' => 8])); * ?> * ``` * * @param $routeName * @param array $params */ public function seeCurrentRouteIs($routeName, array $params = []) { $router = $this->bootstrap->getBootstrap()->getResource('frontcontroller')->getRouter(); $url = $router->assemble($params, $routeName); $this->seeCurrentUrlEquals($url); } protected function getInternalDomains() { $router = $this->bootstrap->getBootstrap()->getResource('frontcontroller')->getRouter(); $this->domainCollector = []; $this->addInternalDomainsFromRoutes($router->getRoutes()); return array_unique($this->domainCollector); } private function addInternalDomainsFromRoutes($routes) { foreach ($routes as $name => $route) { try { $route->assemble([]); } catch (\Exception $e) { } if ($route instanceof Zend_Controller_Router_Route_Hostname) { $this->addInternalDomain($route); } elseif ($route instanceof Zend_Controller_Router_Route_Chain) { $chainRoutes = ReflectionHelper::readPrivateProperty($route, '_routes'); $this->addInternalDomainsFromRoutes($chainRoutes); } } } private function addInternalDomain(Zend_Controller_Router_Route_Hostname $route) { $parts = ReflectionHelper::readPrivateProperty($route, '_parts'); foreach ($parts as &$part) { if ($part === null) { $part = '[^.]+'; } } $regex = implode('\.', $parts); $this->domainCollector []= '/^' . $regex . '$/iu'; } } <?php namespace Codeception\Module; use Codeception\Util\FileSystem as Util; use Symfony\Component\Finder\Finder; use Codeception\Module as CodeceptionModule; use Codeception\TestInterface; use Codeception\Configuration; /** * Module for testing local filesystem. * Fork it to extend the module for FTP, Amazon S3, others. * * ## Status * * * Maintainer: **davert** * * Stability: **stable** * * Contact: codecept@davert.mail.ua * * Module was developed to test Codeception itself. */ class Filesystem extends CodeceptionModule { protected $file = null; protected $filepath = null; protected $path = ''; public function _before(TestInterface $test) { $this->path = Configuration::projectDir(); } /** * Enters a directory In local filesystem. * Project root directory is used by default * * @param string $path */ public function amInPath($path) { chdir($this->path = $this->absolutizePath($path) . DIRECTORY_SEPARATOR); $this->debug('Moved to ' . getcwd()); } /** * @param string $path * @return string */ protected function absolutizePath($path) { // *nix way if (strpos($path, '/') === 0) { return $path; } // windows if (strpos($path, ':\\') === 1) { return $path; } return $this->path . $path; } /** * Opens a file and stores it's content. * * Usage: * * ``` php * <?php * $I->openFile('composer.json'); * $I->seeInThisFile('codeception/codeception'); * ?> * ``` * * @param string $filename */ public function openFile($filename) { $this->file = file_get_contents($this->absolutizePath($filename)); $this->filepath = $filename; } /** * Deletes a file * * ``` php * <?php * $I->deleteFile('composer.lock'); * ?> * ``` * * @param string $filename */ public function deleteFile($filename) { if (!file_exists($this->absolutizePath($filename))) { \PHPUnit_Framework_Assert::fail('file not found'); } unlink($this->absolutizePath($filename)); } /** * Deletes directory with all subdirectories * * ``` php * <?php * $I->deleteDir('vendor'); * ?> * ``` * * @param string $dirname */ public function deleteDir($dirname) { $dir = $this->absolutizePath($dirname); Util::deleteDir($dir); } /** * Copies directory with all contents * * ``` php * <?php * $I->copyDir('vendor','old_vendor'); * ?> * ``` * * @param string $src * @param string $dst */ public function copyDir($src, $dst) { Util::copyDir($src, $dst); } /** * Checks If opened file has `text` in it. * * Usage: * * ``` php * <?php * $I->openFile('composer.json'); * $I->seeInThisFile('codeception/codeception'); * ?> * ``` * * @param string $text */ public function seeInThisFile($text) { $this->assertContains($text, $this->file, "No text '$text' in currently opened file"); } /** * Checks If opened file has the `number` of new lines. * * Usage: * * ``` php * <?php * $I->openFile('composer.json'); * $I->seeNumberNewLines(5); * ?> * ``` * * @param int $number New lines */ public function seeNumberNewLines($number) { $lines = preg_split('/\n|\r/', $this->file); $this->assertTrue( (int) $number === count($lines), "The number of new lines does not match with $number" ); } /** * Checks that contents of currently opened file matches $regex * * @param string $regex */ public function seeThisFileMatches($regex) { $this->assertRegExp($regex, $this->file, "Contents of currently opened file does not match '$regex'"); } /** * Checks the strict matching of file contents. * Unlike `seeInThisFile` will fail if file has something more than expected lines. * Better to use with HEREDOC strings. * Matching is done after removing "\r" chars from file content. * * ``` php * <?php * $I->openFile('process.pid'); * $I->seeFileContentsEqual('3192'); * ?> * ``` * * @param string $text */ public function seeFileContentsEqual($text) { $file = str_replace("\r", '', $this->file); \PHPUnit_Framework_Assert::assertEquals($text, $file); } /** * Checks If opened file doesn't contain `text` in it * * ``` php * <?php * $I->openFile('composer.json'); * $I->dontSeeInThisFile('codeception/codeception'); * ?> * ``` * * @param string $text */ public function dontSeeInThisFile($text) { $this->assertNotContains($text, $this->file, "Found text '$text' in currently opened file"); } /** * Deletes a file */ public function deleteThisFile() { $this->deleteFile($this->filepath); } /** * Checks if file exists in path. * Opens a file when it's exists * * ``` php * <?php * $I->seeFileFound('UserModel.php','app/models'); * ?> * ``` * * @param string $filename * @param string $path */ public function seeFileFound($filename, $path = '') { if ($path === '' && file_exists($filename)) { $this->openFile($filename); \PHPUnit_Framework_Assert::assertFileExists($filename); return; } $found = $this->findFileInPath($filename, $path); if ($found === false) { $this->fail("File \"$filename\" not found at \"$path\""); } $this->openFile($found); \PHPUnit_Framework_Assert::assertFileExists($found); } /** * Checks if file does not exist in path * * @param string $filename * @param string $path */ public function dontSeeFileFound($filename, $path = '') { if ($path === '') { \PHPUnit_Framework_Assert::assertFileNotExists($filename); return; } $found = $this->findFileInPath($filename, $path); if ($found === false) { //this line keeps a count of assertions correct \PHPUnit_Framework_Assert::assertTrue(true); return; } \PHPUnit_Framework_Assert::assertFileNotExists($found); } /** * Finds the first matching file * * @param string $filename * @param string $path * @throws \PHPUnit_Framework_AssertionFailedError When path does not exist * @return string|false Path to the first matching file */ private function findFileInPath($filename, $path) { $path = $this->absolutizePath($path); if (!file_exists($path)) { $this->fail("Directory does not exist: $path"); } $files = Finder::create()->files()->name($filename)->in($path); if ($files->count() === 0) { return false; } foreach ($files as $file) { return $file->getRealPath(); } } /** * Erases directory contents * * ``` php * <?php * $I->cleanDir('logs'); * ?> * ``` * * @param string $dirname */ public function cleanDir($dirname) { $path = $this->absolutizePath($dirname); Util::doEmptyDir($path); } /** * Saves contents to file * * @param string $filename * @param string $contents */ public function writeToFile($filename, $contents) { file_put_contents($filename, $contents); } } <?php namespace Codeception\Module; use Codeception\Lib\Framework; use Codeception\TestInterface; use Codeception\Configuration; use Codeception\Lib\Connector\ZendExpressive as ZendExpressiveConnector; /** * This module allows you to run tests inside Zend Expressive. * * Uses `config/container.php` file by default. * * ## Status * * * Maintainer: **Naktibalda** * * Stability: **alpha** * * ## Config * * * container: relative path to file which returns Container (default: `config/container.php`) * * ## API * * * application - instance of `\Zend\Expressive\Application` * * container - instance of `\Interop\Container\ContainerInterface` * * client - BrowserKit client * */ class ZendExpressive extends Framework { protected $config = [ 'container' => 'config/container.php', ]; /** * @var \Codeception\Lib\Connector\ZendExpressive */ public $client; /** * @var \Interop\Container\ContainerInterface */ public $container; /** * @var \Zend\Expressive\Application */ public $application; protected $responseCollector; public function _initialize() { $cwd = getcwd(); $projectDir = Configuration::projectDir(); chdir($projectDir); $this->container = require $projectDir . $this->config['container']; $app = $this->container->get('Zend\Expressive\Application'); $pipelineFile = $projectDir . 'config/pipeline.php'; if (file_exists($pipelineFile)) { require $pipelineFile; } $routesFile = $projectDir . 'config/routes.php'; if (file_exists($routesFile)) { require $routesFile; } chdir($cwd); $this->application = $app; $this->initResponseCollector(); } public function _before(TestInterface $test) { $this->client = new ZendExpressiveConnector(); $this->client->setApplication($this->application); $this->client->setResponseCollector($this->responseCollector); } public function _after(TestInterface $test) { //Close the session, if any are open if (session_status() == PHP_SESSION_ACTIVE) { session_write_close(); } parent::_after($test); } private function initResponseCollector() { /** * @var Zend\Expressive\Emitter\EmitterStack */ $emitterStack = $this->application->getEmitter(); while (!$emitterStack->isEmpty()) { $emitterStack->pop(); } $this->responseCollector = new ZendExpressiveConnector\ResponseCollector; $emitterStack->unshift($this->responseCollector); } } <?php namespace Codeception\Module; use Codeception\Module; use Codeception\TestInterface; use Codeception\Exception\ModuleException; /** * This module interacts with the [Alternative PHP Cache (APC)](http://php.net/manual/en/intro.apcu.php) * using either _APCu_ or _APC_ extension. * * Performs a cleanup by flushing all values after each test run. * * ## Status * * * Maintainer: **Serghei Iakovlev** * * Stability: **stable** * * Contact: serghei@phalconphp.com * * ### Example (`unit.suite.yml`) * * ```yaml * modules: * - Apc * ``` * * Be sure you don't use the production server to connect. * */ class Apc extends Module { /** * Code to run before each test. * * @param TestInterface $test * @throws ModuleException */ public function _before(TestInterface $test) { if (!extension_loaded('apc') && !extension_loaded('apcu')) { throw new ModuleException( __CLASS__, 'The APC(u) extension not loaded.' ); } if (!ini_get('apc.enabled') || (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli'))) { throw new ModuleException( __CLASS__, 'The "apc.enable_cli" parameter must be set to "On".' ); } } /** * Code to run after each test. * * @param TestInterface $test */ public function _after(TestInterface $test) { $this->clear(); } /** * Grabs value from APC(u) by key. * * Example: * * ``` php * <?php * $users_count = $I->grabValueFromApc('users_count'); * ?> * ``` * * @param string|string[] $key * @return mixed */ public function grabValueFromApc($key) { $value = $this->fetch($key); $this->debugSection('Value', $value); return $value; } /** * Checks item in APC(u) exists and the same as expected. * * Examples: * * ``` php * <?php * // With only one argument, only checks the key exists * $I->seeInApc('users_count'); * * // Checks a 'users_count' exists and has the value 200 * $I->seeInApc('users_count', 200); * ?> * ``` * * @param string|string[] $key * @param mixed $value */ public function seeInApc($key, $value = null) { if (null === $value) { $this->assertTrue($this->exists($key), "Cannot find key '$key' in APC(u)."); return; } $actual = $this->grabValueFromApc($key); $this->assertEquals($value, $actual, "Cannot find key '$key' in APC(u) with the provided value."); } /** * Checks item in APC(u) doesn't exist or is the same as expected. * * Examples: * * ``` php * <?php * // With only one argument, only checks the key does not exist * $I->dontSeeInApc('users_count'); * * // Checks a 'users_count' exists does not exist or its value is not the one provided * $I->dontSeeInApc('users_count', 200); * ?> * ``` * * @param string|string[] $key * @param mixed $value */ public function dontSeeInApc($key, $value = null) { if (null === $value) { $this->assertFalse($this->exists($key), "The key '$key' exists in APC(u)."); return; } $actual = $this->grabValueFromApc($key); if (false !== $actual) { $this->assertEquals($value, $actual, "The key '$key' exists in APC(u) with the provided value."); } } /** * Stores an item `$value` with `$key` on the APC(u). * * Examples: * * ```php * <?php * // Array * $I->haveInApc('users', ['name' => 'miles', 'email' => 'miles@davis.com']); * * // Object * $I->haveInApc('user', UserRepository::findFirst()); * * // Key as array of 'key => value' * $entries = []; * $entries['key1'] = 'value1'; * $entries['key2'] = 'value2'; * $entries['key3'] = ['value3a','value3b']; * $entries['key4'] = 4; * $I->haveInApc($entries, null); * ?> * ``` * * @param string|array $key * @param mixed $value * @param int $expiration * @return mixed */ public function haveInApc($key, $value, $expiration = null) { $this->store($key, $value, $expiration); return $key; } /** * Clears the APC(u) cache */ public function flushApc() { // Returns TRUE always $this->clear(); } /** * Clears the APC(u) cache. * * @return bool */ protected function clear() { if (function_exists('apcu_clear_cache')) { return apcu_clear_cache(); } return apc_clear_cache('user'); } /** * Checks if entry exists * * @param string|string[] $key * * @return bool|\string[] */ protected function exists($key) { if (function_exists('apcu_exists')) { return apcu_exists($key); } return apc_exists($key); } /** * Fetch a stored variable from the cache * * @param string|string[] $key * * @return mixed */ protected function fetch($key) { $success = false; if (function_exists('apcu_fetch')) { $data = apcu_fetch($key, $success); } else { $data = apc_fetch($key, $success); } $this->debugSection('Fetching a stored variable', $success ? 'OK' : 'FAILED'); return $data; } /** * Cache a variable in the data store. * * @param string|array $key * @param mixed $var * @param int $ttl * * @return array|bool */ protected function store($key, $var, $ttl = 0) { if (function_exists('apcu_store')) { return apcu_store($key, $var, $ttl); } return apc_store($key, $var, $ttl); } } <?php namespace Codeception\Module; use Codeception\Configuration; use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Framework; use Codeception\Lib\Interfaces\DoctrineProvider; use Codeception\TestInterface; use Symfony\Component\HttpKernel\Client; /** * Module for testing Silex applications like you would regularly do with Silex\WebTestCase. * This module uses Symfony2 Crawler and HttpKernel to emulate requests and get response. * * This module may be considered experimental and require feedback and pull requests from you ) * * ## Status * * * Maintainer: **davert** * * Stability: **alpha** * * Contact: davert.codecept@resend.cc * * ## Config * * * app: **required** - path to Silex bootstrap file. * * em_service: 'db.orm.em' - use the stated EntityManager to pair with Doctrine Module. * * ### Bootstrap File * * Bootstrap is the same as [WebTestCase.createApplication](http://silex.sensiolabs.org/doc/testing.html#webtestcase). * * ``` php * <? * $app = require __DIR__.'/path/to/app.php'; * $app['debug'] = true; * unset($app['exception_handler']); * * return $app; // optionally * ?> * ``` * * ### Example (`functional.suite.yml`) * * modules: * enabled: * - Silex: * app: 'app/bootstrap.php' * * Class Silex * @package Codeception\Module */ class Silex extends Framework implements DoctrineProvider { protected $app; protected $requiredFields = ['app']; protected $config = [ 'em_service' => 'db.orm.em' ]; public function _initialize() { if (!file_exists(Configuration::projectDir() . $this->config['app'])) { throw new ModuleConfigException(__CLASS__, "Bootstrap file {$this->config['app']} not found"); } $this->loadApp(); } public function _before(TestInterface $test) { $this->loadApp(); $this->client = new Client($this->app); } public function _getEntityManager() { if (!isset($this->app[$this->config['em_service']])) { return null; } return $this->app[$this->config['em_service']]; } protected function loadApp() { $this->app = require Configuration::projectDir() . $this->config['app']; // if $app is not returned but exists if (isset($app)) { $this->app = $app; } if (!isset($this->app)) { throw new ModuleConfigException(__CLASS__, "\$app instance was not received from bootstrap file"); } // make doctrine persistent $db_orm_em = $this->_getEntityManager(); if ($db_orm_em) { $this->app->extend($this->config['em_service'], function () use ($db_orm_em) { return $db_orm_em; }); } // some silex apps (like bolt) may rely on global $app variable $GLOBALS['app'] = $this->app; } /** * Return an instance of a class from the Container. * * Example * ``` php * <?php * $I->grabService('session'); * ?> * ``` * * @param string $service * @return mixed */ public function grabService($service) { return $this->app[$service]; } /** * Returns a list of recognized domain names * * @return array */ public function getInternalDomains() { $internalDomains = []; foreach ($this->app['routes'] as $route) { if ($domain = $route->getHost()) { $internalDomains[] = '/^' . preg_quote($domain, '/') . '$/'; } } return $internalDomains; } } <?php namespace Codeception\Module; use Codeception\Module as CodeceptionModule; use Codeception\Configuration; use Codeception\Exception\ModuleException; use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Interfaces\Db as DbInterface; use Codeception\Lib\Driver\Db as Driver; use Codeception\Lib\DbPopulator; use Codeception\TestInterface; /** * Access a database. * * The most important function of this module is to clean a database before each test. * This module also provides actions to perform checks in a database, e.g. [seeInDatabase()](http://codeception.com/docs/modules/Db#seeInDatabase) * * In order to have your database populated with data you need a raw SQL dump. * Simply put the dump in the `tests/_data` directory (by default) and specify the path in the config. * The next time after the database is cleared, all your data will be restored from the dump. * Don't forget to include `CREATE TABLE` statements in the dump. * * Supported and tested databases are: * * * MySQL * * SQLite (i.e. just one file) * * PostgreSQL * * Also available: * * * MS SQL * * Oracle * * Connection is done by database Drivers, which are stored in the `Codeception\Lib\Driver` namespace. * [Check out the drivers](https://github.com/Codeception/Codeception/tree/2.3/src/Codeception/Lib/Driver) * if you run into problems loading dumps and cleaning databases. * * ## Config * * * dsn *required* - PDO DSN * * user *required* - username to access database * * password *required* - password * * dump - path to database dump * * populate: false - whether the the dump should be loaded before the test suite is started * * cleanup: false - whether the dump should be reloaded before each test * * reconnect: false - whether the module should reconnect to the database before each test * * ## Example * * modules: * enabled: * - Db: * dsn: 'mysql:host=localhost;dbname=testdb' * user: 'root' * password: '' * dump: 'tests/_data/dump.sql' * populate: true * cleanup: true * reconnect: true * * ## SQL data dump * * There are two ways of loading the dump into your database: * * ### Populator * * The recommended approach is to configure a `populator`, an external command to load a dump. Command parameters like host, username, password, database * can be obtained from the config and inserted into placeholders: * * For MySQL: * * ```yaml * modules: * enabled: * - Db: * dsn: 'mysql:host=localhost;dbname=testdb' * user: 'root' * password: '' * dump: 'tests/_data/dump.sql' * populate: true # run populator before all tests * cleanup: true # run populator before each test * populator: 'mysql -u $user -h $host $dbname < $dump' * ``` * * For PostgreSQL (using pg_restore) * * ``` * modules: * enabled: * - Db: * dsn: 'pgsql:host=localhost;dbname=testdb' * user: 'root' * password: '' * dump: 'tests/_data/db_backup.dump' * populate: true # run populator before all tests * cleanup: true # run populator before each test * populator: 'pg_restore -u $user -h $host -D $dbname < $dump' * ``` * * Variable names are being taken from config and DSN which has a `keyword=value` format, so you should expect to have a variable named as the * keyword with the full value inside it. * * PDO dsn elements for the supported drivers: * * MySQL: [PDO_MYSQL DSN](https://secure.php.net/manual/en/ref.pdo-mysql.connection.php) * * SQLite: [PDO_SQLITE DSN](https://secure.php.net/manual/en/ref.pdo-sqlite.connection.php) * * PostgreSQL: [PDO_PGSQL DSN](https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php) * * MSSQL: [PDO_SQLSRV DSN](https://secure.php.net/manual/en/ref.pdo-sqlsrv.connection.php) * * Oracle: [PDO_OCI DSN](https://secure.php.net/manual/en/ref.pdo-oci.connection.php) * * ### Dump * * Db module by itself can load SQL dump without external tools by using current database connection. * This approach is system-independent, however, it is slower than using a populator and may have parsing issues (see below). * * Provide a path to SQL file in `dump` config option: * * ```yaml * modules: * enabled: * - Db: * dsn: 'mysql:host=localhost;dbname=testdb' * user: 'root' * password: '' * populate: true # load dump before all tests * cleanup: true # load dump for each test * dump: 'tests/_data/dump.sql' * ``` * * To parse SQL Db file, it should follow this specification: * * Comments are permitted. * * The `dump.sql` may contain multiline statements. * * The delimiter, a semi-colon in this case, must be on the same line as the last statement: * * ```sql * -- Add a few contacts to the table. * REPLACE INTO `Contacts` (`created`, `modified`, `status`, `contact`, `first`, `last`) VALUES * (NOW(), NOW(), 1, 'Bob Ross', 'Bob', 'Ross'), * (NOW(), NOW(), 1, 'Fred Flintstone', 'Fred', 'Flintstone'); * * -- Remove existing orders for testing. * DELETE FROM `Order`; * ``` * ## Query generation * * `seeInDatabase`, `dontSeeInDatabase`, `seeNumRecords`, `grabFromDatabase` and `grabNumRecords` methods * accept arrays as criteria. WHERE condition is generated using item key as a field name and * item value as a field value. * * Example: * ```php * <?php * $I->seeInDatabase('users', array('name' => 'Davert', 'email' => 'davert@mail.com')); * * ``` * Will generate: * * ```sql * SELECT COUNT(*) FROM `users` WHERE `name` = 'Davert' AND `email` = 'davert@mail.com' * ``` * Since version 2.1.9 it's possible to use LIKE in a condition, as shown here: * * ```php * <?php * $I->seeInDatabase('users', array('name' => 'Davert', 'email like' => 'davert%')); * * ``` * Will generate: * * ```sql * SELECT COUNT(*) FROM `users` WHERE `name` = 'Davert' AND `email` LIKE 'davert%' * ``` * ## Public Properties * * dbh - contains the PDO connection * * driver - contains the Connection Driver * */ class Db extends CodeceptionModule implements DbInterface { /** * @api * @var */ public $dbh; /** * @var array */ protected $sql = []; /** * @var array */ protected $config = [ 'populate' => false, 'cleanup' => false, 'reconnect' => false, 'dump' => null, 'populator' => null, ]; /** * @var bool */ protected $populated = false; /** * @var \Codeception\Lib\Driver\Db */ public $driver; /** * @var array */ protected $insertedRows = []; /** * @var array */ protected $requiredFields = ['dsn', 'user', 'password']; public function _initialize() { $this->connect(); } public function _beforeSuite($settings = []) { if (!$this->config['populator'] && $this->config['dump'] && ($this->config['cleanup'] || ($this->config['populate'])) ) { $this->readSql(); } $this->connect(); // starting with loading dump if ($this->config['populate']) { if ($this->config['cleanup']) { $this->_cleanup(); } $this->_loadDump(); } if ($this->config['reconnect']) { $this->disconnect(); } } private function readSql() { if (!file_exists(Configuration::projectDir() . $this->config['dump'])) { throw new ModuleConfigException( __CLASS__, "\nFile with dump doesn't exist.\n" . "Please, check path for sql file: " . $this->config['dump'] ); } $sql = file_get_contents(Configuration::projectDir() . $this->config['dump']); // remove C-style comments (except MySQL directives) $sql = preg_replace('%/\*(?!!\d+).*?\*/%s', '', $sql); if (!empty($sql)) { // split SQL dump into lines $this->sql = preg_split('/\r\n|\n|\r/', $sql, -1, PREG_SPLIT_NO_EMPTY); } } private function connect() { try { $this->driver = Driver::create($this->config['dsn'], $this->config['user'], $this->config['password']); } catch (\PDOException $e) { $message = $e->getMessage(); if ($message === 'could not find driver') { list ($missingDriver, ) = explode(':', $this->config['dsn'], 2); $message = "could not find $missingDriver driver"; } throw new ModuleException(__CLASS__, $message . ' while creating PDO connection'); } $this->debugSection('Db', 'Connected to ' . $this->driver->getDb()); $this->dbh = $this->driver->getDbh(); } private function disconnect() { $this->debugSection('Db', 'Disconnected'); $this->dbh = null; $this->driver = null; } public function _before(TestInterface $test) { if ($this->config['reconnect']) { $this->connect(); } if ($this->config['cleanup'] && !$this->populated) { $this->_cleanup(); $this->_loadDump(); } parent::_before($test); } public function _after(TestInterface $test) { $this->removeInserted(); if ($this->config['reconnect']) { $this->disconnect(); } parent::_after($test); } protected function removeInserted() { foreach (array_reverse($this->insertedRows) as $row) { try { $this->driver->deleteQueryByCriteria($row['table'], $row['primary']); } catch (\Exception $e) { $this->debug("couldn't delete record " . json_encode($row['primary']) ." from {$row['table']}"); } } $this->insertedRows = []; $this->populated = false; } public function _cleanup() { $dbh = $this->driver->getDbh(); if (!$dbh) { throw new ModuleConfigException( __CLASS__, 'No connection to database. Remove this module from config if you don\'t need database repopulation' ); } try { // don't clear database for empty dump if (!count($this->sql)) { return; } $this->driver->cleanup(); $this->populated = false; } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } } public function isPopulated() { return $this->populated; } public function _loadDump() { if ($this->config['populator']) { $this->loadDumpUsingPopulator(); return; } $this->loadDumpUsingDriver(); } protected function loadDumpUsingPopulator() { $populator = new DbPopulator($this->config); $this->populated = $populator->run(); } protected function loadDumpUsingDriver() { if (!$this->sql) { $this->debugSection('Db', 'No SQL loaded, loading dump skipped'); return; } $this->driver->load($this->sql); $this->populated = true; } /** * Inserts an SQL record into a database. This record will be erased after the test. * * ```php * <?php * $I->haveInDatabase('users', array('name' => 'miles', 'email' => 'miles@davis.com')); * ?> * ``` * * @param string $table * @param array $data * * @return integer $id */ public function haveInDatabase($table, array $data) { $query = $this->driver->insert($table, $data); $parameters = array_values($data); $this->debugSection('Query', $query); $this->debugSection('Parameters', $parameters); $this->driver->executeQuery($query, $parameters); try { $lastInsertId = (int)$this->driver->lastInsertId($table); } catch (\PDOException $e) { // ignore errors due to uncommon DB structure, // such as tables without _id_seq in PGSQL $lastInsertId = 0; } $this->addInsertedRow($table, $data, $lastInsertId); return $lastInsertId; } private function addInsertedRow($table, array $row, $id) { $primaryKey = $this->driver->getPrimaryKey($table); $primary = []; if ($primaryKey) { if ($id && count($primaryKey) === 1) { $primary [$primaryKey[0]] = $id; } else { foreach ($primaryKey as $column) { if (isset($row[$column])) { $primary[$column] = $row[$column]; } else { throw new \InvalidArgumentException( 'Primary key field ' . $column . ' is not set for table ' . $table ); } } } } else { $primary = $row; } $this->insertedRows[] = [ 'table' => $table, 'primary' => $primary, ]; } public function seeInDatabase($table, $criteria = []) { $res = $this->countInDatabase($table, $criteria); $this->assertGreaterThan( 0, $res, 'No matching records found for criteria ' . json_encode($criteria) . ' in table ' . $table ); } /** * Asserts that the given number of records were found in the database. * * ```php * <?php * $I->seeNumRecords(1, 'users', ['name' => 'davert']) * ?> * ``` * * @param int $expectedNumber Expected number * @param string $table Table name * @param array $criteria Search criteria [Optional] */ public function seeNumRecords($expectedNumber, $table, array $criteria = []) { $actualNumber = $this->countInDatabase($table, $criteria); $this->assertEquals( $expectedNumber, $actualNumber, sprintf( 'The number of found rows (%d) does not match expected number %d for criteria %s in table %s', $actualNumber, $expectedNumber, json_encode($criteria), $table ) ); } public function dontSeeInDatabase($table, $criteria = []) { $count = $this->countInDatabase($table, $criteria); $this->assertLessThan( 1, $count, 'Unexpectedly found matching records for criteria ' . json_encode($criteria) . ' in table ' . $table ); } /** * Count rows in a database * * @param string $table Table name * @param array $criteria Search criteria [Optional] * * @return int */ protected function countInDatabase($table, array $criteria = []) { return (int) $this->proceedSeeInDatabase($table, 'count(*)', $criteria); } protected function proceedSeeInDatabase($table, $column, $criteria) { $query = $this->driver->select($column, $table, $criteria); $parameters = array_values($criteria); $this->debugSection('Query', $query); if (!empty($parameters)) { $this->debugSection('Parameters', $parameters); } $sth = $this->driver->executeQuery($query, $parameters); return $sth->fetchColumn(); } public function grabFromDatabase($table, $column, $criteria = []) { return $this->proceedSeeInDatabase($table, $column, $criteria); } /** * Returns the number of rows in a database * * @param string $table Table name * @param array $criteria Search criteria [Optional] * * @return int */ public function grabNumRecords($table, array $criteria = []) { return $this->countInDatabase($table, $criteria); } } <?php namespace Codeception\Module; use Codeception\Module as CodeceptionModule; use Codeception\TestInterface; use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Driver\AmazonSQS; use Codeception\Lib\Driver\Beanstalk; use Codeception\Lib\Driver\Iron; /** * * Works with Queue servers. * * Testing with a selection of remote/local queueing services, including Amazon's SQS service * Iron.io service and beanstalkd service. * * Supported and tested queue types are: * * * [Iron.io](http://iron.io/) * * [Beanstalkd](http://kr.github.io/beanstalkd/) * * [Amazon SQS](http://aws.amazon.com/sqs/) * * The following dependencies are needed for the listed queue servers: * * * Beanstalkd: pda/pheanstalk ~3.0 * * Amazon SQS: aws/aws-sdk-php * * IronMQ: iron-io/iron_mq * * ## Status * * * Maintainer: **nathanmac** * * Stability: * - Iron.io: **stable** * - Beanstalkd: **stable** * - Amazon SQS: **stable** * * Contact: nathan.macnamara@outlook.com * * ## Config * * The configuration settings depending on which queueing service is being used, all the options are listed * here. Refer to the configuration examples below to identify the configuration options required for your chosen * service. * * * type - type of queueing server (defaults to beanstalkd). * * host - hostname/ip address of the queue server or the host for the iron.io when using iron.io service. * * port: 11300 - port number for the queue server. * * timeout: 90 - timeout settings for connecting the queue server. * * token - Iron.io access token. * * project - Iron.io project ID. * * key - AWS access key ID. * * secret - AWS secret access key. * Warning: * Hard-coding your credentials can be dangerous, because it is easy to accidentally commit your credentials * into an SCM repository, potentially exposing your credentials to more people than intended. * It can also make it difficult to rotate credentials in the future. * * profile - AWS credential profile * - it should be located in ~/.aws/credentials file * - eg: [default] * aws_access_key_id = YOUR_AWS_ACCESS_KEY_ID * aws_secret_access_key = YOUR_AWS_SECRET_ACCESS_KEY * [project1] * aws_access_key_id = YOUR_AWS_ACCESS_KEY_ID * aws_secret_access_key = YOUR_AWS_SECRET_ACCESS_KEY * - Note: Using IAM roles is the preferred technique for providing credentials * to applications running on Amazon EC2 * http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/credentials.html?highlight=credentials * * * region - A region parameter is also required for AWS, refer to the AWS documentation for possible values list. * * ### Example * #### Example (beanstalkd) * * modules: * enabled: [Queue] * config: * Queue: * type: 'beanstalkd' * host: '127.0.0.1' * port: 11300 * timeout: 120 * * #### Example (Iron.io) * * modules: * enabled: [Queue] * config: * Queue: * 'type' => 'iron', * 'host' => 'mq-aws-us-east-1.iron.io', * 'token' => 'your-token', * 'project' => 'your-project-id' * * #### Example (AWS SQS) * * modules: * enabled: [Queue] * config: * Queue: * 'type' => 'aws', * 'key' => 'your-public-key', * 'secret' => 'your-secret-key', * 'region' => 'us-west-2' * * #### Example AWS SQS using profile credentials * * modules: * enabled: [Queue] * config: * Queue: * 'type' => 'aws', * 'profile' => 'project1', //see documentation * 'region' => 'us-west-2' * * #### Example AWS SQS running on Anazon EC2 instance * * modules: * enabled: [Queue] * config: * Queue: * 'type' => 'aws', * 'region' => 'us-west-2' * */ class Queue extends CodeceptionModule { /** * @var \Codeception\Lib\Interfaces\Queue */ public $queueDriver; /** * Setup connection and open/setup the connection with config settings * * @param \Codeception\TestInterface $test */ public function _before(TestInterface $test) { $this->queueDriver->openConnection($this->config); } /** * Provide and override for the config settings and allow custom settings depending on the service being used. */ protected function validateConfig() { $this->queueDriver = $this->createQueueDriver(); $this->requiredFields = $this->queueDriver->getRequiredConfig(); $this->config = array_merge($this->queueDriver->getDefaultConfig(), $this->config); parent::validateConfig(); } /** * @return \Codeception\Lib\Interfaces\Queue * @throws ModuleConfigException */ protected function createQueueDriver() { switch ($this->config['type']) { case 'aws': case 'sqs': case 'aws_sqs': return new AmazonSQS(); case 'iron': case 'iron_mq': return new Iron(); case 'beanstalk': case 'beanstalkd': case 'beanstalkq': return new Beanstalk(); default: throw new ModuleConfigException( __CLASS__, "Unknown queue type {$this->config}; Supported queue types are: aws, iron, beanstalk" ); } } // ----------- SEARCH METHODS BELOW HERE ------------------------// /** * Check if a queue/tube exists on the queueing server. * * ```php * <?php * $I->seeQueueExists('default'); * ?> * ``` * * @param string $queue Queue Name */ public function seeQueueExists($queue) { $this->assertContains($queue, $this->queueDriver->getQueues()); } /** * Check if a queue/tube does NOT exist on the queueing server. * * ```php * <?php * $I->dontSeeQueueExists('default'); * ?> * ``` * * @param string $queue Queue Name */ public function dontSeeQueueExists($queue) { $this->assertNotContains($queue, $this->queueDriver->getQueues()); } /** * Check if a queue/tube is empty of all messages * * ```php * <?php * $I->seeEmptyQueue('default'); * ?> * ``` * * @param string $queue Queue Name */ public function seeEmptyQueue($queue) { $this->assertEquals(0, $this->queueDriver->getMessagesCurrentCountOnQueue($queue)); } /** * Check if a queue/tube is NOT empty of all messages * * ```php * <?php * $I->dontSeeEmptyQueue('default'); * ?> * ``` * * @param string $queue Queue Name */ public function dontSeeEmptyQueue($queue) { $this->assertNotEquals(0, $this->queueDriver->getMessagesCurrentCountOnQueue($queue)); } /** * Check if a queue/tube has a given current number of messages * * ```php * <?php * $I->seeQueueHasCurrentCount('default', 10); * ?> * ``` * * @param string $queue Queue Name * @param int $expected Number of messages expected */ public function seeQueueHasCurrentCount($queue, $expected) { $this->assertEquals($expected, $this->queueDriver->getMessagesCurrentCountOnQueue($queue)); } /** * Check if a queue/tube does NOT have a given current number of messages * * ```php * <?php * $I->dontSeeQueueHasCurrentCount('default', 10); * ?> * ``` * * @param string $queue Queue Name * @param int $expected Number of messages expected */ public function dontSeeQueueHasCurrentCount($queue, $expected) { $this->assertNotEquals($expected, $this->queueDriver->getMessagesCurrentCountOnQueue($queue)); } /** * Check if a queue/tube has a given total number of messages * * ```php * <?php * $I->seeQueueHasTotalCount('default', 10); * ?> * ``` * * @param string $queue Queue Name * @param int $expected Number of messages expected */ public function seeQueueHasTotalCount($queue, $expected) { $this->assertEquals($expected, $this->queueDriver->getMessagesTotalCountOnQueue($queue)); } /** * Check if a queue/tube does NOT have a given total number of messages * * ```php * <?php * $I->dontSeeQueueHasTotalCount('default', 10); * ?> * ``` * * @param string $queue Queue Name * @param int $expected Number of messages expected */ public function dontSeeQueueHasTotalCount($queue, $expected) { $this->assertNotEquals($expected, $this->queueDriver->getMessagesTotalCountOnQueue($queue)); } // ----------- UTILITY METHODS BELOW HERE -------------------------// /** * Add a message to a queue/tube * * ```php * <?php * $I->addMessageToQueue('this is a messages', 'default'); * ?> * ``` * * @param string $message Message Body * @param string $queue Queue Name */ public function addMessageToQueue($message, $queue) { $this->queueDriver->addMessageToQueue($message, $queue); } /** * Clear all messages of the queue/tube * * ```php * <?php * $I->clearQueue('default'); * ?> * ``` * * @param string $queue Queue Name */ public function clearQueue($queue) { $this->queueDriver->clearQueue($queue); } // ----------- GRABBER METHODS BELOW HERE -----------------------// /** * Grabber method to get the list of queues/tubes on the server * * ```php * <?php * $queues = $I->grabQueues(); * ?> * ``` * * @return array List of Queues/Tubes */ public function grabQueues() { return $this->queueDriver->getQueues(); } /** * Grabber method to get the current number of messages on the queue/tube (pending/ready) * * ```php * <?php * $I->grabQueueCurrentCount('default'); * ?> * ``` * @param string $queue Queue Name * * @return int Count */ public function grabQueueCurrentCount($queue) { return $this->queueDriver->getMessagesCurrentCountOnQueue($queue); } /** * Grabber method to get the total number of messages on the queue/tube * * ```php * <?php * $I->grabQueueTotalCount('default'); * ?> * ``` * * @param $queue Queue Name * * @return int Count */ public function grabQueueTotalCount($queue) { return $this->queueDriver->getMessagesTotalCountOnQueue($queue); } } <?php namespace Codeception\Module; use Codeception\Module as CodeceptionModule; use Codeception\Exception\ModuleException; use Codeception\TestInterface; /** * Sequence solves data cleanup issue in alternative way. * Instead cleaning up the database between tests, * you can use generated unique names, that should not conflict. * When you create article on a site, for instance, you can assign it a unique name and then check it. * * This module has no actions, but introduces a function `sq` for generating unique sequences within test and * `sqs` for generating unique sequences across suite. * * ### Usage * * Function `sq` generates sequence, the only parameter it takes, is id. * You can get back to previously generated sequence using that id: * * ``` php * <?php * sq('post1'); // post1_521fbc63021eb * sq('post2'); // post2_521fbc6302266 * sq('post1'); // post1_521fbc63021eb * ``` * * Example: * * ``` php * <?php * $I->wantTo('create article'); * $I->click('New Article'); * $I->fillField('Title', sq('Article')); * $I->fillField('Body', 'Demo article with Lorem Ipsum'); * $I->click('save'); * $I->see(sq('Article') ,'#articles') * ``` * * Populating Database: * * ``` php * <?php * * for ($i = 0; $i<10; $i++) { * $I->haveInDatabase('users', array('login' => sq("user$i"), 'email' => sq("user$i").'@email.com'); * } * ?> * ``` * * Cest Suite tests: * * ``` php * <?php * class UserTest * { * public function createUser(AcceptanceTester $I) * { * $I->createUser(sqs('user') . '@mailserver.com', sqs('login'), sqs('pwd')); * } * * public function checkEmail(AcceptanceTester $I) * { * $I->seeInEmailTo(sqs('user') . '@mailserver.com', sqs('login')); * } * * public function removeUser(AcceptanceTester $I) * { * $I->removeUser(sqs('user') . '@mailserver.com'); * } * } * ?> * ``` * * ### Config * * By default produces unique string with param as a prefix: * * ``` * sq('user') => 'user_876asd8as87a' * ``` * * This behavior can be configured using `prefix` config param. * * Old style sequences: * * ```yaml * Sequence: * prefix: '_' * ``` * * Using id param inside prefix: * * ```yaml * Sequence: * prefix: '{id}.' * ``` */ class Sequence extends CodeceptionModule { public static $hash = []; public static $suiteHash = []; public static $prefix = ''; protected $config = ['prefix' => '{id}_']; public function _initialize() { static::$prefix = $this->config['prefix']; } public function _after(TestInterface $t) { self::$hash = []; } public function _afterSuite() { self::$suiteHash = []; } } if (!function_exists('sq') && !function_exists('sqs')) { require_once __DIR__ . '/../Util/sq.php'; } else { throw new ModuleException('Codeception\Module\Sequence', "function 'sq' and 'sqs' already defined"); } <?php namespace Codeception\Module; use Codeception\Exception\ModuleException as ModuleException; use Codeception\Exception\ModuleConfigException as ModuleConfigException; use Codeception\Lib\Driver\Facebook as FacebookDriver; use Codeception\Lib\Interfaces\DependsOnModule; use Codeception\Lib\Interfaces\RequiresPackage; use Codeception\Module as BaseModule; /** * Provides testing for projects integrated with Facebook API. * Relies on Facebook's tool Test User API. * * <div class="alert alert-info"> * To use this module with Composer you need <em>"facebook/php-sdk4": "5.*"</em> package. * </div> * * ## Status * * [ ![Facebook Status for Codeception/Codeception](https://codeship.com/projects/e4bc90d0-1ed5-0134-566c-1ed679ae6c9d/status?branch=2.2)](https://codeship.com/projects/160201) * * * Stability: **beta** * * Maintainer: **tiger-seo** * * Contact: tiger.seo@codeception.com * * ## Config * * * app_id *required* - Facebook application ID * * secret *required* - Facebook application secret * * test_user - Facebook test user parameters: * * name - You can specify a name for the test user you create. The specified name will also be used in the email address assigned to the test user. * * locale - You can specify a locale for the test user you create, the default is en_US. The list of supported locales is available at https://www.facebook.com/translations/FacebookLocales.xml * * permissions - An array of permissions. Your app is granted these permissions for the new test user. The full list of permissions is available at https://developers.facebook.com/docs/authentication/permissions * * ### Config example * * modules: * enabled: * - Facebook: * depends: PhpBrowser * app_id: 412345678901234 * secret: ccb79c1b0fdff54e4f7c928bf233aea5 * test_user: * name: FacebookGuy * locale: uk_UA * permissions: [email, publish_stream] * * ### Test example: * * ``` php * <?php * $I = new ApiGuy($scenario); * $I->am('Guest'); * $I->wantToTest('check-in to a place be published on the Facebook using API'); * $I->haveFacebookTestUserAccount(); * $accessToken = $I->grabFacebookTestUserAccessToken(); * $I->haveHttpHeader('Auth', 'FacebookToken ' . $accessToken); * $I->amGoingTo('send request to the backend, so that it will publish on user\'s wall on Facebook'); * $I->sendPOST('/api/v1/some-api-endpoint'); * $I->seePostOnFacebookWithAttachedPlace('167724369950862'); * * ``` * * ``` php * <?php * $I = new WebGuy($scenario); * $I->am('Guest'); * $I->wantToTest('log in to site using Facebook'); * $I->haveFacebookTestUserAccount(); // create facebook test user * $I->haveTestUserLoggedInOnFacebook(); // so that facebook will not ask us for login and password * $fbUserFirstName = $I->grabFacebookTestUserFirstName(); * $I->amOnPage('/welcome'); * $I->see('Welcome, Guest'); * $I->click('Login with Facebook'); * $I->see('Welcome, ' . $fbUserFirstName); * * ``` * * @since 1.6.3 * @author tiger.seo@gmail.com */ class Facebook extends BaseModule implements DependsOnModule, RequiresPackage { protected $requiredFields = ['app_id', 'secret']; /** * @var FacebookDriver */ protected $facebook; /** * @var array */ protected $testUser = []; /** * @var PhpBrowser */ protected $browserModule; protected $dependencyMessage = <<<EOF Example configuring PhpBrowser -- modules enabled: - Facebook: depends: PhpBrowser app_id: 412345678901234 secret: ccb79c1b0fdff54e4f7c928bf233aea5 test_user: name: FacebookGuy locale: uk_UA permissions: [email, publish_stream] EOF; public function _requires() { return ['Facebook\Facebook' => '"facebook/graph-sdk": "~5.3"']; } public function _depends() { return ['Codeception\Module\PhpBrowser' => $this->dependencyMessage]; } public function _inject(PhpBrowser $browserModule) { $this->browserModule = $browserModule; } protected function deleteTestUser() { if (array_key_exists('id', $this->testUser)) { // make api-call for test user deletion $this->facebook->deleteTestUser($this->testUser['id']); $this->testUser = []; } } public function _initialize() { if (!array_key_exists('test_user', $this->config)) { $this->config['test_user'] = [ 'permissions' => [], 'name' => 'Codeception Testuser' ]; } elseif (!array_key_exists('permissions', $this->config['test_user'])) { $this->config['test_user']['permissions'] = []; } elseif (!array_key_exists('name', $this->config['test_user'])) { $this->config['test_user']['name'] = "codeception testuser"; } $this->facebook = new FacebookDriver( [ 'app_id' => $this->config['app_id'], 'secret' => $this->config['secret'], ], function ($title, $message) { if (version_compare(PHP_VERSION, '5.4', '>=')) { $this->debugSection($title, $message); } } ); } public function _afterSuite() { $this->deleteTestUser(); } /** * Get facebook test user be created. * * *Please, note that the test user is created only at first invoke, unless $renew arguments is true.* * * @param bool $renew true if the test user should be recreated */ public function haveFacebookTestUserAccount($renew = false) { if ($renew) { $this->deleteTestUser(); } // make api-call for test user creation only if it's not yet created if (!array_key_exists('id', $this->testUser)) { $this->testUser = $this->facebook->createTestUser( $this->config['test_user']['name'], $this->config['test_user']['permissions'] ); } } /** * Get facebook test user be logged in on facebook. * This is done by going to facebook.com * * @throws ModuleConfigException */ public function haveTestUserLoggedInOnFacebook() { if (!array_key_exists('id', $this->testUser)) { throw new ModuleException( __CLASS__, 'Facebook test user was not found. Did you forget to create one?' ); } $callbackUrl = $this->browserModule->_getUrl(); $this->browserModule->amOnUrl('https://facebook.com/login'); $this->browserModule->submitForm('#login_form', [ 'email' => $this->grabFacebookTestUserEmail(), 'pass' => $this->grabFacebookTestUserPassword() ]); // if login in successful we are back on login screen: $this->browserModule->dontSeeInCurrentUrl('/login'); $this->browserModule->amOnUrl($callbackUrl); } /** * Returns the test user access token. * * @return string */ public function grabFacebookTestUserAccessToken() { return $this->testUser['access_token']; } /** * Returns the test user id. * * @return string */ public function grabFacebookTestUserId() { return $this->testUser['id']; } /** * Returns the test user email. * * @return string */ public function grabFacebookTestUserEmail() { return $this->testUser['email']; } /** * Returns URL for test user auto-login. * * @return string */ public function grabFacebookTestUserLoginUrl() { return $this->testUser['login_url']; } public function grabFacebookTestUserPassword() { return $this->testUser['password']; } /** * Returns the test user name. * * @return string */ public function grabFacebookTestUserName() { if (!array_key_exists('profile', $this->testUser)) { $this->testUser['profile'] = $this->facebook->getTestUserInfo($this->grabFacebookTestUserAccessToken()); } return $this->testUser['profile']['name']; } /** * Please, note that you must have publish_actions permission to be able to publish to user's feed. * * @param array $params */ public function postToFacebookAsTestUser($params) { $this->facebook->sendPostToFacebook($this->grabFacebookTestUserAccessToken(), $params); } /** * * Please, note that you must have publish_actions permission to be able to publish to user's feed. * * @param string $placeId Place identifier to be verified against user published posts */ public function seePostOnFacebookWithAttachedPlace($placeId) { $token = $this->grabFacebookTestUserAccessToken(); $this->debugSection('Access Token', $token); $place = $this->facebook->getVisitedPlaceTagForTestUser($placeId, $token); $this->assertEquals($placeId, $place['id'], "The place was not found on facebook page"); } /** * * Please, note that you must have publish_actions permission to be able to publish to user's feed. * * @param string $message published post to be verified against the actual post on facebook */ public function seePostOnFacebookWithMessage($message) { $posts = $this->facebook->getLastPostsForTestUser($this->grabFacebookTestUserAccessToken()); $facebook_post_message = ''; $this->assertNotEquals($message, $facebook_post_message, "You can not test for an empty message post"); if ($posts['data']) { foreach ($posts['data'] as $post) { if (array_key_exists('message', $post) && ($post['message'] == $message)) { $facebook_post_message = $post['message']; } } } $this->assertEquals($message, $facebook_post_message, "The post message was not found on facebook page"); } } <?php namespace Codeception; use Codeception\Exception\ConfigurationException; use Codeception\Subscriber\ExtensionLoader; use Symfony\Component\EventDispatcher\EventDispatcher; class Codecept { const VERSION = "2.3.2"; /** * @var \Codeception\PHPUnit\Runner */ protected $runner; /** * @var \PHPUnit_Framework_TestResult */ protected $result; /** * @var \Codeception\CodeCoverage */ protected $coverage; /** * @var \Symfony\Component\EventDispatcher\EventDispatcher */ protected $dispatcher; /** * @var ExtensionLoader */ protected $extensionLoader; /** * @var array */ protected $options = [ 'silent' => false, 'debug' => false, 'steps' => false, 'html' => false, 'xml' => false, 'json' => false, 'tap' => false, 'report' => false, 'colors' => false, 'coverage' => false, 'coverage-xml' => false, 'coverage-html' => false, 'coverage-text' => false, 'coverage-crap4j' => false, 'groups' => null, 'excludeGroups' => null, 'filter' => null, 'env' => null, 'fail-fast' => false, 'ansi' => true, 'verbosity' => 1, 'interactive' => true, 'no-rebuild' => false, 'quiet' => false, ]; protected $config = []; /** * @var array */ protected $extensions = []; public function __construct($options = []) { $this->result = new \PHPUnit_Framework_TestResult; $this->dispatcher = new EventDispatcher(); $this->extensionLoader = new ExtensionLoader($this->dispatcher); $baseOptions = $this->mergeOptions($options); $this->extensionLoader->bootGlobalExtensions($baseOptions); // extensions may override config $this->config = Configuration::config(); $this->options = $this->mergeOptions($options); // options updated from config $this->registerSubscribers(); $this->registerPHPUnitListeners(); $this->registerPrinter(); } /** * Merges given options with default values and current configuration * * @param array $options options * @return array * @throws ConfigurationException */ protected function mergeOptions($options) { $config = Configuration::config(); $baseOptions = array_merge($this->options, $config['settings']); return array_merge($baseOptions, $options); } protected function registerPHPUnitListeners() { $listener = new PHPUnit\Listener($this->dispatcher); $this->result->addListener($listener); } public function registerSubscribers() { // required $this->dispatcher->addSubscriber(new Subscriber\GracefulTermination()); $this->dispatcher->addSubscriber(new Subscriber\ErrorHandler()); $this->dispatcher->addSubscriber(new Subscriber\Dependencies()); $this->dispatcher->addSubscriber(new Subscriber\Bootstrap()); $this->dispatcher->addSubscriber(new Subscriber\Module()); $this->dispatcher->addSubscriber(new Subscriber\BeforeAfterTest()); // optional if (!$this->options['no-rebuild']) { $this->dispatcher->addSubscriber(new Subscriber\AutoRebuild()); } if (!$this->options['silent']) { $this->dispatcher->addSubscriber(new Subscriber\Console($this->options)); } if ($this->options['fail-fast']) { $this->dispatcher->addSubscriber(new Subscriber\FailFast()); } if ($this->options['coverage']) { $this->dispatcher->addSubscriber(new Coverage\Subscriber\Local($this->options)); $this->dispatcher->addSubscriber(new Coverage\Subscriber\LocalServer($this->options)); $this->dispatcher->addSubscriber(new Coverage\Subscriber\RemoteServer($this->options)); $this->dispatcher->addSubscriber(new Coverage\Subscriber\Printer($this->options)); } $this->dispatcher->addSubscriber($this->extensionLoader); $this->extensionLoader->registerGlobalExtensions(); } public function run($suite, $test = null) { ini_set( 'memory_limit', isset($this->config['settings']['memory_limit']) ? $this->config['settings']['memory_limit'] : '1024M' ); $settings = Configuration::suiteSettings($suite, Configuration::config()); $selectedEnvironments = $this->options['env']; $environments = Configuration::suiteEnvironments($suite); if (!$selectedEnvironments or empty($environments)) { $this->runSuite($settings, $suite, $test); return; } foreach (array_unique($selectedEnvironments) as $envList) { $envArray = explode(',', $envList); $config = []; foreach ($envArray as $env) { if (isset($environments[$env])) { $currentEnvironment = isset($config['current_environment']) ? [$config['current_environment']] : []; $config = Configuration::mergeConfigs($config, $environments[$env]); $currentEnvironment[] = $config['current_environment']; $config['current_environment'] = implode(',', $currentEnvironment); } } if (empty($config)) { continue; } $suiteToRun = $suite; if (!empty($envList)) { $suiteToRun .= ' (' . implode(', ', $envArray) . ')'; } $this->runSuite($config, $suiteToRun, $test); } } public function runSuite($settings, $suite, $test = null) { $suiteManager = new SuiteManager($this->dispatcher, $suite, $settings); $suiteManager->initialize(); $suiteManager->loadTests($test); $suiteManager->run($this->runner, $this->result, $this->options); return $this->result; } public static function versionString() { return 'Codeception PHP Testing Framework v' . self::VERSION; } public function printResult() { $result = $this->getResult(); $result->flushListeners(); $printer = $this->runner->getPrinter(); $printer->printResult($result); $this->dispatcher->dispatch(Events::RESULT_PRINT_AFTER, new Event\PrintResultEvent($result, $printer)); } /** * @return \PHPUnit_Framework_TestResult */ public function getResult() { return $this->result; } public function getOptions() { return $this->options; } /** * @return EventDispatcher */ public function getDispatcher() { return $this->dispatcher; } protected function registerPrinter() { $printer = new PHPUnit\ResultPrinter\UI($this->dispatcher, $this->options); $this->runner = new PHPUnit\Runner(); $this->runner->setPrinter($printer); } } <?php namespace Codeception; use Codeception\Test\Descriptor; use Codeception\Test\Interfaces\Dependent; class Suite extends \PHPUnit_Framework_TestSuite { protected $modules; protected $baseName; public function reorderDependencies() { $tests = []; foreach ($this->tests as $test) { $tests = array_merge($tests, $this->getDependencies($test)); } $queue = []; $hashes = []; foreach ($tests as $test) { if (in_array(spl_object_hash($test), $hashes)) { continue; } $hashes[] = spl_object_hash($test); $queue[] = $test; } $this->tests = $queue; } protected function getDependencies($test) { if (!$test instanceof Dependent) { return [$test]; } $tests = []; foreach ($test->getDependencies() as $requiredTestName) { $required = $this->findMatchedTest($requiredTestName); if (!$required) { continue; } $tests = array_merge($tests, $this->getDependencies($required)); } $tests[] = $test; return $tests; } protected function findMatchedTest($testSignature) { foreach ($this->tests as $test) { $signature = Descriptor::getTestSignature($test); if ($signature === $testSignature) { return $test; } } if ($test instanceof TestInterface) { $test->getMetadata()->setSkip("Dependent test for $testSignature not found"); } } /** * @return mixed */ public function getModules() { return $this->modules; } /** * @param mixed $modules */ public function setModules($modules) { $this->modules = $modules; } /** * @return mixed */ public function getBaseName() { return $this->baseName; } /** * @param mixed $baseName */ public function setBaseName($baseName) { $this->baseName = $baseName; } } <?php namespace Codeception\Extension; use Codeception\Event\FailEvent; use Codeception\Events; use Codeception\Extension; use Codeception\Subscriber\Console; /** * DotReporter provides less verbose output for test execution. * Like PHPUnit printer it prints dots "." for successful testes and "F" for failures. * * ![](https://cloud.githubusercontent.com/assets/220264/26132800/4d23f336-3aab-11e7-81ba-2896a4c623d2.png) * * ```bash * .......... * .......... * .......... * .......... * .......... * .......... * .......... * .......... * * Time: 2.07 seconds, Memory: 20.00MB * * OK (80 tests, 124 assertions) * ``` * * * Enable this reporter with `--ext option` * * ``` * codecept run --ext DotReporter * ``` * * Failures and Errors are printed by a standard Codeception reporter. * Use this extension as an example for building custom reporters. */ class DotReporter extends Extension { /** * @var Console */ protected $standardReporter; protected $errors = []; protected $failures = []; protected $width = 10; protected $currentPos = 0; public function _initialize() { $this->options['silent'] = false; // turn on printing for this extension $this->_reconfigure(['settings' => ['silent' => true]]); // turn off printing for everything else $this->standardReporter = new Console($this->options); $this->width = $this->standardReporter->detectWidth(); } // we are listening for events public static $events = [ Events::SUITE_BEFORE => 'beforeSuite', Events::TEST_SUCCESS => 'success', Events::TEST_FAIL => 'fail', Events::TEST_ERROR => 'error', Events::TEST_SKIPPED => 'skipped', Events::TEST_FAIL_PRINT => 'printFailed' ]; public function beforeSuite() { $this->writeln(""); } public function success() { $this->printChar('.'); } public function fail(FailEvent $e) { $this->printChar("<error>F</error>"); } public function error(FailEvent $e) { $this->printChar('<error>E</error>'); } public function skipped() { $this->printChar('S'); } protected function printChar($char) { if ($this->currentPos >= $this->width) { $this->writeln(''); $this->currentPos = 0; } $this->write($char); $this->currentPos++; } public function printFailed(FailEvent $event) { $this->standardReporter->printFail($event); } } <?php namespace Codeception\Extension; use Codeception\Event\PrintResultEvent; use Codeception\Events; use Codeception\Extension; use Codeception\Test\Descriptor; /** * Saves failed tests into tests/log/failed in order to rerun failed tests. * * To rerun failed tests just run the `failed` group: * * ``` * php codecept run -g failed * ``` * * Starting from Codeception 2.1 **this extension is enabled by default**. * * ``` yaml * extensions: * enabled: [Codeception\Extension\RunFailed] * ``` * * On each execution failed tests are logged and saved into `tests/_output/failed` file. */ class RunFailed extends Extension { public static $events = [ Events::RESULT_PRINT_AFTER => 'saveFailed' ]; protected $config = ['file' => 'failed']; public function _initialize() { $logPath = str_replace($this->getRootDir(), '', $this->getLogDir()); // get local path to logs $this->_reconfigure(['groups' => ['failed' => $logPath . $this->config['file']]]); } public function saveFailed(PrintResultEvent $e) { $file = $this->getLogDir() . $this->config['file']; $result = $e->getResult(); if ($result->wasSuccessful()) { if (is_file($file)) { unlink($file); } return; } $output = []; foreach ($result->failures() as $fail) { $output[] = $this->localizePath(Descriptor::getTestFullName($fail->failedTest())); } foreach ($result->errors() as $fail) { $output[] = $this->localizePath(Descriptor::getTestFullName($fail->failedTest())); } file_put_contents($file, implode("\n", $output)); } protected function localizePath($path) { $root = realpath($this->getRootDir()) . DIRECTORY_SEPARATOR; if (substr($path, 0, strlen($root)) == $root) { return substr($path, strlen($root)); } return $path; } } <?php namespace Codeception\Extension; use Codeception\Event\FailEvent; use Codeception\Event\StepEvent; use Codeception\Event\SuiteEvent; use Codeception\Event\TestEvent; use Codeception\Events; use Codeception\Exception\ConfigurationException; use Codeception\Extension; use Codeception\Test\Descriptor; use Monolog\Handler\RotatingFileHandler; /** * Log suites/tests/steps using Monolog library. * Monolog should be installed additionally by Composer. * * ``` * composer require monolog/monolog * ``` * * Steps are logged into `tests/_output/codeception.log` * * To enable this module add to your `codeception.yml`: * * ``` yaml * extensions: * enabled: [Codeception\Extension\Logger] * ``` * * #### Config * * * `max_files` (default: 3) - how many log files to keep * */ class Logger extends Extension { public static $events = [ Events::SUITE_BEFORE => 'beforeSuite', Events::TEST_BEFORE => 'beforeTest', Events::TEST_AFTER => 'afterTest', Events::TEST_END => 'endTest', Events::STEP_BEFORE => 'beforeStep', Events::TEST_FAIL => 'testFail', Events::TEST_ERROR => 'testError', Events::TEST_INCOMPLETE => 'testIncomplete', Events::TEST_SKIPPED => 'testSkipped', ]; protected $logHandler; /** * @var \Monolog\Logger */ protected $logger; protected $path; protected $config = ['max_files' => 3]; public function _initialize() { if (!class_exists('\Monolog\Logger')) { throw new ConfigurationException("Logger extension requires Monolog library to be installed"); } $this->path = $this->getLogDir(); // internal log $logHandler = new RotatingFileHandler($this->path . 'codeception.log', $this->config['max_files']); $this->logger = new \Monolog\Logger('Codeception'); $this->logger->pushHandler($logHandler); } public function beforeSuite(SuiteEvent $e) { $suite = str_replace('\\', '_', $e->getSuite()->getName()); $this->logHandler = new RotatingFileHandler($this->path . $suite, $this->config['max_files']); } public function beforeTest(TestEvent $e) { $this->logger = new \Monolog\Logger(Descriptor::getTestFileName($e->getTest())); $this->logger->pushHandler($this->logHandler); $this->logger->info('------------------------------------'); $this->logger->info("STARTED: " . ucfirst(Descriptor::getTestAsString($e->getTest()))); } public function afterTest(TestEvent $e) { } public function endTest(TestEvent $e) { $this->logger->info("PASSED"); } public function testFail(FailEvent $e) { $this->logger->alert($e->getFail()->getMessage()); $this->logger->info("# FAILED #"); } public function testError(FailEvent $e) { $this->logger->alert($e->getFail()->getMessage()); $this->logger->info("# ERROR #"); } public function testSkipped(FailEvent $e) { $this->logger->info("# Skipped #"); } public function testIncomplete(FailEvent $e) { $this->logger->info("# Incomplete #"); } public function beforeStep(StepEvent $e) { $this->logger->info((string) $e->getStep()); } } <?php namespace Codeception\Extension; use Codeception\Event\StepEvent; use Codeception\Event\TestEvent; use Codeception\Events; use Codeception\Exception\ExtensionException; use Codeception\Lib\Interfaces\ScreenshotSaver; use Codeception\Module\WebDriver; use Codeception\Step\Comment as CommentStep; use Codeception\Test\Descriptor; use Codeception\Util\FileSystem; use Codeception\Util\Template; /** * Saves a screenshot of each step in acceptance tests and shows them as a slideshow on one HTML page (here's an [example](http://codeception.com/images/recorder.gif)) * Activated only for suites with WebDriver module enabled. * * The screenshots are saved to `tests/_output/record_*` directories, open `index.html` to see them as a slideshow. * * #### Installation * * Add this to the list of enabled extensions in `codeception.yml` or `acceptance.suite.yml`: * * ``` yaml * extensions: * enabled: * - Codeception\Extension\Recorder * ``` * * #### Configuration * * * `delete_successful` (default: true) - delete screenshots for successfully passed tests (i.e. log only failed and errored tests). * * `module` (default: WebDriver) - which module for screenshots to use. Set `AngularJS` if you want to use it with AngularJS module. Generally, the module should implement `Codeception\Lib\Interfaces\ScreenshotSaver` interface. * * * #### Examples: * * ``` yaml * extensions: * enabled: * Codeception\Extension\Recorder: * module: AngularJS # enable for Angular * delete_successful: false # keep screenshots of successful tests * ``` * */ class Recorder extends \Codeception\Extension { protected $config = [ 'delete_successful' => true, 'module' => 'WebDriver', 'template' => null, 'animate_slides' => true ]; protected $template = <<<EOF <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Recorder Result
EOF; protected $indicatorTemplate = << EOF; protected $indexTemplate = << Recorder Results Index

Record #{{seed}}

    {{records}}
EOF; protected $slidesTemplate = <<
EOF; public static $events = [ Events::SUITE_BEFORE => 'beforeSuite', Events::SUITE_AFTER => 'afterSuite', Events::TEST_BEFORE => 'before', Events::TEST_ERROR => 'persist', Events::TEST_FAIL => 'persist', Events::TEST_SUCCESS => 'cleanup', Events::STEP_AFTER => 'afterStep', ]; /** * @var WebDriver */ protected $webDriverModule; protected $dir; protected $slides = []; protected $stepNum = 0; protected $seed; protected $recordedTests = []; public function beforeSuite() { $this->webDriverModule = null; if (!$this->hasModule($this->config['module'])) { $this->writeln("Recorder is disabled, no available modules"); return; } $this->seed = uniqid(); $this->webDriverModule = $this->getModule($this->config['module']); if (!$this->webDriverModule instanceof ScreenshotSaver) { throw new ExtensionException( $this, 'You should pass module which implements Codeception\Lib\Interfaces\ScreenshotSaver interface' ); } $this->writeln(sprintf( "⏺ Recording ⏺ step-by-step screenshots will be saved to %s", codecept_output_dir() )); $this->writeln("Directory Format: record_{$this->seed}_{testname} ----"); } public function afterSuite() { if (!$this->webDriverModule or !$this->dir) { return; } $links = ''; foreach ($this->recordedTests as $link => $url) { $links .= "
  • $link
  • \n"; } $indexHTML = (new Template($this->indexTemplate)) ->place('seed', $this->seed) ->place('records', $links) ->produce(); file_put_contents(codecept_output_dir().'records.html', $indexHTML); $this->writeln("⏺ Records saved into: file://" . codecept_output_dir().'records.html'); } public function before(TestEvent $e) { if (!$this->webDriverModule) { return; } $this->dir = null; $this->stepNum = 0; $this->slides = []; $testName = preg_replace('~\W~', '_', Descriptor::getTestAsString($e->getTest())); $this->dir = codecept_output_dir() . "record_{$this->seed}_$testName"; @mkdir($this->dir); } public function cleanup(TestEvent $e) { if (!$this->webDriverModule or !$this->dir) { return; } if (!$this->config['delete_successful']) { $this->persist($e); return; } // deleting successfully executed tests FileSystem::deleteDir($this->dir); } public function persist(TestEvent $e) { if (!$this->webDriverModule or !$this->dir) { return; } $indicatorHtml = ''; $slideHtml = ''; foreach ($this->slides as $i => $step) { $indicatorHtml .= (new Template($this->indicatorTemplate)) ->place('step', (int)$i) ->place('isActive', (int)$i ? '' : 'class="active"') ->produce(); $slideHtml .= (new Template($this->slidesTemplate)) ->place('image', $i) ->place('caption', $step->getHtml('#3498db')) ->place('isActive', (int)$i ? '' : 'active') ->place('isError', $step->hasFailed() ? 'error' : '') ->produce(); } $html = (new Template($this->template)) ->place('indicators', $indicatorHtml) ->place('slides', $slideHtml) ->place('feature', ucfirst($e->getTest()->getFeature())) ->place('test', Descriptor::getTestSignature($e->getTest())) ->place('carousel_class', $this->config['animate_slides'] ? ' slide' : '') ->produce(); $indexFile = $this->dir . DIRECTORY_SEPARATOR . 'index.html'; file_put_contents($indexFile, $html); $testName = Descriptor::getTestSignature($e->getTest()). ' - '.ucfirst($e->getTest()->getFeature()); $this->recordedTests[$testName] = substr($indexFile, strlen(codecept_output_dir())); } public function afterStep(StepEvent $e) { if (!$this->webDriverModule or !$this->dir) { return; } if ($e->getStep() instanceof CommentStep) { return; } $filename = str_pad($this->stepNum, 3, "0", STR_PAD_LEFT) . '.png'; $this->webDriverModule->_saveScreenshot($this->dir . DIRECTORY_SEPARATOR . $filename); $this->stepNum++; $this->slides[$filename] = $e->getStep(); } } options['silent'] = false; // turn on printing for this extension $this->_reconfigure(['settings' => ['silent' => true]]); // turn off printing for everything else } // we are listening for events public static $events = [ Events::SUITE_BEFORE => 'beforeSuite', Events::TEST_END => 'after', Events::TEST_SUCCESS => 'success', Events::TEST_FAIL => 'fail', Events::TEST_ERROR => 'error', ]; public function beforeSuite() { $this->writeln(""); } public function success() { $this->write('[+] '); } public function fail() { $this->write('[-] '); } public function error() { $this->write('[E] '); } // we are printing test status and time taken public function after(TestEvent $e) { $seconds_input = $e->getTime(); // stack overflow: http://stackoverflow.com/questions/16825240/how-to-convert-microtime-to-hhmmssuu $seconds = (int)($milliseconds = (int)($seconds_input * 1000)) / 1000; $time = ($seconds % 60) . (($milliseconds === 0) ? '' : '.' . $milliseconds); $this->write(Descriptor::getTestSignature($e->getTest())); $this->writeln(' (' . $time . 's)'); } } driver = $driver; $this->timeout = isset($timeout_in_second) ? $timeout_in_second : 30; $this->interval = $interval_in_millisecond ?: 250; } public function until($func_or_ec, $message = '') { $end = microtime(true) + $this->timeout; $last_exception = null; while ($end > microtime(true)) { try { if ($func_or_ec instanceof WebDriverExpectedCondition) { $ret_val = call_user_func($func_or_ec->getApply(), $this->driver); } else { $ret_val = call_user_func($func_or_ec, $this->driver); } if ($ret_val) { return $ret_val; } } catch (NoSuchElementException $e) { $last_exception = $e; } usleep($this->interval * 1000); } if ($last_exception) { throw $last_exception; } throw new TimeOutException($message); } } apply; } protected function __construct(callable $apply) { $this->apply = $apply; } public static function titleIs($title) { return new static( function (WebDriver $driver) use ($title) { return $title === $driver->getTitle(); } ); } public static function titleContains($title) { return new static( function (WebDriver $driver) use ($title) { return strpos($driver->getTitle(), $title) !== false; } ); } public static function titleMatches($titleRegexp) { return new static( function (WebDriver $driver) use ($titleRegexp) { return (bool) preg_match($titleRegexp, $driver->getTitle()); } ); } public static function urlIs($url) { return new static( function (WebDriver $driver) use ($url) { return $url === $driver->getCurrentURL(); } ); } public static function urlContains($url) { return new static( function (WebDriver $driver) use ($url) { return strpos($driver->getCurrentURL(), $url) !== false; } ); } public static function urlMatches($urlRegexp) { return new static( function (WebDriver $driver) use ($urlRegexp) { return (bool) preg_match($urlRegexp, $driver->getCurrentURL()); } ); } public static function presenceOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { return $driver->findElement($by); } ); } public static function presenceOfAllElementsLocatedBy(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { $elements = $driver->findElements($by); return count($elements) > 0 ? $elements : null; } ); } public static function visibilityOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { $element = $driver->findElement($by); return $element->isDisplayed() ? $element : null; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function visibilityOf(WebDriverElement $element) { return new static( function () use ($element) { return $element->isDisplayed() ? $element : null; } ); } public static function textToBePresentInElement(WebDriverBy $by, $text) { return self::elementTextContains($by, $text); } public static function elementTextContains(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { $element_text = $driver->findElement($by)->getText(); return strpos($element_text, $text) !== false; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function elementTextIs(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { return $driver->findElement($by)->getText() == $text; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function elementTextMatches(WebDriverBy $by, $regexp) { return new static( function (WebDriver $driver) use ($by, $regexp) { try { return (bool) preg_match($regexp, $driver->findElement($by)->getText()); } catch (StaleElementReferenceException $e) { return null; } } ); } public static function textToBePresentInElementValue(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { $element_text = $driver->findElement($by)->getAttribute('value'); return strpos($element_text, $text) !== false; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function frameToBeAvailableAndSwitchToIt($frame_locator) { return new static( function (WebDriver $driver) use ($frame_locator) { try { return $driver->switchTo()->frame($frame_locator); } catch (NoSuchFrameException $e) { return false; } } ); } public static function invisibilityOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { return !$driver->findElement($by)->isDisplayed(); } catch (NoSuchElementException $e) { return true; } catch (StaleElementReferenceException $e) { return true; } } ); } public static function invisibilityOfElementWithText(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { return !($driver->findElement($by)->getText() === $text); } catch (NoSuchElementException $e) { return true; } catch (StaleElementReferenceException $e) { return true; } } ); } public static function elementToBeClickable(WebDriverBy $by) { $visibility_of_element_located = self::visibilityOfElementLocated($by); return new static( function (WebDriver $driver) use ($visibility_of_element_located) { $element = call_user_func( $visibility_of_element_located->getApply(), $driver ); try { if ($element !== null && $element->isEnabled()) { return $element; } return null; } catch (StaleElementReferenceException $e) { return null; } } ); } public static function stalenessOf(WebDriverElement $element) { return new static( function () use ($element) { try { $element->isEnabled(); return false; } catch (StaleElementReferenceException $e) { return true; } } ); } public static function refreshed(WebDriverExpectedCondition $condition) { return new static( function (WebDriver $driver) use ($condition) { try { return call_user_func($condition->getApply(), $driver); } catch (StaleElementReferenceException $e) { return null; } } ); } public static function elementToBeSelected($element_or_by) { return self::elementSelectionStateToBe( $element_or_by, true ); } public static function elementSelectionStateToBe($element_or_by, $selected) { if ($element_or_by instanceof WebDriverElement) { return new static( function () use ($element_or_by, $selected) { return $element_or_by->isSelected() === $selected; } ); } else { if ($element_or_by instanceof WebDriverBy) { return new static( function (WebDriver $driver) use ($element_or_by, $selected) { try { $element = $driver->findElement($element_or_by); return $element->isSelected() === $selected; } catch (StaleElementReferenceException $e) { return null; } } ); } } } public static function alertIsPresent() { return new static( function (WebDriver $driver) { try { $alert = $driver->switchTo()->alert(); $alert->getText(); return $alert; } catch (NoAlertOpenException $e) { return null; } } ); } public static function numberOfWindowsToBe($expectedNumberOfWindows) { return new static( function (WebDriver $driver) use ($expectedNumberOfWindows) { return count($driver->getWindowHandles()) == $expectedNumberOfWindows; } ); } public static function not(WebDriverExpectedCondition $condition) { return new static( function (WebDriver $driver) use ($condition) { $result = call_user_func($condition->getApply(), $driver); return !$result; } ); } } x = $x; $this->y = $y; } public function getX() { return $this->x; } public function getY() { return $this->y; } public function move($new_x, $new_y) { $this->x = $new_x; $this->y = $new_y; return $this; } public function moveBy($x_offset, $y_offset) { $this->x += $x_offset; $this->y += $y_offset; return $this; } public function equals(WebDriverPoint $point) { return $this->x === $point->getX() && $this->y === $point->getY(); } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->up($this->x, $this->y); } } executor = $executor; } public function click(WebDriverCoordinates $where = null) { $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ 'button' => 0, ]); return $this; } public function contextClick(WebDriverCoordinates $where = null) { $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ 'button' => 2, ]); return $this; } public function doubleClick(WebDriverCoordinates $where = null) { $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::DOUBLE_CLICK); return $this; } public function mouseDown(WebDriverCoordinates $where = null) { $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_DOWN); return $this; } public function mouseMove( WebDriverCoordinates $where = null, $x_offset = null, $y_offset = null ) { $params = []; if ($where !== null) { $params['element'] = $where->getAuxiliary(); } if ($x_offset !== null) { $params['xoffset'] = $x_offset; } if ($y_offset !== null) { $params['yoffset'] = $y_offset; } $this->executor->execute(DriverCommand::MOVE_TO, $params); return $this; } public function mouseUp(WebDriverCoordinates $where = null) { $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_UP); return $this; } protected function moveIfNeeded(WebDriverCoordinates $where = null) { if ($where) { $this->mouseMove($where); } } } capabilities = $capabilities; } public function getBrowserName() { return $this->get(WebDriverCapabilityType::BROWSER_NAME, ''); } public function setBrowserName($browser_name) { $this->set(WebDriverCapabilityType::BROWSER_NAME, $browser_name); return $this; } public function getVersion() { return $this->get(WebDriverCapabilityType::VERSION, ''); } public function setVersion($version) { $this->set(WebDriverCapabilityType::VERSION, $version); return $this; } public function getCapability($name) { return $this->get($name); } public function setCapability($name, $value) { $this->set($name, $value); return $this; } public function getPlatform() { return $this->get(WebDriverCapabilityType::PLATFORM, ''); } public function setPlatform($platform) { $this->set(WebDriverCapabilityType::PLATFORM, $platform); return $this; } public function is($capability_name) { return (bool) $this->get($capability_name); } public function isJavascriptEnabled() { return $this->get(WebDriverCapabilityType::JAVASCRIPT_ENABLED, false); } public function setJavascriptEnabled($enabled) { $browser = $this->getBrowserName(); if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) { throw new Exception( 'isJavascriptEnable() is a htmlunit-only option. ' . 'See https://code.google.com/p/selenium/wiki/DesiredCapabilities#Read-write_capabilities.' ); } $this->set(WebDriverCapabilityType::JAVASCRIPT_ENABLED, $enabled); return $this; } public function toArray() { if (isset($this->capabilities[ChromeOptions::CAPABILITY]) && $this->capabilities[ChromeOptions::CAPABILITY] instanceof ChromeOptions ) { $this->capabilities[ChromeOptions::CAPABILITY] = $this->capabilities[ChromeOptions::CAPABILITY]->toArray(); } if (isset($this->capabilities[FirefoxDriver::PROFILE]) && $this->capabilities[FirefoxDriver::PROFILE] instanceof FirefoxProfile ) { $this->capabilities[FirefoxDriver::PROFILE] = $this->capabilities[FirefoxDriver::PROFILE]->encode(); } return $this->capabilities; } private function set($key, $value) { $this->capabilities[$key] = $value; return $this; } private function get($key, $default = null) { return isset($this->capabilities[$key]) ? $this->capabilities[$key] : $default; } public static function android() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::ANDROID, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANDROID, ]); } public static function chrome() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::CHROME, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function firefox() { $caps = new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::FIREFOX, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); $profile = new FirefoxProfile(); $profile->setPreference(FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED, false); $caps->setCapability(FirefoxDriver::PROFILE, $profile); return $caps; } public static function htmlUnit() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function htmlUnitWithJS() { $caps = new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); return $caps->setJavascriptEnabled(true); } public static function internetExplorer() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS, ]); } public static function microsoftEdge() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::MICROSOFT_EDGE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS, ]); } public static function iphone() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPHONE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC, ]); } public static function ipad() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPAD, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC, ]); } public static function opera() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::OPERA, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function safari() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::SAFARI, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } public static function phantomjs() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::PHANTOMJS, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } } executor = $executor; $this->driver = $driver; } public function defaultContent() { $params = ['id' => null]; $this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params); return $this->driver; } public function frame($frame) { if ($frame instanceof WebDriverElement) { $id = ['ELEMENT' => $frame->getID()]; } else { $id = (string) $frame; } $params = ['id' => $id]; $this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params); return $this->driver; } public function window($handle) { $params = ['name' => (string) $handle]; $this->executor->execute(DriverCommand::SWITCH_TO_WINDOW, $params); return $this->driver; } public function alert() { return new WebDriverAlert($this->executor); } public function activeElement() { $response = $this->driver->execute(DriverCommand::GET_ACTIVE_ELEMENT, []); $method = new RemoteExecuteMethod($this->driver); return new RemoteWebElement($method, $response['ELEMENT']); } } executor = $commandExecutor; $this->sessionID = $sessionId; if ($capabilities !== null) { $this->capabilities = $capabilities; } } public static function create( $selenium_server_url = 'http://localhost:4444/wd/hub', $desired_capabilities = null, $connection_timeout_in_ms = null, $request_timeout_in_ms = null, $http_proxy = null, $http_proxy_port = null, DesiredCapabilities $required_capabilities = null ) { $selenium_server_url = preg_replace('#/+$#', '', $selenium_server_url); $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities); $executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); } if ($request_timeout_in_ms !== null) { $executor->setRequestTimeout($request_timeout_in_ms); } if ($required_capabilities !== null) { $desired_capabilities->setCapability('requiredCapabilities', $required_capabilities->toArray()); } $command = new WebDriverCommand( null, DriverCommand::NEW_SESSION, ['desiredCapabilities' => $desired_capabilities->toArray()] ); $response = $executor->execute($command); $returnedCapabilities = new DesiredCapabilities($response->getValue()); $driver = new static($executor, $response->getSessionID(), $returnedCapabilities); return $driver; } protected static function castToDesiredCapabilitiesObject($desired_capabilities = null) { if ($desired_capabilities === null) { return new DesiredCapabilities(); } if (is_array($desired_capabilities)) { return new DesiredCapabilities($desired_capabilities); } return $desired_capabilities; } public static function createBySessionID($session_id, $selenium_server_url = 'http://localhost:4444/wd/hub') { $executor = new HttpCommandExecutor($selenium_server_url); return new static($executor, $session_id); } public function close() { $this->execute(DriverCommand::CLOSE, []); return $this; } public function findElement(WebDriverBy $by) { $params = ['using' => $by->getMechanism(), 'value' => $by->getValue()]; $raw_element = $this->execute( DriverCommand::FIND_ELEMENT, $params ); return $this->newElement($raw_element['ELEMENT']); } public function findElements(WebDriverBy $by) { $params = ['using' => $by->getMechanism(), 'value' => $by->getValue()]; $raw_elements = $this->execute( DriverCommand::FIND_ELEMENTS, $params ); $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement($raw_element['ELEMENT']); } return $elements; } public function get($url) { $params = ['url' => (string) $url]; $this->execute(DriverCommand::GET, $params); return $this; } public function getCurrentURL() { return $this->execute(DriverCommand::GET_CURRENT_URL); } public function getPageSource() { return $this->execute(DriverCommand::GET_PAGE_SOURCE); } public function getTitle() { return $this->execute(DriverCommand::GET_TITLE); } public function getWindowHandle() { return $this->execute( DriverCommand::GET_CURRENT_WINDOW_HANDLE, [] ); } public function getWindowHandles() { return $this->execute(DriverCommand::GET_WINDOW_HANDLES, []); } public function quit() { $this->execute(DriverCommand::QUIT); $this->executor = null; } private function prepareScriptArguments(array $arguments) { $args = []; foreach ($arguments as $key => $value) { if ($value instanceof WebDriverElement) { $args[$key] = ['ELEMENT' => $value->getID()]; } else { if (is_array($value)) { $value = $this->prepareScriptArguments($value); } $args[$key] = $value; } } return $args; } public function executeScript($script, array $arguments = []) { $params = [ 'script' => $script, 'args' => $this->prepareScriptArguments($arguments), ]; return $this->execute(DriverCommand::EXECUTE_SCRIPT, $params); } public function executeAsyncScript($script, array $arguments = []) { $params = [ 'script' => $script, 'args' => $this->prepareScriptArguments($arguments), ]; return $this->execute( DriverCommand::EXECUTE_ASYNC_SCRIPT, $params ); } public function takeScreenshot($save_as = null) { $screenshot = base64_decode( $this->execute(DriverCommand::SCREENSHOT) ); if ($save_as) { file_put_contents($save_as, $screenshot); } return $screenshot; } public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) { return new WebDriverWait( $this, $timeout_in_second, $interval_in_millisecond ); } public function manage() { return new WebDriverOptions($this->getExecuteMethod()); } public function navigate() { return new WebDriverNavigation($this->getExecuteMethod()); } public function switchTo() { return new RemoteTargetLocator($this->getExecuteMethod(), $this); } public function getMouse() { if (!$this->mouse) { $this->mouse = new RemoteMouse($this->getExecuteMethod()); } return $this->mouse; } public function getKeyboard() { if (!$this->keyboard) { $this->keyboard = new RemoteKeyboard($this->getExecuteMethod()); } return $this->keyboard; } public function getTouch() { if (!$this->touch) { $this->touch = new RemoteTouchScreen($this->getExecuteMethod()); } return $this->touch; } protected function getExecuteMethod() { if (!$this->executeMethod) { $this->executeMethod = new RemoteExecuteMethod($this); } return $this->executeMethod; } public function action() { return new WebDriverActions($this); } protected function newElement($id) { return new RemoteWebElement($this->getExecuteMethod(), $id); } public function setCommandExecutor(WebDriverCommandExecutor $executor) { $this->executor = $executor; return $this; } public function getCommandExecutor() { return $this->executor; } public function setSessionID($session_id) { $this->sessionID = $session_id; return $this; } public function getSessionID() { return $this->sessionID; } public function getCapabilities() { return $this->capabilities; } public static function getAllSessions($selenium_server_url = 'http://localhost:4444/wd/hub', $timeout_in_ms = 30000) { $executor = new HttpCommandExecutor($selenium_server_url); $executor->setConnectionTimeout($timeout_in_ms); $command = new WebDriverCommand( null, DriverCommand::GET_ALL_SESSIONS, [] ); return $executor->execute($command)->getValue(); } public function execute($command_name, $params = []) { $command = new WebDriverCommand( $this->sessionID, $command_name, $params ); if ($this->executor) { $response = $this->executor->execute($command); return $response->getValue(); } return null; } } driver = $driver; } public function execute($command_name, array $parameters = []) { return $this->driver->execute($command_name, $parameters); } } executable = self::checkExecutable($executable); $this->url = sprintf('http://localhost:%d', $port); $this->args = $args; $this->environment = $environment ?: $_ENV; } public function getURL() { return $this->url; } public function start() { if ($this->process !== null) { return $this; } $processBuilder = (new ProcessBuilder()) ->setPrefix($this->executable) ->setArguments($this->args) ->addEnvironmentVariables($this->environment); $this->process = $processBuilder->getProcess(); $this->process->start(); $checker = new URLChecker(); $checker->waitUntilAvailable(20 * 1000, $this->url . '/status'); return $this; } public function stop() { if ($this->process === null) { return $this; } $this->process->stop(); $this->process = null; $checker = new URLChecker(); $checker->waitUntilUnavailable(3 * 1000, $this->url . '/shutdown'); return $this; } public function isRunning() { if ($this->process === null) { return false; } return $this->process->isRunning(); } protected static function checkExecutable($executable) { if (!is_file($executable)) { throw new Exception("'$executable' is not a file."); } if (!is_executable($executable)) { throw new Exception("'$executable' is not executable."); } return $executable; } } getURL()); $this->service = $service; } public function execute(WebDriverCommand $command) { if ($command->getName() === DriverCommand::NEW_SESSION) { $this->service->start(); } try { $value = parent::execute($command); if ($command->getName() === DriverCommand::QUIT) { $this->service->stop(); } return $value; } catch (\Exception $e) { if (!$this->service->isRunning()) { throw new WebDriverException('The driver server has died.'); } throw $e; } } } ['method' => 'POST', 'url' => '/session/:sessionId/accept_alert'], DriverCommand::ADD_COOKIE => ['method' => 'POST', 'url' => '/session/:sessionId/cookie'], DriverCommand::CLEAR_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/clear'], DriverCommand::CLICK_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/click'], DriverCommand::CLOSE => ['method' => 'DELETE', 'url' => '/session/:sessionId/window'], DriverCommand::DELETE_ALL_COOKIES => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie'], DriverCommand::DELETE_COOKIE => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie/:name'], DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/dismiss_alert'], DriverCommand::ELEMENT_EQUALS => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/equals/:other'], DriverCommand::FIND_CHILD_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/element'], DriverCommand::FIND_CHILD_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/elements'], DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute'], DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute_async'], DriverCommand::FIND_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element'], DriverCommand::FIND_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/elements'], DriverCommand::SWITCH_TO_FRAME => ['method' => 'POST', 'url' => '/session/:sessionId/frame'], DriverCommand::SWITCH_TO_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window'], DriverCommand::GET => ['method' => 'POST', 'url' => '/session/:sessionId/url'], DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/active'], DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert_text'], DriverCommand::GET_ALL_COOKIES => ['method' => 'GET', 'url' => '/session/:sessionId/cookie'], DriverCommand::GET_ALL_SESSIONS => ['method' => 'GET', 'url' => '/sessions'], DriverCommand::GET_AVAILABLE_LOG_TYPES => ['method' => 'GET', 'url' => '/session/:sessionId/log/types'], DriverCommand::GET_CURRENT_URL => ['method' => 'GET', 'url' => '/session/:sessionId/url'], DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window_handle'], DriverCommand::GET_ELEMENT_ATTRIBUTE => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/attribute/:name', ], DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/css/:propertyName', ], DriverCommand::GET_ELEMENT_LOCATION => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/location', ], DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/location_in_view', ], DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/size'], DriverCommand::GET_ELEMENT_TAG_NAME => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/name'], DriverCommand::GET_ELEMENT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/text'], DriverCommand::GET_LOG => ['method' => 'POST', 'url' => '/session/:sessionId/log'], DriverCommand::GET_PAGE_SOURCE => ['method' => 'GET', 'url' => '/session/:sessionId/source'], DriverCommand::GET_SCREEN_ORIENTATION => ['method' => 'GET', 'url' => '/session/:sessionId/orientation'], DriverCommand::GET_CAPABILITIES => ['method' => 'GET', 'url' => '/session/:sessionId'], DriverCommand::GET_TITLE => ['method' => 'GET', 'url' => '/session/:sessionId/title'], DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window_handles'], DriverCommand::GET_WINDOW_POSITION => [ 'method' => 'GET', 'url' => '/session/:sessionId/window/:windowHandle/position', ], DriverCommand::GET_WINDOW_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/window/:windowHandle/size'], DriverCommand::GO_BACK => ['method' => 'POST', 'url' => '/session/:sessionId/back'], DriverCommand::GO_FORWARD => ['method' => 'POST', 'url' => '/session/:sessionId/forward'], DriverCommand::IS_ELEMENT_DISPLAYED => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/displayed', ], DriverCommand::IS_ELEMENT_ENABLED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/enabled'], DriverCommand::IS_ELEMENT_SELECTED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/selected'], DriverCommand::MAXIMIZE_WINDOW => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/maximize', ], DriverCommand::MOUSE_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/buttondown'], DriverCommand::MOUSE_UP => ['method' => 'POST', 'url' => '/session/:sessionId/buttonup'], DriverCommand::CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/click'], DriverCommand::DOUBLE_CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/doubleclick'], DriverCommand::MOVE_TO => ['method' => 'POST', 'url' => '/session/:sessionId/moveto'], DriverCommand::NEW_SESSION => ['method' => 'POST', 'url' => '/session'], DriverCommand::QUIT => ['method' => 'DELETE', 'url' => '/session/:sessionId'], DriverCommand::REFRESH => ['method' => 'POST', 'url' => '/session/:sessionId/refresh'], DriverCommand::UPLOAD_FILE => ['method' => 'POST', 'url' => '/session/:sessionId/file'], DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/keys'], DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert_text'], DriverCommand::SEND_KEYS_TO_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/value'], DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/implicit_wait'], DriverCommand::SET_SCREEN_ORIENTATION => ['method' => 'POST', 'url' => '/session/:sessionId/orientation'], DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/async_script'], DriverCommand::SET_WINDOW_POSITION => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/position', ], DriverCommand::SET_WINDOW_SIZE => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/size', ], DriverCommand::SUBMIT_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/submit'], DriverCommand::SCREENSHOT => ['method' => 'GET', 'url' => '/session/:sessionId/screenshot'], DriverCommand::TOUCH_SINGLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/click'], DriverCommand::TOUCH_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/touch/down'], DriverCommand::TOUCH_DOUBLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/doubleclick'], DriverCommand::TOUCH_FLICK => ['method' => 'POST', 'url' => '/session/:sessionId/touch/flick'], DriverCommand::TOUCH_LONG_PRESS => ['method' => 'POST', 'url' => '/session/:sessionId/touch/longclick'], DriverCommand::TOUCH_MOVE => ['method' => 'POST', 'url' => '/session/:sessionId/touch/move'], DriverCommand::TOUCH_SCROLL => ['method' => 'POST', 'url' => '/session/:sessionId/touch/scroll'], DriverCommand::TOUCH_UP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/up'], ]; protected $url; protected $curl; public function __construct($url, $http_proxy = null, $http_proxy_port = null) { $this->url = $url; $this->curl = curl_init(); if (!empty($http_proxy)) { curl_setopt($this->curl, CURLOPT_PROXY, $http_proxy); if (!empty($http_proxy_port)) { curl_setopt($this->curl, CURLOPT_PROXYPORT, $http_proxy_port); } } $matches = null; if (preg_match("/^(https?:\/\/)(.*):(.*)@(.*?)/U", $url, $matches)) { $this->url = $matches[1] . $matches[4]; $auth_creds = $matches[2] . ':' . $matches[3]; curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_setopt($this->curl, CURLOPT_USERPWD, $auth_creds); } curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt( $this->curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json;charset=UTF-8', 'Accept: application/json', ] ); $this->setRequestTimeout(30000); $this->setConnectionTimeout(30000); } public function setConnectionTimeout($timeout_in_ms) { curl_setopt( $this->curl, 156, $timeout_in_ms ); return $this; } public function setRequestTimeout($timeout_in_ms) { curl_setopt( $this->curl, 155, $timeout_in_ms ); return $this; } public function execute(WebDriverCommand $command) { if (!isset(self::$commands[$command->getName()])) { throw new InvalidArgumentException($command->getName() . ' is not a valid command.'); } $raw = self::$commands[$command->getName()]; $http_method = $raw['method']; $url = $raw['url']; $url = str_replace(':sessionId', $command->getSessionID(), $url); $params = $command->getParameters(); foreach ($params as $name => $value) { if ($name[0] === ':') { $url = str_replace($name, $value, $url); unset($params[$name]); } } if ($params && is_array($params) && $http_method !== 'POST') { throw new BadMethodCallException(sprintf( 'The http method called for %s is %s but it has to be POST' . ' if you want to pass the JSON params %s', $url, $http_method, json_encode($params) )); } curl_setopt($this->curl, CURLOPT_URL, $this->url . $url); if ($command->getName() === DriverCommand::NEW_SESSION) { curl_setopt($this->curl, CURLOPT_POST, 1); } else { curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); } $encoded_params = null; if ($http_method === 'POST' && $params && is_array($params)) { $encoded_params = json_encode($params); } curl_setopt($this->curl, CURLOPT_POSTFIELDS, $encoded_params); $raw_results = trim(curl_exec($this->curl)); if ($error = curl_error($this->curl)) { $msg = sprintf( 'Curl error thrown for http %s to %s', $http_method, $url ); if ($params && is_array($params)) { $msg .= sprintf(' with params: %s', json_encode($params)); } throw new WebDriverCurlException($msg . "\n\n" . $error); } $results = json_decode($raw_results, true); if ($results === null && json_last_error() !== JSON_ERROR_NONE) { throw new WebDriverException( sprintf( "JSON decoding of remote response failed.\n" . "Error code: %d\n" . "The response: '%s'\n", json_last_error(), $raw_results ) ); } $value = null; if (is_array($results) && array_key_exists('value', $results)) { $value = $results['value']; } $message = null; if (is_array($value) && array_key_exists('message', $value)) { $message = $value['message']; } $sessionId = null; if (is_array($results) && array_key_exists('sessionId', $results)) { $sessionId = $results['sessionId']; } $status = isset($results['status']) ? $results['status'] : 0; if ($status != 0) { WebDriverException::throwException($status, $message, $results); } $response = new WebDriverResponse($sessionId); return $response ->setStatus($status) ->setValue($value); } public function getAddressOfRemoteServer() { return $this->url; } } sessionID = $session_id; } public function getStatus() { return $this->status; } public function setStatus($status) { $this->status = $status; return $this; } public function getValue() { return $this->value; } public function setValue($value) { $this->value = $value; return $this; } public function getSessionID() { return $this->sessionID; } public function setSessionID($session_id) { $this->sessionID = $session_id; return $this; } } executor = $executor; } public function sendKeys($keys) { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => WebDriverKeys::encode($keys), ]); return $this; } public function pressKey($key) { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => [(string) $key], ]); return $this; } public function releaseKey($key) { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => [(string) $key], ]); return $this; } } sessionID = $session_id; $this->name = $name; $this->parameters = $parameters; } public function getName() { return $this->name; } public function getSessionID() { return $this->sessionID; } public function getParameters() { return $this->parameters; } } executor = $executor; $this->id = $id; $this->fileDetector = new UselessFileDetector(); } public function clear() { $this->executor->execute( DriverCommand::CLEAR_ELEMENT, [':id' => $this->id] ); return $this; } public function click() { $this->executor->execute( DriverCommand::CLICK_ELEMENT, [':id' => $this->id] ); return $this; } public function findElement(WebDriverBy $by) { $params = [ 'using' => $by->getMechanism(), 'value' => $by->getValue(), ':id' => $this->id, ]; $raw_element = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENT, $params ); return $this->newElement($raw_element['ELEMENT']); } public function findElements(WebDriverBy $by) { $params = [ 'using' => $by->getMechanism(), 'value' => $by->getValue(), ':id' => $this->id, ]; $raw_elements = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENTS, $params ); $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement($raw_element['ELEMENT']); } return $elements; } public function getAttribute($attribute_name) { $params = [ ':name' => $attribute_name, ':id' => $this->id, ]; return $this->executor->execute( DriverCommand::GET_ELEMENT_ATTRIBUTE, $params ); } public function getCSSValue($css_property_name) { $params = [ ':propertyName' => $css_property_name, ':id' => $this->id, ]; return $this->executor->execute( DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY, $params ); } public function getLocation() { $location = $this->executor->execute( DriverCommand::GET_ELEMENT_LOCATION, [':id' => $this->id] ); return new WebDriverPoint($location['x'], $location['y']); } public function getLocationOnScreenOnceScrolledIntoView() { $location = $this->executor->execute( DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW, [':id' => $this->id] ); return new WebDriverPoint($location['x'], $location['y']); } public function getCoordinates() { $element = $this; $on_screen = null; $in_view_port = function () use ($element) { return $element->getLocationOnScreenOnceScrolledIntoView(); }; $on_page = function () use ($element) { return $element->getLocation(); }; $auxiliary = $this->getID(); return new WebDriverCoordinates( $on_screen, $in_view_port, $on_page, $auxiliary ); } public function getSize() { $size = $this->executor->execute( DriverCommand::GET_ELEMENT_SIZE, [':id' => $this->id] ); return new WebDriverDimension($size['width'], $size['height']); } public function getTagName() { return strtolower($this->executor->execute( DriverCommand::GET_ELEMENT_TAG_NAME, [':id' => $this->id] )); } public function getText() { return $this->executor->execute( DriverCommand::GET_ELEMENT_TEXT, [':id' => $this->id] ); } public function isDisplayed() { return $this->executor->execute( DriverCommand::IS_ELEMENT_DISPLAYED, [':id' => $this->id] ); } public function isEnabled() { return $this->executor->execute( DriverCommand::IS_ELEMENT_ENABLED, [':id' => $this->id] ); } public function isSelected() { return $this->executor->execute( DriverCommand::IS_ELEMENT_SELECTED, [':id' => $this->id] ); } public function sendKeys($value) { $local_file = $this->fileDetector->getLocalFile($value); if ($local_file === null) { $params = [ 'value' => WebDriverKeys::encode($value), ':id' => $this->id, ]; $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); } else { $remote_path = $this->upload($local_file); $params = [ 'value' => WebDriverKeys::encode($remote_path), ':id' => $this->id, ]; $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); } return $this; } private function upload($local_file) { if (!is_file($local_file)) { throw new WebDriverException('You may only upload files: ' . $local_file); } $temp_zip = tempnam(sys_get_temp_dir(), 'WebDriverZip'); $zip = new ZipArchive(); if ($zip->open($temp_zip, ZipArchive::CREATE) !== true) { return false; } $info = pathinfo($local_file); $file_name = $info['basename']; $zip->addFile($local_file, $file_name); $zip->close(); $params = [ 'file' => base64_encode(file_get_contents($temp_zip)), ]; $remote_path = $this->executor->execute( DriverCommand::UPLOAD_FILE, $params ); unlink($temp_zip); return $remote_path; } public function setFileDetector(FileDetector $detector) { $this->fileDetector = $detector; return $this; } public function submit() { $this->executor->execute( DriverCommand::SUBMIT_ELEMENT, [':id' => $this->id] ); return $this; } public function getID() { return $this->id; } public function equals(WebDriverElement $other) { return $this->executor->execute(DriverCommand::ELEMENT_EQUALS, [ ':id' => $this->id, ':other' => $other->getID(), ]); } protected function newElement($id) { return new static($this->executor, $id); } } executor = $executor; } public function tap(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_SINGLE_TAP, ['element' => $element->getID()] ); return $this; } public function doubleTap(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_DOUBLE_TAP, ['element' => $element->getID()] ); return $this; } public function down($x, $y) { $this->executor->execute(DriverCommand::TOUCH_DOWN, [ 'x' => $x, 'y' => $y, ]); return $this; } public function flick($xspeed, $yspeed) { $this->executor->execute(DriverCommand::TOUCH_FLICK, [ 'xspeed' => $xspeed, 'yspeed' => $yspeed, ]); return $this; } public function flickFromElement(WebDriverElement $element, $xoffset, $yoffset, $speed) { $this->executor->execute(DriverCommand::TOUCH_FLICK, [ 'xoffset' => $xoffset, 'yoffset' => $yoffset, 'element' => $element->getID(), 'speed' => $speed, ]); return $this; } public function longPress(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_LONG_PRESS, ['element' => $element->getID()] ); return $this; } public function move($x, $y) { $this->executor->execute(DriverCommand::TOUCH_MOVE, [ 'x' => $x, 'y' => $y, ]); return $this; } public function scroll($xoffset, $yoffset) { $this->executor->execute(DriverCommand::TOUCH_SCROLL, [ 'xoffset' => $xoffset, 'yoffset' => $yoffset, ]); return $this; } public function scrollFromElement(WebDriverElement $element, $xoffset, $yoffset) { $this->executor->execute(DriverCommand::TOUCH_SCROLL, [ 'element' => $element->getID(), 'xoffset' => $xoffset, 'yoffset' => $yoffset, ]); return $this; } public function up($x, $y) { $this->executor->execute(DriverCommand::TOUCH_UP, [ 'x' => $x, 'y' => $y, ]); return $this; } } binary = $path; return $this; } public function addArguments(array $arguments) { $this->arguments = array_merge($this->arguments, $arguments); return $this; } public function addExtensions(array $paths) { foreach ($paths as $path) { $this->addExtension($path); } return $this; } public function addEncodedExtensions(array $encoded_extensions) { foreach ($encoded_extensions as $encoded_extension) { $this->addEncodedExtension($encoded_extension); } return $this; } public function setExperimentalOption($name, $value) { $this->experimentalOptions[$name] = $value; return $this; } public function toCapabilities() { $capabilities = DesiredCapabilities::chrome(); $capabilities->setCapability(self::CAPABILITY, $this); return $capabilities; } public function toArray() { $options = $this->experimentalOptions; $options['binary'] = $this->binary; if ($this->arguments) { $options['args'] = $this->arguments; } if ($this->extensions) { $options['extensions'] = $this->extensions; } return $options; } private function addExtension($path) { $this->addEncodedExtension(base64_encode(file_get_contents($path))); return $this; } private function addEncodedExtension($encoded_extension) { $this->extensions[] = $encoded_extension; return $this; } } startSession($desired_capabilities); return $driver; } public function startSession(DesiredCapabilities $desired_capabilities) { $command = new WebDriverCommand( null, DriverCommand::NEW_SESSION, [ 'desiredCapabilities' => $desired_capabilities->toArray(), ] ); $response = $this->executor->execute($command); $this->sessionID = $response->getSessionID(); } public static function create( $selenium_server_url = 'http://localhost:4444/wd/hub', $desired_capabilities = null, $connection_timeout_in_ms = null, $request_timeout_in_ms = null, $http_proxy = null, $http_proxy_port = null, DesiredCapabilities $required_capabilities = null ) { throw new WebDriverException('Please use ChromeDriver::start() instead.'); } public static function createBySessionID( $session_id, $selenium_server_url = 'http://localhost:4444/wd/hub' ) { throw new WebDriverException('Please use ChromeDriver::start() instead.'); } } extensions[] = $extension; return $this; } public function addExtensionDatas($extension_datas) { if (!is_dir($extension_datas)) { return null; } $this->extensions_datas[basename($extension_datas)] = $extension_datas; return $this; } public function setRdfFile($rdf_file) { if (!is_file($rdf_file)) { return null; } $this->rdf_file = $rdf_file; return $this; } public function setPreference($key, $value) { if (is_string($value)) { $value = sprintf('"%s"', $value); } else { if (is_int($value)) { $value = sprintf('%d', $value); } else { if (is_bool($value)) { $value = $value ? 'true' : 'false'; } else { throw new WebDriverException( 'The value of the preference should be either a string, int or bool.' ); } } } $this->preferences[$key] = $value; return $this; } public function getPreference($key) { if (array_key_exists($key, $this->preferences)) { return $this->preferences[$key]; } return null; } public function encode() { $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfile'); if (isset($this->rdf_file)) { copy($this->rdf_file, $temp_dir . DIRECTORY_SEPARATOR . 'mimeTypes.rdf'); } foreach ($this->extensions as $extension) { $this->installExtension($extension, $temp_dir); } foreach ($this->extensions_datas as $dirname => $extension_datas) { mkdir($temp_dir . DIRECTORY_SEPARATOR . $dirname); $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($extension_datas, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { $target_dir = $temp_dir . DIRECTORY_SEPARATOR . $dirname . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); if ($item->isDir()) { mkdir($target_dir); } else { copy($item, $target_dir); } } } $content = ''; foreach ($this->preferences as $key => $value) { $content .= sprintf("user_pref(\"%s\", %s);\n", $key, $value); } file_put_contents($temp_dir . '/user.js', $content); $zip = new ZipArchive(); $temp_zip = tempnam(sys_get_temp_dir(), 'WebDriverFirefoxProfileZip'); $zip->open($temp_zip, ZipArchive::CREATE); $dir = new RecursiveDirectoryIterator($temp_dir); $files = new RecursiveIteratorIterator($dir); $dir_prefix = preg_replace( '#\\\\#', '\\\\\\\\', $temp_dir . DIRECTORY_SEPARATOR ); foreach ($files as $name => $object) { if (is_dir($name)) { continue; } $path = preg_replace("#^{$dir_prefix}#", '', $name); $zip->addFile($name, $path); } $zip->close(); $profile = base64_encode(file_get_contents($temp_zip)); $this->deleteDirectory($temp_dir); unlink($temp_zip); return $profile; } private function installExtension($extension, $profile_dir) { $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfileExtension'); $this->extractTo($extension, $temp_dir); $install_rdf_path = $temp_dir . '/install.rdf'; $xml = simplexml_load_file($install_rdf_path); $ns = $xml->getDocNamespaces(); $prefix = ''; if (!empty($ns)) { foreach ($ns as $key => $value) { if (strpos($value, '//www.mozilla.org/2004/em-rdf') > 0) { if ($key != '') { $prefix = $key . ':'; } break; } } } $matches = []; preg_match('#<' . $prefix . 'id>([^<]+)#', $xml->asXML(), $matches); if (isset($matches[1])) { $ext_dir = $profile_dir . '/extensions/' . $matches[1]; mkdir($ext_dir, 0777, true); $this->extractTo($extension, $ext_dir); } else { $this->deleteDirectory($temp_dir); throw new WebDriverException('Cannot get the extension id from the install manifest.'); } $this->deleteDirectory($temp_dir); return $ext_dir; } private function createTempDirectory($prefix = '') { $temp_dir = tempnam(sys_get_temp_dir(), $prefix); if (file_exists($temp_dir)) { unlink($temp_dir); mkdir($temp_dir); if (!is_dir($temp_dir)) { throw new WebDriverException('Cannot create firefox profile.'); } } return $temp_dir; } private function deleteDirectory($directory) { $dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS); $paths = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST); foreach ($paths as $path) { if ($path->isDir() && !$path->isLink()) { rmdir($path->getPathname()); } else { unlink($path->getPathname()); } } rmdir($directory); } private function extractTo($xpi, $target_dir) { $zip = new ZipArchive(); if (file_exists($xpi)) { if ($zip->open($xpi)) { $zip->extractTo($target_dir); $zip->close(); } else { throw new \Exception("Failed to open the firefox extension. '$xpi'"); } } else { throw new \Exception("Firefox extension doesn't exist. '$xpi'"); } return $this; } } dispatcher = $dispatcher ?: new WebDriverDispatcher(); if (!$this->dispatcher->getDefaultDriver()) { $this->dispatcher->setDefaultDriver($this); } $this->driver = $driver; } public function getDispatcher() { return $this->dispatcher; } protected function dispatch($method) { if (!$this->dispatcher) { return; } $arguments = func_get_args(); unset($arguments[0]); $this->dispatcher->dispatch($method, $arguments); } public function getWebDriver() { return $this->driver; } protected function newElement(WebDriverElement $element) { return new EventFiringWebElement($element, $this->getDispatcher()); } public function get($url) { $this->dispatch('beforeNavigateTo', $url, $this); try { $this->driver->get($url); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterNavigateTo', $url, $this); return $this; } public function findElements(WebDriverBy $by) { $this->dispatch('beforeFindBy', $by, null, $this); $elements = []; try { foreach ($this->driver->findElements($by) as $element) { $elements[] = $this->newElement($element); } } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterFindBy', $by, null, $this); return $elements; } public function findElement(WebDriverBy $by) { $this->dispatch('beforeFindBy', $by, null, $this); try { $element = $this->newElement($this->driver->findElement($by)); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterFindBy', $by, null, $this); return $element; } public function executeScript($script, array $arguments = []) { if (!$this->driver instanceof JavaScriptExecutor) { throw new UnsupportedOperationException( 'driver does not implement JavaScriptExecutor' ); } $this->dispatch('beforeScript', $script, $this); try { $result = $this->driver->executeScript($script, $arguments); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterScript', $script, $this); return $result; } public function executeAsyncScript($script, array $arguments = []) { if (!$this->driver instanceof JavaScriptExecutor) { throw new UnsupportedOperationException( 'driver does not implement JavaScriptExecutor' ); } $this->dispatch('beforeScript', $script, $this); try { $result = $this->driver->executeAsyncScript($script, $arguments); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterScript', $script, $this); return $result; } public function close() { try { $this->driver->close(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getCurrentURL() { try { return $this->driver->getCurrentURL(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getPageSource() { try { return $this->driver->getPageSource(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getTitle() { try { return $this->driver->getTitle(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getWindowHandle() { try { return $this->driver->getWindowHandle(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getWindowHandles() { try { return $this->driver->getWindowHandles(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function quit() { try { $this->driver->quit(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function takeScreenshot($save_as = null) { try { return $this->driver->takeScreenshot($save_as); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) { try { return $this->driver->wait($timeout_in_second, $interval_in_millisecond); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function manage() { try { return $this->driver->manage(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function navigate() { try { return new EventFiringWebDriverNavigation( $this->driver->navigate(), $this->getDispatcher() ); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function switchTo() { try { return $this->driver->switchTo(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getTouch() { try { return $this->driver->getTouch(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } private function dispatchOnException(WebDriverException $exception) { $this->dispatch('onException', $exception, $this); } public function execute($name, $params) { try { return $this->driver->execute($name, $params); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } } element = $element; $this->dispatcher = $dispatcher; } public function getDispatcher() { return $this->dispatcher; } protected function dispatch($method) { if (!$this->dispatcher) { return; } $arguments = func_get_args(); unset($arguments[0]); $this->dispatcher->dispatch($method, $arguments); } public function getElement() { return $this->element; } protected function newElement(WebDriverElement $element) { return new static($element, $this->getDispatcher()); } public function sendKeys($value) { $this->dispatch('beforeChangeValueOf', $this); try { $this->element->sendKeys($value); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterChangeValueOf', $this); return $this; } public function click() { $this->dispatch('beforeClickOn', $this); try { $this->element->click(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterClickOn', $this); return $this; } public function findElement(WebDriverBy $by) { $this->dispatch( 'beforeFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); try { $element = $this->newElement($this->element->findElement($by)); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); return $element; } public function findElements(WebDriverBy $by) { $this->dispatch( 'beforeFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); try { $elements = []; foreach ($this->element->findElements($by) as $element) { $elements[] = $this->newElement($element); } } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); return $elements; } public function clear() { try { $this->element->clear(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getAttribute($attribute_name) { try { return $this->element->getAttribute($attribute_name); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getCSSValue($css_property_name) { try { return $this->element->getCSSValue($css_property_name); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getLocation() { try { return $this->element->getLocation(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getLocationOnScreenOnceScrolledIntoView() { try { return $this->element->getLocationOnScreenOnceScrolledIntoView(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getCoordinates() { try { return $this->element->getCoordinates(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getSize() { try { return $this->element->getSize(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getTagName() { try { return $this->element->getTagName(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getText() { try { return $this->element->getText(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function isDisplayed() { try { return $this->element->isDisplayed(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function isEnabled() { try { return $this->element->isEnabled(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function isSelected() { try { return $this->element->isSelected(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function submit() { try { $this->element->submit(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getID() { try { return $this->element->getID(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function equals(WebDriverElement $other) { try { return $this->element->equals($other); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } private function dispatchOnException(WebDriverException $exception) { $this->dispatch( 'onException', $exception, $this->dispatcher->getDefaultDriver() ); } } navigator = $navigator; $this->dispatcher = $dispatcher; return $this; } public function getDispatcher() { return $this->dispatcher; } protected function dispatch($method) { if (!$this->dispatcher) { return; } $arguments = func_get_args(); unset($arguments[0]); $this->dispatcher->dispatch($method, $arguments); } public function getNavigator() { return $this->navigator; } public function back() { $this->dispatch( 'beforeNavigateBack', $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->back(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } $this->dispatch( 'afterNavigateBack', $this->getDispatcher()->getDefaultDriver() ); return $this; } public function forward() { $this->dispatch( 'beforeNavigateForward', $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->forward(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } $this->dispatch( 'afterNavigateForward', $this->getDispatcher()->getDefaultDriver() ); return $this; } public function refresh() { try { $this->navigator->refresh(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } } public function to($url) { $this->dispatch( 'beforeNavigateTo', $url, $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->to($url); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } $this->dispatch( 'afterNavigateTo', $url, $this->getDispatcher()->getDefaultDriver() ); return $this; } private function dispatchOnException($exception) { $this->dispatch('onException', $exception); throw $exception; } } executor = $executor; } public function accept() { $this->executor->execute(DriverCommand::ACCEPT_ALERT); return $this; } public function dismiss() { $this->executor->execute(DriverCommand::DISMISS_ALERT); return $this; } public function getText() { return $this->executor->execute(DriverCommand::GET_ALERT_TEXT); } public function sendKeys($value) { $this->executor->execute( DriverCommand::SET_ALERT_VALUE, ['text' => $value] ); return $this; } } executor = $executor; } public function implicitlyWait($seconds) { $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, ['ms' => $seconds * 1000] ); return $this; } public function setScriptTimeout($seconds) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['ms' => $seconds * 1000] ); return $this; } public function pageLoadTimeout($seconds) { $this->executor->execute(DriverCommand::SET_TIMEOUT, [ 'type' => 'page load', 'ms' => $seconds * 1000, ]); return $this; } } executor = $executor; } public function back() { $this->executor->execute(DriverCommand::GO_BACK); return $this; } public function forward() { $this->executor->execute(DriverCommand::GO_FORWARD); return $this; } public function refresh() { $this->executor->execute(DriverCommand::REFRESH); return $this; } public function to($url) { $params = ['url' => (string) $url]; $this->executor->execute(DriverCommand::GET, $params); return $this; } } executor = $executor; } public function getPosition() { $position = $this->executor->execute( DriverCommand::GET_WINDOW_POSITION, [':windowHandle' => 'current'] ); return new WebDriverPoint( $position['x'], $position['y'] ); } public function getSize() { $size = $this->executor->execute( DriverCommand::GET_WINDOW_SIZE, [':windowHandle' => 'current'] ); return new WebDriverDimension( $size['width'], $size['height'] ); } public function maximize() { $this->executor->execute( DriverCommand::MAXIMIZE_WINDOW, [':windowHandle' => 'current'] ); return $this; } public function setSize(WebDriverDimension $size) { $params = [ 'width' => $size->getWidth(), 'height' => $size->getHeight(), ':windowHandle' => 'current', ]; $this->executor->execute(DriverCommand::SET_WINDOW_SIZE, $params); return $this; } public function setPosition(WebDriverPoint $position) { $params = [ 'x' => $position->getX(), 'y' => $position->getY(), ':windowHandle' => 'current', ]; $this->executor->execute(DriverCommand::SET_WINDOW_POSITION, $params); return $this; } public function getScreenOrientation() { return $this->executor->execute(DriverCommand::GET_SCREEN_ORIENTATION); } public function setScreenOrientation($orientation) { $orientation = strtoupper($orientation); if (!in_array($orientation, ['PORTRAIT', 'LANDSCAPE'])) { throw new IndexOutOfBoundsException( 'Orientation must be either PORTRAIT, or LANDSCAPE' ); } $this->executor->execute( DriverCommand::SET_SCREEN_ORIENTATION, ['orientation' => $orientation] ); return $this; } } executor = $executor; } public function addCookie(array $cookie) { $this->validate($cookie); $this->executor->execute( DriverCommand::ADD_COOKIE, ['cookie' => $cookie] ); return $this; } public function deleteAllCookies() { $this->executor->execute(DriverCommand::DELETE_ALL_COOKIES); return $this; } public function deleteCookieNamed($name) { $this->executor->execute( DriverCommand::DELETE_COOKIE, [':name' => $name] ); return $this; } public function getCookieNamed($name) { $cookies = $this->getCookies(); foreach ($cookies as $cookie) { if ($cookie['name'] === $name) { return $cookie; } } return null; } public function getCookies() { return $this->executor->execute(DriverCommand::GET_ALL_COOKIES); } private function validate(array $cookie) { if (!isset($cookie['name']) || $cookie['name'] === '' || strpos($cookie['name'], ';') !== false ) { throw new InvalidArgumentException( '"name" should be non-empty and does not contain a ";"' ); } if (!isset($cookie['value'])) { throw new InvalidArgumentException( '"value" is required when setting a cookie.' ); } if (isset($cookie['domain']) && strpos($cookie['domain'], ':') !== false) { throw new InvalidArgumentException( '"domain" should not contain a port:' . (string) $cookie['domain'] ); } } public function timeouts() { return new WebDriverTimeouts($this->executor); } public function window() { return new WebDriverWindow($this->executor); } public function getLog($log_type) { return $this->executor->execute( DriverCommand::GET_LOG, ['type' => $log_type] ); } public function getAvailableLogTypes() { return $this->executor->execute(DriverCommand::GET_AVAILABLE_LOG_TYPES); } } getTagName(); if ($tag_name !== 'select') { throw new UnexpectedTagNameException('select', $tag_name); } $this->element = $element; $value = $element->getAttribute('multiple'); $this->isMulti = ($value === 'true'); } public function isMultiple() { return $this->isMulti; } public function getOptions() { return $this->element->findElements(WebDriverBy::tagName('option')); } public function getAllSelectedOptions() { $selected_options = []; foreach ($this->getOptions() as $option) { if ($option->isSelected()) { $selected_options[] = $option; if (!$this->isMultiple()) { return $selected_options; } } } return $selected_options; } public function getFirstSelectedOption() { foreach ($this->getOptions() as $option) { if ($option->isSelected()) { return $option; } } throw new NoSuchElementException('No options are selected'); } public function selectByIndex($index) { foreach ($this->getOptions() as $option) { if ($option->getAttribute('index') === (string) $index) { $this->selectOption($option); return; } } throw new NoSuchElementException(sprintf('Cannot locate option with index: %d', $index)); } public function selectByValue($value) { $matched = false; $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with value: %s', $value) ); } } public function selectByVisibleText($text) { $matched = false; $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { foreach ($this->getOptions() as $option) { if ($option->getText() === $text) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } } } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with text: %s', $text) ); } } public function selectByVisiblePartialText($text) { $matched = false; $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with text: %s', $text) ); } } public function deselectAll() { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect all options of a multi-select'); } foreach ($this->getOptions() as $option) { $this->deselectOption($option); } } public function deselectByIndex($index) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } foreach ($this->getOptions() as $option) { if ($option->getAttribute('index') === (string) $index) { $this->deselectOption($option); return; } } } public function deselectByValue($value) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } public function deselectByVisibleText($text) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } public function deselectByVisiblePartialText($text) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } protected function selectOption(WebDriverElement $option) { if (!$option->isSelected()) { $option->click(); } } protected function deselectOption(WebDriverElement $option) { if ($option->isSelected()) { $option->click(); } } } results = $results; } public function getResults() { return $this->results; } public static function throwException($status_code, $message, $results) { switch ($status_code) { case 1: throw new IndexOutOfBoundsException($message, $results); case 2: throw new NoCollectionException($message, $results); case 3: throw new NoStringException($message, $results); case 4: throw new NoStringLengthException($message, $results); case 5: throw new NoStringWrapperException($message, $results); case 6: throw new NoSuchDriverException($message, $results); case 7: throw new NoSuchElementException($message, $results); case 8: throw new NoSuchFrameException($message, $results); case 9: throw new UnknownCommandException($message, $results); case 10: throw new StaleElementReferenceException($message, $results); case 11: throw new ElementNotVisibleException($message, $results); case 12: throw new InvalidElementStateException($message, $results); case 13: throw new UnknownServerException($message, $results); case 14: throw new ExpectedException($message, $results); case 15: throw new ElementNotSelectableException($message, $results); case 16: throw new NoSuchDocumentException($message, $results); case 17: throw new UnexpectedJavascriptException($message, $results); case 18: throw new NoScriptResultException($message, $results); case 19: throw new XPathLookupException($message, $results); case 20: throw new NoSuchCollectionException($message, $results); case 21: throw new TimeOutException($message, $results); case 22: throw new NullPointerException($message, $results); case 23: throw new NoSuchWindowException($message, $results); case 24: throw new InvalidCookieDomainException($message, $results); case 25: throw new UnableToSetCookieException($message, $results); case 26: throw new UnexpectedAlertOpenException($message, $results); case 27: throw new NoAlertOpenException($message, $results); case 28: throw new ScriptTimeoutException($message, $results); case 29: throw new InvalidCoordinatesException($message, $results); case 30: throw new IMENotAvailableException($message, $results); case 31: throw new IMEEngineActivationFailedException($message, $results); case 32: throw new InvalidSelectorException($message, $results); case 33: throw new SessionNotCreatedException($message, $results); case 34: throw new MoveTargetOutOfBoundsException($message, $results); default: throw new UnrecognizedExceptionException($message, $results); } } } mechanism = $mechanism; $this->value = $value; } public function getMechanism() { return $this->mechanism; } public function getValue() { return $this->value; } public static function className($class_name) { return new static('class name', $class_name); } public static function cssSelector($css_selector) { return new static('css selector', $css_selector); } public static function id($id) { return new static('id', $id); } public static function name($name) { return new static('name', $name); } public static function linkText($link_text) { return new static('link text', $link_text); } public static function partialLinkText($partial_link_text) { return new static('partial link text', $partial_link_text); } public static function tagName($tag_name) { return new static('tag name', $tag_name); } public static function xpath($xpath) { return new static('xpath', $xpath); } } actions[] = $action; return $this; } public function getNumberOfActions() { return count($this->actions); } public function perform() { foreach ($this->actions as $action) { $action->perform(); } } } driver = $driver; $this->keyboard = $driver->getKeyboard(); $this->mouse = $driver->getMouse(); $this->action = new WebDriverCompositeAction(); } public function perform() { $this->action->perform(); } public function click(WebDriverElement $element = null) { $this->action->addAction( new WebDriverClickAction($this->mouse, $element) ); return $this; } public function clickAndHold(WebDriverElement $element = null) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $element) ); return $this; } public function contextClick(WebDriverElement $element = null) { $this->action->addAction( new WebDriverContextClickAction($this->mouse, $element) ); return $this; } public function doubleClick(WebDriverElement $element = null) { $this->action->addAction( new WebDriverDoubleClickAction($this->mouse, $element) ); return $this; } public function dragAndDrop(WebDriverElement $source, WebDriverElement $target) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $source) ); $this->action->addAction( new WebDriverMouseMoveAction($this->mouse, $target) ); $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, $target) ); return $this; } public function dragAndDropBy(WebDriverElement $source, $x_offset, $y_offset) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $source) ); $this->action->addAction( new WebDriverMoveToOffsetAction($this->mouse, null, $x_offset, $y_offset) ); $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, null) ); return $this; } public function moveByOffset($x_offset, $y_offset) { $this->action->addAction( new WebDriverMoveToOffsetAction($this->mouse, null, $x_offset, $y_offset) ); return $this; } public function moveToElement(WebDriverElement $element, $x_offset = null, $y_offset = null) { $this->action->addAction(new WebDriverMoveToOffsetAction( $this->mouse, $element, $x_offset, $y_offset )); return $this; } public function release(WebDriverElement $element = null) { $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, $element) ); return $this; } public function keyDown(WebDriverElement $element = null, $key = null) { $this->action->addAction( new WebDriverKeyDownAction($this->keyboard, $this->mouse, $element, $key) ); return $this; } public function keyUp(WebDriverElement $element = null, $key = null) { $this->action->addAction( new WebDriverKeyUpAction($this->keyboard, $this->mouse, $element, $key) ); return $this; } public function sendKeys(WebDriverElement $element = null, $keys = null) { $this->action->addAction( new WebDriverSendKeysAction( $this->keyboard, $this->mouse, $element, $keys ) ); return $this; } } touchScreen = $driver->getTouch(); } public function tap(WebDriverElement $element) { $this->action->addAction( new WebDriverTapAction($this->touchScreen, $element) ); return $this; } public function down($x, $y) { $this->action->addAction( new WebDriverDownAction($this->touchScreen, $x, $y) ); return $this; } public function up($x, $y) { $this->action->addAction( new WebDriverUpAction($this->touchScreen, $x, $y) ); return $this; } public function move($x, $y) { $this->action->addAction( new WebDriverMoveAction($this->touchScreen, $x, $y) ); return $this; } public function scroll($x, $y) { $this->action->addAction( new WebDriverScrollAction($this->touchScreen, $x, $y) ); return $this; } public function scrollFromElement(WebDriverElement $element, $x, $y) { $this->action->addAction( new WebDriverScrollFromElementAction($this->touchScreen, $element, $x, $y) ); return $this; } public function doubleTap(WebDriverElement $element) { $this->action->addAction( new WebDriverDoubleTapAction($this->touchScreen, $element) ); return $this; } public function longPress(WebDriverElement $element) { $this->action->addAction( new WebDriverLongPressAction($this->touchScreen, $element) ); return $this; } public function flick($x, $y) { $this->action->addAction( new WebDriverFlickAction($this->touchScreen, $x, $y) ); return $this; } public function flickFromElement(WebDriverElement $element, $x, $y, $speed) { $this->action->addAction( new WebDriverFlickFromElementAction( $this->touchScreen, $element, $x, $y, $speed ) ); return $this; } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->down($this->x, $this->y); } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->move($this->x, $this->y); } } touchScreen->longPress($this->locationProvider); } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->flick($this->x, $this->y); } } touchScreen = $touch_screen; $this->locationProvider = $location_provider; } protected function getActionLocation() { return $this->locationProvider !== null ? $this->locationProvider->getCoordinates() : null; } } x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->scroll($this->x, $this->y); } } touchScreen->tap($this->locationProvider); } } touchScreen->doubleTap($this->locationProvider); } } x = $x; $this->y = $y; $this->speed = $speed; parent::__construct($touch_screen, $element); } public function perform() { $this->touchScreen->flickFromElement( $this->locationProvider, $this->x, $this->y, $this->speed ); } } x = $x; $this->y = $y; parent::__construct($touch_screen, $element); } public function perform() { $this->touchScreen->scrollFromElement( $this->locationProvider, $this->x, $this->y ); } } mouse->mouseDown($this->getActionLocation()); } } key = $key; } } keys = $keys; } public function perform() { $this->focusOnElement(); $this->keyboard->sendKeys($this->keys); } } xOffset = $x_offset; $this->yOffset = $y_offset; } public function perform() { $this->mouse->mouseMove( $this->getActionLocation(), $this->xOffset, $this->yOffset ); } } mouse->mouseMove($this->getActionLocation()); } } mouse->mouseUp($this->getActionLocation()); } } mouse->contextClick($this->getActionLocation()); } } focusOnElement(); $this->keyboard->pressKey($this->key); } } mouse = $mouse; $this->locationProvider = $location_provider; } protected function getActionLocation() { if ($this->locationProvider !== null) { return $this->locationProvider->getCoordinates(); } return null; } protected function moveToLocation() { $this->mouse->mouseMove($this->locationProvider); } } focusOnElement(); $this->keyboard->releaseKey($this->key); } } mouse->doubleClick($this->getActionLocation()); } } mouse->click($this->getActionLocation()); } } keyboard = $keyboard; $this->mouse = $mouse; $this->locationProvider = $location_provider; } protected function focusOnElement() { if ($this->locationProvider) { $this->mouse->click($this->locationProvider->getCoordinates()); } } } onScreen = $on_screen; $this->inViewPort = $in_view_port; $this->onPage = $on_page; $this->auxiliary = $auxiliary; } public function onScreen() { throw new UnsupportedOperationException( 'onScreen is planned but not yet supported by Selenium' ); } public function inViewPort() { return call_user_func($this->inViewPort); } public function onPage() { return call_user_func($this->onPage); } public function getAuxiliary() { return $this->auxiliary; } } microtime(true)) { if ($this->getHTTPResponseCode($url) === 200) { return $this; } usleep(self::POLL_INTERVAL_MS); } throw new TimeOutException(sprintf( 'Timed out waiting for %s to become available after %d ms.', $url, $timeout_in_ms )); } public function waitUntilUnavailable($timeout_in_ms, $url) { $end = microtime(true) + $timeout_in_ms / 1000; while ($end > microtime(true)) { if ($this->getHTTPResponseCode($url) !== 200) { return $this; } usleep(self::POLL_INTERVAL_MS); } throw new TimeOutException(sprintf( 'Timed out waiting for %s to become unavailable after %d ms.', $url, $timeout_in_ms )); } private function getHTTPResponseCode($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (!defined(CURLOPT_CONNECTTIMEOUT_MS)) { define('CURLOPT_CONNECTTIMEOUT_MS', 156); } curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, self::CONNECT_TIMEOUT_MS); $code = null; try { curl_exec($ch); $info = curl_getinfo($ch); $code = $info['http_code']; } catch (Exception $e) { } curl_close($ch); return $code; } } driver = $driver; return $this; } public function getDefaultDriver() { return $this->driver; } public function register(WebDriverEventListener $listener) { $this->listeners[] = $listener; return $this; } public function unregister(WebDriverEventListener $listener) { $key = array_search($listener, $this->listeners, true); if ($key !== false) { unset($this->listeners[$key]); } return $this; } public function dispatch($method, $arguments) { foreach ($this->listeners as $listener) { call_user_func_array([$listener, $method], $arguments); } return $this; } } width = $width; $this->height = $height; } public function getHeight() { return $this->height; } public function getWidth() { return $this->width; } public function equals(WebDriverDimension $dimension) { return $this->height === $dimension->getHeight() && $this->width === $dimension->getWidth(); } } httpClientHandler = $httpClientHandler ?: $this->detectHttpClientHandler(); $this->enableBetaMode = $enableBeta; } public function setHttpClientHandler(FacebookHttpClientInterface $httpClientHandler) { $this->httpClientHandler = $httpClientHandler; } public function getHttpClientHandler() { return $this->httpClientHandler; } public function detectHttpClientHandler() { return extension_loaded('curl') ? new FacebookCurlHttpClient() : new FacebookStreamHttpClient(); } public function enableBetaMode($betaMode = true) { $this->enableBetaMode = $betaMode; } public function getBaseGraphUrl($postToVideoUrl = false) { if ($postToVideoUrl) { return $this->enableBetaMode ? static::BASE_GRAPH_VIDEO_URL_BETA : static::BASE_GRAPH_VIDEO_URL; } return $this->enableBetaMode ? static::BASE_GRAPH_URL_BETA : static::BASE_GRAPH_URL; } public function prepareRequestMessage(FacebookRequest $request) { $postToVideoUrl = $request->containsVideoUploads(); $url = $this->getBaseGraphUrl($postToVideoUrl) . $request->getUrl(); if ($request->containsFileUploads()) { $requestBody = $request->getMultipartBody(); $request->setHeaders([ 'Content-Type' => 'multipart/form-data; boundary=' . $requestBody->getBoundary(), ]); } else { $requestBody = $request->getUrlEncodedBody(); $request->setHeaders([ 'Content-Type' => 'application/x-www-form-urlencoded', ]); } return [ $url, $request->getMethod(), $request->getHeaders(), $requestBody->getBody(), ]; } public function sendRequest(FacebookRequest $request) { if (get_class($request) === 'Facebook\FacebookRequest') { $request->validateAccessToken(); } list($url, $method, $headers, $body) = $this->prepareRequestMessage($request); $timeOut = static::DEFAULT_REQUEST_TIMEOUT; if ($request->containsFileUploads()) { $timeOut = static::DEFAULT_FILE_UPLOAD_REQUEST_TIMEOUT; } elseif ($request->containsVideoUploads()) { $timeOut = static::DEFAULT_VIDEO_UPLOAD_REQUEST_TIMEOUT; } $rawResponse = $this->httpClientHandler->send($url, $method, $body, $headers, $timeOut); static::$requestCount++; $returnResponse = new FacebookResponse( $request, $rawResponse->getBody(), $rawResponse->getHttpResponseCode(), $rawResponse->getHeaders() ); if ($returnResponse->isError()) { throw $returnResponse->getThrownException(); } return $returnResponse; } public function sendBatchRequest(FacebookBatchRequest $request) { $request->prepareRequestsForBatch(); $facebookResponse = $this->sendRequest($request); return new FacebookBatchResponse($request, $facebookResponse); } } getField('id'); } } '\Facebook\GraphNodes\GraphPage', 'location' => '\Facebook\GraphNodes\GraphPage', 'significant_other' => '\Facebook\GraphNodes\GraphUser', 'picture' => '\Facebook\GraphNodes\GraphPicture', ]; public function getId() { return $this->getField('id'); } public function getName() { return $this->getField('name'); } public function getFirstName() { return $this->getField('first_name'); } public function getMiddleName() { return $this->getField('middle_name'); } public function getLastName() { return $this->getField('last_name'); } public function getEmail() { return $this->getField('email'); } public function getGender() { return $this->getField('gender'); } public function getLink() { return $this->getField('link'); } public function getBirthday() { return $this->getField('birthday'); } public function getLocation() { return $this->getField('location'); } public function getHometown() { return $this->getField('hometown'); } public function getSignificantOther() { return $this->getField('significant_other'); } public function getPicture() { return $this->getField('picture'); } } '\Facebook\GraphNodes\GraphUser', 'place' => '\Facebook\GraphNodes\GraphPage', ]; public function getId() { return $this->getField('id'); } public function getCanUpload() { return $this->getField('can_upload'); } public function getCount() { return $this->getField('count'); } public function getCoverPhoto() { return $this->getField('cover_photo'); } public function getCreatedTime() { return $this->getField('created_time'); } public function getUpdatedTime() { return $this->getField('updated_time'); } public function getDescription() { return $this->getField('description'); } public function getFrom() { return $this->getField('from'); } public function getPlace() { return $this->getField('place'); } public function getLink() { return $this->getField('link'); } public function getLocation() { return $this->getField('location'); } public function getName() { return $this->getField('name'); } public function getPrivacy() { return $this->getField('privacy'); } public function getType() { return $this->getField('type'); } } hasYear = count($parts) === 3 || count($parts) === 1; $this->hasDate = count($parts) === 3 || count($parts) === 2; parent::__construct($date); } public function hasDate() { return $this->hasDate; } public function hasYear() { return $this->hasYear; } } '\Facebook\GraphNodes\GraphPage', 'global_brand_parent_page' => '\Facebook\GraphNodes\GraphPage', 'location' => '\Facebook\GraphNodes\GraphLocation', 'cover' => '\Facebook\GraphNodes\GraphCoverPhoto', 'picture' => '\Facebook\GraphNodes\GraphPicture', ]; public function getId() { return $this->getField('id'); } public function getCategory() { return $this->getField('category'); } public function getName() { return $this->getField('name'); } public function getBestPage() { return $this->getField('best_page'); } public function getGlobalBrandParentPage() { return $this->getField('global_brand_parent_page'); } public function getLocation() { return $this->getField('location'); } public function getCover() { return $this->getField('cover'); } public function getPicture() { return $this->getField('picture'); } public function getAccessToken() { return $this->getField('access_token'); } public function getPerms() { return $this->getField('perms'); } } items = $items; } public function getField($name, $default = null) { if (isset($this->items[$name])) { return $this->items[$name]; } return $default; } public function getProperty($name, $default = null) { return $this->getField($name, $default); } public function getFieldNames() { return array_keys($this->items); } public function getPropertyNames() { return $this->getFieldNames(); } public function all() { return $this->items; } public function asArray() { return array_map(function ($value) { return $value instanceof Collection ? $value->asArray() : $value; }, $this->items); } public function map(\Closure $callback) { return new static(array_map($callback, $this->items, array_keys($this->items))); } public function asJson($options = 0) { return json_encode($this->asArray(), $options); } public function count() { return count($this->items); } public function getIterator() { return new ArrayIterator($this->items); } public function offsetExists($key) { return array_key_exists($key, $this->items); } public function offsetGet($key) { return $this->items[$key]; } public function offsetSet($key, $value) { if (is_null($key)) { $this->items[] = $value; } else { $this->items[$key] = $value; } } public function offsetUnset($key) { unset($this->items[$key]); } public function __toString() { return $this->asJson(); } } response = $response; $this->decodedBody = $response->getDecodedBody(); } public function makeGraphNode($subclassName = null) { $this->validateResponseAsArray(); $this->validateResponseCastableAsGraphNode(); return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName); } public function makeGraphAchievement() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAchievement'); } public function makeGraphAlbum() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAlbum'); } public function makeGraphPage() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphPage'); } public function makeGraphSessionInfo() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphSessionInfo'); } public function makeGraphUser() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphUser'); } public function makeGraphEvent() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent'); } public function makeGraphGroup() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphGroup'); } public function makeGraphEdge($subclassName = null, $auto_prefix = true) { $this->validateResponseAsArray(); $this->validateResponseCastableAsGraphEdge(); if ($subclassName && $auto_prefix) { $subclassName = static::BASE_GRAPH_OBJECT_PREFIX . $subclassName; } return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName); } public function validateResponseAsArray() { if (!is_array($this->decodedBody)) { throw new FacebookSDKException('Unable to get response from Graph as array.', 620); } } public function validateResponseCastableAsGraphNode() { if (isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data'])) { throw new FacebookSDKException( 'Unable to convert response from Graph to a GraphNode because the response looks like a GraphEdge. Try using GraphNodeFactory::makeGraphEdge() instead.', 620 ); } } public function validateResponseCastableAsGraphEdge() { if (!(isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data']))) { throw new FacebookSDKException( 'Unable to convert response from Graph to a GraphEdge because the response does not look like a GraphEdge. Try using GraphNodeFactory::makeGraphNode() instead.', 620 ); } } public function safelyMakeGraphNode(array $data, $subclassName = null) { $subclassName = $subclassName ?: static::BASE_GRAPH_NODE_CLASS; static::validateSubclass($subclassName); $parentNodeId = isset($data['id']) ? $data['id'] : null; $items = []; foreach ($data as $k => $v) { if (is_array($v)) { $graphObjectMap = $subclassName::getObjectMap(); $objectSubClass = isset($graphObjectMap[$k]) ? $graphObjectMap[$k] : null; $items[$k] = $this->castAsGraphNodeOrGraphEdge($v, $objectSubClass, $k, $parentNodeId); } else { $items[$k] = $v; } } return new $subclassName($items); } public function castAsGraphNodeOrGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null) { if (isset($data['data'])) { if (static::isCastableAsGraphEdge($data['data'])) { return $this->safelyMakeGraphEdge($data, $subclassName, $parentKey, $parentNodeId); } $data = $data['data']; } return $this->safelyMakeGraphNode($data, $subclassName); } public function safelyMakeGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null) { if (!isset($data['data'])) { throw new FacebookSDKException('Cannot cast data to GraphEdge. Expected a "data" key.', 620); } $dataList = []; foreach ($data['data'] as $graphNode) { $dataList[] = $this->safelyMakeGraphNode($graphNode, $subclassName); } $metaData = $this->getMetaData($data); $parentGraphEdgeEndpoint = $parentNodeId && $parentKey ? '/' . $parentNodeId . '/' . $parentKey : null; $className = static::BASE_GRAPH_EDGE_CLASS; return new $className($this->response->getRequest(), $dataList, $metaData, $parentGraphEdgeEndpoint, $subclassName); } public function getMetaData(array $data) { unset($data['data']); return $data; } public static function isCastableAsGraphEdge(array $data) { if ($data === []) { return true; } return array_keys($data) === range(0, count($data) - 1); } public static function validateSubclass($subclassName) { if ($subclassName == static::BASE_GRAPH_NODE_CLASS || is_subclass_of($subclassName, static::BASE_GRAPH_NODE_CLASS)) { return; } throw new FacebookSDKException('The given subclass "' . $subclassName . '" is not valid. Cannot cast to an object that is not a GraphNode subclass.', 620); } } getField('street'); } public function getCity() { return $this->getField('city'); } public function getState() { return $this->getField('state'); } public function getCountry() { return $this->getField('country'); } public function getZip() { return $this->getField('zip'); } public function getLatitude() { return $this->getField('latitude'); } public function getLongitude() { return $this->getField('longitude'); } } request = $request; $this->metaData = $metaData; $this->parentEdgeEndpoint = $parentEdgeEndpoint; $this->subclassName = $subclassName; parent::__construct($data); } public function getParentGraphEdge() { return $this->parentEdgeEndpoint; } public function getSubClassName() { return $this->subclassName; } public function getMetaData() { return $this->metaData; } public function getNextCursor() { return $this->getCursor('after'); } public function getPreviousCursor() { return $this->getCursor('before'); } public function getCursor($direction) { if (isset($this->metaData['paging']['cursors'][$direction])) { return $this->metaData['paging']['cursors'][$direction]; } return null; } public function getPaginationUrl($direction) { $this->validateForPagination(); if (!isset($this->metaData['paging'][$direction])) { return null; } $pageUrl = $this->metaData['paging'][$direction]; return FacebookUrlManipulator::baseGraphUrlEndpoint($pageUrl); } public function validateForPagination() { if ($this->request->getMethod() !== 'GET') { throw new FacebookSDKException('You can only paginate on a GET request.', 720); } } public function getPaginationRequest($direction) { $pageUrl = $this->getPaginationUrl($direction); if (!$pageUrl) { return null; } $newRequest = clone $this->request; $newRequest->setEndpoint($pageUrl); return $newRequest; } public function getNextPageRequest() { return $this->getPaginationRequest('next'); } public function getPreviousPageRequest() { return $this->getPaginationRequest('previous'); } public function getTotalCount() { if (isset($this->metaData['summary']['total_count'])) { return $this->metaData['summary']['total_count']; } return null; } public function map(\Closure $callback) { return new static( $this->request, array_map($callback, $this->items, array_keys($this->items)), $this->metaData, $this->parentEdgeEndpoint, $this->subclassName ); } } '\Facebook\GraphNodes\GraphCoverPhoto', 'venue' => '\Facebook\GraphNodes\GraphLocation', ]; public function getId() { return $this->getField('id'); } public function getCover() { return $this->getField('cover'); } public function getDescription() { return $this->getField('description'); } public function getEmail() { return $this->getField('email'); } public function getIcon() { return $this->getField('icon'); } public function getLink() { return $this->getField('link'); } public function getName() { return $this->getField('name'); } public function getMemberRequestCount() { return $this->getField('member_request_count'); } public function getOwner() { return $this->getField('owner'); } public function getParent() { return $this->getField('parent'); } public function getPrivacy() { return $this->getField('privacy'); } public function getUpdatedTime() { return $this->getField('updated_time'); } public function getVenue() { return $this->getField('venue'); } } getField('app_id'); } public function getApplication() { return $this->getField('application'); } public function getExpiresAt() { return $this->getField('expires_at'); } public function getIsValid() { return $this->getField('is_valid'); } public function getIssuedAt() { return $this->getField('issued_at'); } public function getScopes() { return $this->getField('scopes'); } public function getUserId() { return $this->getField('user_id'); } } getField('is_silhouette'); } public function getUrl() { return $this->getField('url'); } public function getWidth() { return $this->getField('width'); } public function getHeight() { return $this->getField('height'); } } makeGraphNode($subclassName); } public function makeGraphEvent() { return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent'); } public function makeGraphList($subclassName = null, $auto_prefix = true) { return $this->makeGraphEdge($subclassName, $auto_prefix); } } '\Facebook\GraphNodes\GraphCoverPhoto', 'place' => '\Facebook\GraphNodes\GraphPage', 'picture' => '\Facebook\GraphNodes\GraphPicture', 'parent_group' => '\Facebook\GraphNodes\GraphGroup', ]; public function getId() { return $this->getField('id'); } public function getCover() { return $this->getField('cover'); } public function getDescription() { return $this->getField('description'); } public function getEndTime() { return $this->getField('end_time'); } public function getIsDateOnly() { return $this->getField('is_date_only'); } public function getName() { return $this->getField('name'); } public function getOwner() { return $this->getField('owner'); } public function getParentGroup() { return $this->getField('parent_group'); } public function getPlace() { return $this->getField('place'); } public function getPrivacy() { return $this->getField('privacy'); } public function getStartTime() { return $this->getField('start_time'); } public function getTicketUri() { return $this->getField('ticket_uri'); } public function getTimezone() { return $this->getField('timezone'); } public function getUpdatedTime() { return $this->getField('updated_time'); } public function getPicture() { return $this->getField('picture'); } public function getAttendingCount() { return $this->getField('attending_count'); } public function getDeclinedCount() { return $this->getField('declined_count'); } public function getMaybeCount() { return $this->getField('maybe_count'); } public function getNoreplyCount() { return $this->getField('noreply_count'); } public function getInvitedCount() { return $this->getField('invited_count'); } } '\Facebook\GraphNodes\GraphUser', 'application' => '\Facebook\GraphNodes\GraphApplication', ]; public function getId() { return $this->getField('id'); } public function getFrom() { return $this->getField('from'); } public function getPublishTime() { return $this->getField('publish_time'); } public function getApplication() { return $this->getField('application'); } public function getData() { return $this->getField('data'); } public function getType() { return 'game.achievement'; } public function isNoFeedStory() { return $this->getField('no_feed_story'); } } getField('id'); } public function getSource() { return $this->getField('source'); } public function getOffsetX() { return $this->getField('offset_x'); } public function getOffsetY() { return $this->getField('offset_y'); } } castItems($data)); } public function castItems(array $data) { $items = []; foreach ($data as $k => $v) { if ($this->shouldCastAsDateTime($k) && (is_numeric($v) || $this->isIso8601DateString($v)) ) { $items[$k] = $this->castToDateTime($v); } elseif ($k === 'birthday') { $items[$k] = $this->castToBirthday($v); } else { $items[$k] = $v; } } return $items; } public function uncastItems() { $items = $this->asArray(); return array_map(function ($v) { if ($v instanceof \DateTime) { return $v->format(\DateTime::ISO8601); } return $v; }, $items); } public function asJson($options = 0) { return json_encode($this->uncastItems(), $options); } public function isIso8601DateString($string) { $crazyInsaneRegexThatSomehowDetectsIso8601 = '/^([\+-]?\d{4}(?!\d{2}\b))' . '((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?' . '|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d' . '|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])' . '((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d' . '([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/'; return preg_match($crazyInsaneRegexThatSomehowDetectsIso8601, $string) === 1; } public function shouldCastAsDateTime($key) { return in_array($key, [ 'created_time', 'updated_time', 'start_time', 'end_time', 'backdated_time', 'issued_at', 'expires_at', 'publish_time' ], true); } public function castToDateTime($value) { if (is_int($value)) { $dt = new \DateTime(); $dt->setTimestamp($value); } else { $dt = new \DateTime($value); } return $dt; } public function castToBirthday($value) { return new Birthday($value); } public static function getObjectMap() { return static::$graphObjectMap; } } signedRequest) { return; } $this->pageData = $this->signedRequest->get('page'); } public function getPageData($key, $default = null) { if (isset($this->pageData[$key])) { return $this->pageData[$key]; } return $default; } public function isAdmin() { return $this->getPageData('admin') === true; } public function getPageId() { return $this->getPageData('id'); } } app = $app; $graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; $this->oAuth2Client = new OAuth2Client($this->app, $client, $graphVersion); $this->instantiateSignedRequest(); } public function instantiateSignedRequest($rawSignedRequest = null) { $rawSignedRequest = $rawSignedRequest ?: $this->getRawSignedRequest(); if (!$rawSignedRequest) { return; } $this->signedRequest = new SignedRequest($this->app, $rawSignedRequest); } public function getAccessToken() { if ($this->signedRequest && $this->signedRequest->hasOAuthData()) { $code = $this->signedRequest->get('code'); $accessToken = $this->signedRequest->get('oauth_token'); if ($code && !$accessToken) { return $this->oAuth2Client->getAccessTokenFromCode($code); } $expiresAt = $this->signedRequest->get('expires', 0); return new AccessToken($accessToken, $expiresAt); } return null; } public function getSignedRequest() { return $this->signedRequest; } public function getUserId() { return $this->signedRequest ? $this->signedRequest->getUserId() : null; } abstract public function getRawSignedRequest(); public function getRawSignedRequestFromPost() { if (isset($_POST['signed_request'])) { return $_POST['signed_request']; } return null; } public function getRawSignedRequestFromCookie() { if (isset($_COOKIE['fbsr_' . $this->app->getId()])) { return $_COOKIE['fbsr_' . $this->app->getId()]; } return null; } } oAuth2Client = $oAuth2Client; $this->persistentDataHandler = $persistentDataHandler ?: new FacebookSessionPersistentDataHandler(); $this->urlDetectionHandler = $urlHandler ?: new FacebookUrlDetectionHandler(); $this->pseudoRandomStringGenerator = PseudoRandomStringGeneratorFactory::createPseudoRandomStringGenerator($prsg); } public function getPersistentDataHandler() { return $this->persistentDataHandler; } public function getUrlDetectionHandler() { return $this->urlDetectionHandler; } public function getPseudoRandomStringGenerator() { return $this->pseudoRandomStringGenerator; } private function makeUrl($redirectUrl, array $scope, array $params = [], $separator = '&') { $state = $this->persistentDataHandler->get('state') ?: $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRF_LENGTH); $this->persistentDataHandler->set('state', $state); return $this->oAuth2Client->getAuthorizationUrl($redirectUrl, $state, $scope, $params, $separator); } public function getLoginUrl($redirectUrl, array $scope = [], $separator = '&') { return $this->makeUrl($redirectUrl, $scope, [], $separator); } public function getLogoutUrl($accessToken, $next, $separator = '&') { if (!$accessToken instanceof AccessToken) { $accessToken = new AccessToken($accessToken); } if ($accessToken->isAppAccessToken()) { throw new FacebookSDKException('Cannot generate a logout URL with an app access token.', 722); } $params = [ 'next' => $next, 'access_token' => $accessToken->getValue(), ]; return 'https://www.facebook.com/logout.php?' . http_build_query($params, null, $separator); } public function getReRequestUrl($redirectUrl, array $scope = [], $separator = '&') { $params = ['auth_type' => 'rerequest']; return $this->makeUrl($redirectUrl, $scope, $params, $separator); } public function getReAuthenticationUrl($redirectUrl, array $scope = [], $separator = '&') { $params = ['auth_type' => 'reauthenticate']; return $this->makeUrl($redirectUrl, $scope, $params, $separator); } public function getAccessToken($redirectUrl = null) { if (!$code = $this->getCode()) { return null; } $this->validateCsrf(); $this->resetCsrf(); $redirectUrl = $redirectUrl ?: $this->urlDetectionHandler->getCurrentUrl(); $redirectUrl = FacebookUrlManipulator::removeParamsFromUrl($redirectUrl, ['state']); return $this->oAuth2Client->getAccessTokenFromCode($code, $redirectUrl); } protected function validateCsrf() { $state = $this->getState(); if (!$state) { throw new FacebookSDKException('Cross-site request forgery validation failed. Required GET param "state" missing.'); } $savedState = $this->persistentDataHandler->get('state'); if (!$savedState) { throw new FacebookSDKException('Cross-site request forgery validation failed. Required param "state" missing from persistent data.'); } if (\hash_equals($savedState, $state)) { return; } throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.'); } private function resetCsrf() { $this->persistentDataHandler->set('state', null); } protected function getCode() { return $this->getInput('code'); } protected function getState() { return $this->getInput('state'); } public function getErrorCode() { return $this->getInput('error_code'); } public function getError() { return $this->getInput('error'); } public function getErrorReason() { return $this->getInput('error_reason'); } public function getErrorDescription() { return $this->getInput('error_description'); } private function getInput($key) { return isset($_GET[$key]) ? $_GET[$key] : null; } } signedRequest ? $this->signedRequest->get('app_data') : null; } public function getRawSignedRequest() { return $this->getRawSignedRequestFromPost() ?: null; } } getRawSignedRequestFromCookie(); } } params = $params; $this->files = $files; $this->boundary = $boundary ?: uniqid(); } public function getBody() { $body = ''; $params = $this->getNestedParams($this->params); foreach ($params as $k => $v) { $body .= $this->getParamString($k, $v); } foreach ($this->files as $k => $v) { $body .= $this->getFileString($k, $v); } $body .= "--{$this->boundary}--\r\n"; return $body; } public function getBoundary() { return $this->boundary; } private function getFileString($name, FacebookFile $file) { return sprintf( "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s\r\n\r\n%s\r\n", $this->boundary, $name, $file->getFileName(), $this->getFileHeaders($file), $file->getContents() ); } private function getParamString($name, $value) { return sprintf( "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", $this->boundary, $name, $value ); } private function getNestedParams(array $params) { $query = http_build_query($params, null, '&'); $params = explode('&', $query); $result = []; foreach ($params as $param) { list($key, $value) = explode('=', $param, 2); $result[urldecode($key)] = urldecode($value); } return $result; } protected function getFileHeaders(FacebookFile $file) { return "\r\nContent-Type: {$file->getMimetype()}"; } } httpResponseCode = (int)$httpStatusCode; } if (is_array($headers)) { $this->headers = $headers; } else { $this->setHeadersFromString($headers); } $this->body = $body; } public function getHeaders() { return $this->headers; } public function getBody() { return $this->body; } public function getHttpResponseCode() { return $this->httpResponseCode; } public function setHttpResponseCodeFromHeader($rawResponseHeader) { preg_match('|HTTP/\d\.\d\s+(\d+)\s+.*|', $rawResponseHeader, $match); $this->httpResponseCode = (int)$match[1]; } protected function setHeadersFromString($rawHeaders) { $rawHeaders = str_replace("\r\n", "\n", $rawHeaders); $headerCollection = explode("\n\n", trim($rawHeaders)); $rawHeader = array_pop($headerCollection); $headerComponents = explode("\n", $rawHeader); foreach ($headerComponents as $line) { if (strpos($line, ': ') === false) { $this->setHttpResponseCodeFromHeader($line); } else { list($key, $value) = explode(': ', $line, 2); $this->headers[$key] = $value; } } } } params = $params; } public function getBody() { return http_build_query($this->params, null, '&'); } } request = $request; $this->body = $body; $this->httpStatusCode = $httpStatusCode; $this->headers = $headers; $this->decodeBody(); } public function getRequest() { return $this->request; } public function getApp() { return $this->request->getApp(); } public function getAccessToken() { return $this->request->getAccessToken(); } public function getHttpStatusCode() { return $this->httpStatusCode; } public function getHeaders() { return $this->headers; } public function getBody() { return $this->body; } public function getDecodedBody() { return $this->decodedBody; } public function getAppSecretProof() { return $this->request->getAppSecretProof(); } public function getETag() { return isset($this->headers['ETag']) ? $this->headers['ETag'] : null; } public function getGraphVersion() { return isset($this->headers['Facebook-API-Version']) ? $this->headers['Facebook-API-Version'] : null; } public function isError() { return isset($this->decodedBody['error']); } public function throwException() { throw $this->thrownException; } public function makeException() { $this->thrownException = FacebookResponseException::create($this); } public function getThrownException() { return $this->thrownException; } public function decodeBody() { $this->decodedBody = json_decode($this->body, true); if ($this->decodedBody === null) { $this->decodedBody = []; parse_str($this->body, $this->decodedBody); } elseif (is_bool($this->decodedBody)) { $this->decodedBody = ['success' => $this->decodedBody]; } elseif (is_numeric($this->decodedBody)) { $this->decodedBody = ['id' => $this->decodedBody]; } if (!is_array($this->decodedBody)) { $this->decodedBody = []; } if ($this->isError()) { $this->makeException(); } } public function getGraphObject($subclassName = null) { return $this->getGraphNode($subclassName); } public function getGraphNode($subclassName = null) { $factory = new GraphNodeFactory($this); return $factory->makeGraphNode($subclassName); } public function getGraphAlbum() { $factory = new GraphNodeFactory($this); return $factory->makeGraphAlbum(); } public function getGraphPage() { $factory = new GraphNodeFactory($this); return $factory->makeGraphPage(); } public function getGraphSessionInfo() { $factory = new GraphNodeFactory($this); return $factory->makeGraphSessionInfo(); } public function getGraphUser() { $factory = new GraphNodeFactory($this); return $factory->makeGraphUser(); } public function getGraphEvent() { $factory = new GraphNodeFactory($this); return $factory->makeGraphEvent(); } public function getGraphGroup() { $factory = new GraphNodeFactory($this); return $factory->makeGraphGroup(); } public function getGraphList($subclassName = null, $auto_prefix = true) { return $this->getGraphEdge($subclassName, $auto_prefix); } public function getGraphEdge($subclassName = null, $auto_prefix = true) { $factory = new GraphNodeFactory($this); return $factory->makeGraphEdge($subclassName, $auto_prefix); } } file = $file; $this->uploadSessionId = $uploadSessionId; $this->videoId = $videoId; $this->startOffset = $startOffset; $this->endOffset = $endOffset; } public function getFile() { return $this->file; } public function getPartialFile() { $maxLength = $this->endOffset - $this->startOffset; return new FacebookFile($this->file->getFilePath(), $maxLength, $this->startOffset); } public function getUploadSessionId() { return $this->uploadSessionId; } public function isLastChunk() { return $this->startOffset === $this->endOffset; } public function getStartOffset() { return $this->startOffset; } public function getVideoId() { return $this->videoId; } } 'text/vnd.in3d.3dml', '3g2' => 'video/3gpp2', '3gp' => 'video/3gpp', '7z' => 'application/x-7z-compressed', 'aab' => 'application/x-authorware-bin', 'aac' => 'audio/x-aac', 'aam' => 'application/x-authorware-map', 'aas' => 'application/x-authorware-seg', 'abw' => 'application/x-abiword', 'ac' => 'application/pkix-attr-cert', 'acc' => 'application/vnd.americandynamics.acc', 'ace' => 'application/x-ace-compressed', 'acu' => 'application/vnd.acucobol', 'acutc' => 'application/vnd.acucorp', 'adp' => 'audio/adpcm', 'aep' => 'application/vnd.audiograph', 'afm' => 'application/x-font-type1', 'afp' => 'application/vnd.ibm.modcap', 'ahead' => 'application/vnd.ahead.space', 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'air' => 'application/vnd.adobe.air-application-installer-package+zip', 'ait' => 'application/vnd.dvb.ait', 'ami' => 'application/vnd.amiga.ami', 'apk' => 'application/vnd.android.package-archive', 'application' => 'application/x-ms-application', 'apr' => 'application/vnd.lotus-approach', 'asa' => 'text/plain', 'asax' => 'application/octet-stream', 'asc' => 'application/pgp-signature', 'ascx' => 'text/plain', 'asf' => 'video/x-ms-asf', 'ashx' => 'text/plain', 'asm' => 'text/x-asm', 'asmx' => 'text/plain', 'aso' => 'application/vnd.accpac.simply.aso', 'asp' => 'text/plain', 'aspx' => 'text/plain', 'asx' => 'video/x-ms-asf', 'atc' => 'application/vnd.acucorp', 'atom' => 'application/atom+xml', 'atomcat' => 'application/atomcat+xml', 'atomsvc' => 'application/atomsvc+xml', 'atx' => 'application/vnd.antix.game-component', 'au' => 'audio/basic', 'avi' => 'video/x-msvideo', 'aw' => 'application/applixware', 'axd' => 'text/plain', 'azf' => 'application/vnd.airzip.filesecure.azf', 'azs' => 'application/vnd.airzip.filesecure.azs', 'azw' => 'application/vnd.amazon.ebook', 'bat' => 'application/x-msdownload', 'bcpio' => 'application/x-bcpio', 'bdf' => 'application/x-font-bdf', 'bdm' => 'application/vnd.syncml.dm+wbxml', 'bed' => 'application/vnd.realvnc.bed', 'bh2' => 'application/vnd.fujitsu.oasysprs', 'bin' => 'application/octet-stream', 'bmi' => 'application/vnd.bmi', 'bmp' => 'image/bmp', 'book' => 'application/vnd.framemaker', 'box' => 'application/vnd.previewsystems.box', 'boz' => 'application/x-bzip2', 'bpk' => 'application/octet-stream', 'btif' => 'image/prs.btif', 'bz' => 'application/x-bzip', 'bz2' => 'application/x-bzip2', 'c' => 'text/x-c', 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', 'c4d' => 'application/vnd.clonk.c4group', 'c4f' => 'application/vnd.clonk.c4group', 'c4g' => 'application/vnd.clonk.c4group', 'c4p' => 'application/vnd.clonk.c4group', 'c4u' => 'application/vnd.clonk.c4group', 'cab' => 'application/vnd.ms-cab-compressed', 'car' => 'application/vnd.curl.car', 'cat' => 'application/vnd.ms-pki.seccat', 'cc' => 'text/x-c', 'cct' => 'application/x-director', 'ccxml' => 'application/ccxml+xml', 'cdbcmsg' => 'application/vnd.contact.cmsg', 'cdf' => 'application/x-netcdf', 'cdkey' => 'application/vnd.mediastation.cdkey', 'cdmia' => 'application/cdmi-capability', 'cdmic' => 'application/cdmi-container', 'cdmid' => 'application/cdmi-domain', 'cdmio' => 'application/cdmi-object', 'cdmiq' => 'application/cdmi-queue', 'cdx' => 'chemical/x-cdx', 'cdxml' => 'application/vnd.chemdraw+xml', 'cdy' => 'application/vnd.cinderella', 'cer' => 'application/pkix-cert', 'cfc' => 'application/x-coldfusion', 'cfm' => 'application/x-coldfusion', 'cgm' => 'image/cgm', 'chat' => 'application/x-chat', 'chm' => 'application/vnd.ms-htmlhelp', 'chrt' => 'application/vnd.kde.kchart', 'cif' => 'chemical/x-cif', 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', 'cil' => 'application/vnd.ms-artgalry', 'cla' => 'application/vnd.claymore', 'class' => 'application/java-vm', 'clkk' => 'application/vnd.crick.clicker.keyboard', 'clkp' => 'application/vnd.crick.clicker.palette', 'clkt' => 'application/vnd.crick.clicker.template', 'clkw' => 'application/vnd.crick.clicker.wordbank', 'clkx' => 'application/vnd.crick.clicker', 'clp' => 'application/x-msclip', 'cmc' => 'application/vnd.cosmocaller', 'cmdf' => 'chemical/x-cmdf', 'cml' => 'chemical/x-cml', 'cmp' => 'application/vnd.yellowriver-custom-menu', 'cmx' => 'image/x-cmx', 'cod' => 'application/vnd.rim.cod', 'com' => 'application/x-msdownload', 'conf' => 'text/plain', 'cpio' => 'application/x-cpio', 'cpp' => 'text/x-c', 'cpt' => 'application/mac-compactpro', 'crd' => 'application/x-mscardfile', 'crl' => 'application/pkix-crl', 'crt' => 'application/x-x509-ca-cert', 'cryptonote' => 'application/vnd.rig.cryptonote', 'cs' => 'text/plain', 'csh' => 'application/x-csh', 'csml' => 'chemical/x-csml', 'csp' => 'application/vnd.commonspace', 'css' => 'text/css', 'cst' => 'application/x-director', 'csv' => 'text/csv', 'cu' => 'application/cu-seeme', 'curl' => 'text/vnd.curl', 'cww' => 'application/prs.cww', 'cxt' => 'application/x-director', 'cxx' => 'text/x-c', 'dae' => 'model/vnd.collada+xml', 'daf' => 'application/vnd.mobius.daf', 'dataless' => 'application/vnd.fdsn.seed', 'davmount' => 'application/davmount+xml', 'dcr' => 'application/x-director', 'dcurl' => 'text/vnd.curl.dcurl', 'dd2' => 'application/vnd.oma.dd2+xml', 'ddd' => 'application/vnd.fujixerox.ddd', 'deb' => 'application/x-debian-package', 'def' => 'text/plain', 'deploy' => 'application/octet-stream', 'der' => 'application/x-x509-ca-cert', 'dfac' => 'application/vnd.dreamfactory', 'dic' => 'text/x-c', 'dir' => 'application/x-director', 'dis' => 'application/vnd.mobius.dis', 'dist' => 'application/octet-stream', 'distz' => 'application/octet-stream', 'djv' => 'image/vnd.djvu', 'djvu' => 'image/vnd.djvu', 'dll' => 'application/x-msdownload', 'dmg' => 'application/octet-stream', 'dms' => 'application/octet-stream', 'dna' => 'application/vnd.dna', 'doc' => 'application/msword', 'docm' => 'application/vnd.ms-word.document.macroenabled.12', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dot' => 'application/msword', 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'dp' => 'application/vnd.osgi.dp', 'dpg' => 'application/vnd.dpgraph', 'dra' => 'audio/vnd.dra', 'dsc' => 'text/prs.lines.tag', 'dssc' => 'application/dssc+der', 'dtb' => 'application/x-dtbook+xml', 'dtd' => 'application/xml-dtd', 'dts' => 'audio/vnd.dts', 'dtshd' => 'audio/vnd.dts.hd', 'dump' => 'application/octet-stream', 'dvi' => 'application/x-dvi', 'dwf' => 'model/vnd.dwf', 'dwg' => 'image/vnd.dwg', 'dxf' => 'image/vnd.dxf', 'dxp' => 'application/vnd.spotfire.dxp', 'dxr' => 'application/x-director', 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', 'ecma' => 'application/ecmascript', 'edm' => 'application/vnd.novadigm.edm', 'edx' => 'application/vnd.novadigm.edx', 'efif' => 'application/vnd.picsel', 'ei6' => 'application/vnd.pg.osasli', 'elc' => 'application/octet-stream', 'eml' => 'message/rfc822', 'emma' => 'application/emma+xml', 'eol' => 'audio/vnd.digital-winds', 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', 'es3' => 'application/vnd.eszigno3+xml', 'esf' => 'application/vnd.epson.esf', 'et3' => 'application/vnd.eszigno3+xml', 'etx' => 'text/x-setext', 'exe' => 'application/x-msdownload', 'exi' => 'application/exi', 'ext' => 'application/vnd.novadigm.ext', 'ez' => 'application/andrew-inset', 'ez2' => 'application/vnd.ezpix-album', 'ez3' => 'application/vnd.ezpix-package', 'f' => 'text/x-fortran', 'f4v' => 'video/x-f4v', 'f77' => 'text/x-fortran', 'f90' => 'text/x-fortran', 'fbs' => 'image/vnd.fastbidsheet', 'fcs' => 'application/vnd.isac.fcs', 'fdf' => 'application/vnd.fdf', 'fe_launch' => 'application/vnd.denovo.fcselayout-link', 'fg5' => 'application/vnd.fujitsu.oasysgp', 'fgd' => 'application/x-director', 'fh' => 'image/x-freehand', 'fh4' => 'image/x-freehand', 'fh5' => 'image/x-freehand', 'fh7' => 'image/x-freehand', 'fhc' => 'image/x-freehand', 'fig' => 'application/x-xfig', 'fli' => 'video/x-fli', 'flo' => 'application/vnd.micrografx.flo', 'flv' => 'video/x-flv', 'flw' => 'application/vnd.kde.kivio', 'flx' => 'text/vnd.fmi.flexstor', 'fly' => 'text/vnd.fly', 'fm' => 'application/vnd.framemaker', 'fnc' => 'application/vnd.frogans.fnc', 'for' => 'text/x-fortran', 'fpx' => 'image/vnd.fpx', 'frame' => 'application/vnd.framemaker', 'fsc' => 'application/vnd.fsc.weblaunch', 'fst' => 'image/vnd.fst', 'ftc' => 'application/vnd.fluxtime.clip', 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', 'fvt' => 'video/vnd.fvt', 'fxp' => 'application/vnd.adobe.fxp', 'fxpl' => 'application/vnd.adobe.fxp', 'fzs' => 'application/vnd.fuzzysheet', 'g2w' => 'application/vnd.geoplan', 'g3' => 'image/g3fax', 'g3w' => 'application/vnd.geospace', 'gac' => 'application/vnd.groove-account', 'gdl' => 'model/vnd.gdl', 'geo' => 'application/vnd.dynageo', 'gex' => 'application/vnd.geometry-explorer', 'ggb' => 'application/vnd.geogebra.file', 'ggt' => 'application/vnd.geogebra.tool', 'ghf' => 'application/vnd.groove-help', 'gif' => 'image/gif', 'gim' => 'application/vnd.groove-identity-message', 'gmx' => 'application/vnd.gmx', 'gnumeric' => 'application/x-gnumeric', 'gph' => 'application/vnd.flographit', 'gqf' => 'application/vnd.grafeq', 'gqs' => 'application/vnd.grafeq', 'gram' => 'application/srgs', 'gre' => 'application/vnd.geometry-explorer', 'grv' => 'application/vnd.groove-injector', 'grxml' => 'application/srgs+xml', 'gsf' => 'application/x-font-ghostscript', 'gtar' => 'application/x-gtar', 'gtm' => 'application/vnd.groove-tool-message', 'gtw' => 'model/vnd.gtw', 'gv' => 'text/vnd.graphviz', 'gxt' => 'application/vnd.geonext', 'h' => 'text/x-c', 'h261' => 'video/h261', 'h263' => 'video/h263', 'h264' => 'video/h264', 'hal' => 'application/vnd.hal+xml', 'hbci' => 'application/vnd.hbci', 'hdf' => 'application/x-hdf', 'hh' => 'text/x-c', 'hlp' => 'application/winhlp', 'hpgl' => 'application/vnd.hp-hpgl', 'hpid' => 'application/vnd.hp-hpid', 'hps' => 'application/vnd.hp-hps', 'hqx' => 'application/mac-binhex40', 'hta' => 'application/octet-stream', 'htc' => 'text/html', 'htke' => 'application/vnd.kenameaapp', 'htm' => 'text/html', 'html' => 'text/html', 'hvd' => 'application/vnd.yamaha.hv-dic', 'hvp' => 'application/vnd.yamaha.hv-voice', 'hvs' => 'application/vnd.yamaha.hv-script', 'i2g' => 'application/vnd.intergeo', 'icc' => 'application/vnd.iccprofile', 'ice' => 'x-conference/x-cooltalk', 'icm' => 'application/vnd.iccprofile', 'ico' => 'image/x-icon', 'ics' => 'text/calendar', 'ief' => 'image/ief', 'ifb' => 'text/calendar', 'ifm' => 'application/vnd.shana.informed.formdata', 'iges' => 'model/iges', 'igl' => 'application/vnd.igloader', 'igm' => 'application/vnd.insors.igm', 'igs' => 'model/iges', 'igx' => 'application/vnd.micrografx.igx', 'iif' => 'application/vnd.shana.informed.interchange', 'imp' => 'application/vnd.accpac.simply.imp', 'ims' => 'application/vnd.ms-ims', 'in' => 'text/plain', 'ini' => 'text/plain', 'ipfix' => 'application/ipfix', 'ipk' => 'application/vnd.shana.informed.package', 'irm' => 'application/vnd.ibm.rights-management', 'irp' => 'application/vnd.irepository.package+xml', 'iso' => 'application/octet-stream', 'itp' => 'application/vnd.shana.informed.formtemplate', 'ivp' => 'application/vnd.immervision-ivp', 'ivu' => 'application/vnd.immervision-ivu', 'jad' => 'text/vnd.sun.j2me.app-descriptor', 'jam' => 'application/vnd.jam', 'jar' => 'application/java-archive', 'java' => 'text/x-java-source', 'jisp' => 'application/vnd.jisp', 'jlt' => 'application/vnd.hp-jlyt', 'jnlp' => 'application/x-java-jnlp-file', 'joda' => 'application/vnd.joost.joda-archive', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpgm' => 'video/jpm', 'jpgv' => 'video/jpeg', 'jpm' => 'video/jpm', 'js' => 'text/javascript', 'json' => 'application/json', 'kar' => 'audio/midi', 'karbon' => 'application/vnd.kde.karbon', 'kfo' => 'application/vnd.kde.kformula', 'kia' => 'application/vnd.kidspiration', 'kml' => 'application/vnd.google-earth.kml+xml', 'kmz' => 'application/vnd.google-earth.kmz', 'kne' => 'application/vnd.kinar', 'knp' => 'application/vnd.kinar', 'kon' => 'application/vnd.kde.kontour', 'kpr' => 'application/vnd.kde.kpresenter', 'kpt' => 'application/vnd.kde.kpresenter', 'ksp' => 'application/vnd.kde.kspread', 'ktr' => 'application/vnd.kahootz', 'ktx' => 'image/ktx', 'ktz' => 'application/vnd.kahootz', 'kwd' => 'application/vnd.kde.kword', 'kwt' => 'application/vnd.kde.kword', 'lasxml' => 'application/vnd.las.las+xml', 'latex' => 'application/x-latex', 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', 'les' => 'application/vnd.hhe.lesson-player', 'lha' => 'application/octet-stream', 'link66' => 'application/vnd.route66.link66+xml', 'list' => 'text/plain', 'list3820' => 'application/vnd.ibm.modcap', 'listafp' => 'application/vnd.ibm.modcap', 'log' => 'text/plain', 'lostxml' => 'application/lost+xml', 'lrf' => 'application/octet-stream', 'lrm' => 'application/vnd.ms-lrm', 'ltf' => 'application/vnd.frogans.ltf', 'lvp' => 'audio/vnd.lucent.voice', 'lwp' => 'application/vnd.lotus-wordpro', 'lzh' => 'application/octet-stream', 'm13' => 'application/x-msmediaview', 'm14' => 'application/x-msmediaview', 'm1v' => 'video/mpeg', 'm21' => 'application/mp21', 'm2a' => 'audio/mpeg', 'm2v' => 'video/mpeg', 'm3a' => 'audio/mpeg', 'm3u' => 'audio/x-mpegurl', 'm3u8' => 'application/vnd.apple.mpegurl', 'm4a' => 'audio/mp4', 'm4u' => 'video/vnd.mpegurl', 'm4v' => 'video/mp4', 'ma' => 'application/mathematica', 'mads' => 'application/mads+xml', 'mag' => 'application/vnd.ecowin.chart', 'maker' => 'application/vnd.framemaker', 'man' => 'text/troff', 'mathml' => 'application/mathml+xml', 'mb' => 'application/mathematica', 'mbk' => 'application/vnd.mobius.mbk', 'mbox' => 'application/mbox', 'mc1' => 'application/vnd.medcalcdata', 'mcd' => 'application/vnd.mcd', 'mcurl' => 'text/vnd.curl.mcurl', 'mdb' => 'application/x-msaccess', 'mdi' => 'image/vnd.ms-modi', 'me' => 'text/troff', 'mesh' => 'model/mesh', 'meta4' => 'application/metalink4+xml', 'mets' => 'application/mets+xml', 'mfm' => 'application/vnd.mfmp', 'mgp' => 'application/vnd.osgeo.mapguide.package', 'mgz' => 'application/vnd.proteus.magazine', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mif' => 'application/vnd.mif', 'mime' => 'message/rfc822', 'mj2' => 'video/mj2', 'mjp2' => 'video/mj2', 'mlp' => 'application/vnd.dolby.mlp', 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', 'mmf' => 'application/vnd.smaf', 'mmr' => 'image/vnd.fujixerox.edmics-mmr', 'mny' => 'application/x-msmoney', 'mobi' => 'application/x-mobipocket-ebook', 'mods' => 'application/mods+xml', 'mov' => 'video/quicktime', 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg', 'mp21' => 'application/mp21', 'mp2a' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'mp4a' => 'audio/mp4', 'mp4s' => 'application/mp4', 'mp4v' => 'video/mp4', 'mpc' => 'application/vnd.mophun.certificate', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpg4' => 'video/mp4', 'mpga' => 'audio/mpeg', 'mpkg' => 'application/vnd.apple.installer+xml', 'mpm' => 'application/vnd.blueice.multipass', 'mpn' => 'application/vnd.mophun.application', 'mpp' => 'application/vnd.ms-project', 'mpt' => 'application/vnd.ms-project', 'mpy' => 'application/vnd.ibm.minipay', 'mqy' => 'application/vnd.mobius.mqy', 'mrc' => 'application/marc', 'mrcx' => 'application/marcxml+xml', 'ms' => 'text/troff', 'mscml' => 'application/mediaservercontrol+xml', 'mseed' => 'application/vnd.fdsn.mseed', 'mseq' => 'application/vnd.mseq', 'msf' => 'application/vnd.epson.msf', 'msh' => 'model/mesh', 'msi' => 'application/x-msdownload', 'msl' => 'application/vnd.mobius.msl', 'msty' => 'application/vnd.muvee.style', 'mts' => 'model/vnd.mts', 'mus' => 'application/vnd.musician', 'musicxml' => 'application/vnd.recordare.musicxml+xml', 'mvb' => 'application/x-msmediaview', 'mwf' => 'application/vnd.mfer', 'mxf' => 'application/mxf', 'mxl' => 'application/vnd.recordare.musicxml', 'mxml' => 'application/xv+xml', 'mxs' => 'application/vnd.triscape.mxs', 'mxu' => 'video/vnd.mpegurl', 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', 'n3' => 'text/n3', 'nb' => 'application/mathematica', 'nbp' => 'application/vnd.wolfram.player', 'nc' => 'application/x-netcdf', 'ncx' => 'application/x-dtbncx+xml', 'ngdat' => 'application/vnd.nokia.n-gage.data', 'nlu' => 'application/vnd.neurolanguage.nlu', 'nml' => 'application/vnd.enliven', 'nnd' => 'application/vnd.noblenet-directory', 'nns' => 'application/vnd.noblenet-sealer', 'nnw' => 'application/vnd.noblenet-web', 'npx' => 'image/vnd.net-fpx', 'nsf' => 'application/vnd.lotus-notes', 'oa2' => 'application/vnd.fujitsu.oasys2', 'oa3' => 'application/vnd.fujitsu.oasys3', 'oas' => 'application/vnd.fujitsu.oasys', 'obd' => 'application/x-msbinder', 'oda' => 'application/oda', 'odb' => 'application/vnd.oasis.opendocument.database', 'odc' => 'application/vnd.oasis.opendocument.chart', 'odf' => 'application/vnd.oasis.opendocument.formula', 'odft' => 'application/vnd.oasis.opendocument.formula-template', 'odg' => 'application/vnd.oasis.opendocument.graphics', 'odi' => 'application/vnd.oasis.opendocument.image', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'odp' => 'application/vnd.oasis.opendocument.presentation', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'odt' => 'application/vnd.oasis.opendocument.text', 'oga' => 'audio/ogg', 'ogg' => 'audio/ogg', 'ogv' => 'video/ogg', 'ogx' => 'application/ogg', 'onepkg' => 'application/onenote', 'onetmp' => 'application/onenote', 'onetoc' => 'application/onenote', 'onetoc2' => 'application/onenote', 'opf' => 'application/oebps-package+xml', 'oprc' => 'application/vnd.palm', 'org' => 'application/vnd.lotus-organizer', 'osf' => 'application/vnd.yamaha.openscoreformat', 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 'otc' => 'application/vnd.oasis.opendocument.chart-template', 'otf' => 'application/x-font-otf', 'otg' => 'application/vnd.oasis.opendocument.graphics-template', 'oth' => 'application/vnd.oasis.opendocument.text-web', 'oti' => 'application/vnd.oasis.opendocument.image-template', 'otp' => 'application/vnd.oasis.opendocument.presentation-template', 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', 'ott' => 'application/vnd.oasis.opendocument.text-template', 'oxt' => 'application/vnd.openofficeorg.extension', 'p' => 'text/x-pascal', 'p10' => 'application/pkcs10', 'p12' => 'application/x-pkcs12', 'p7b' => 'application/x-pkcs7-certificates', 'p7c' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', 'p7r' => 'application/x-pkcs7-certreqresp', 'p7s' => 'application/pkcs7-signature', 'p8' => 'application/pkcs8', 'pas' => 'text/x-pascal', 'paw' => 'application/vnd.pawaafile', 'pbd' => 'application/vnd.powerbuilder6', 'pbm' => 'image/x-portable-bitmap', 'pcf' => 'application/x-font-pcf', 'pcl' => 'application/vnd.hp-pcl', 'pclxl' => 'application/vnd.hp-pclxl', 'pct' => 'image/x-pict', 'pcurl' => 'application/vnd.curl.pcurl', 'pcx' => 'image/x-pcx', 'pdb' => 'application/vnd.palm', 'pdf' => 'application/pdf', 'pfa' => 'application/x-font-type1', 'pfb' => 'application/x-font-type1', 'pfm' => 'application/x-font-type1', 'pfr' => 'application/font-tdpfr', 'pfx' => 'application/x-pkcs12', 'pgm' => 'image/x-portable-graymap', 'pgn' => 'application/x-chess-pgn', 'pgp' => 'application/pgp-encrypted', 'php' => 'text/x-php', 'phps' => 'application/x-httpd-phps', 'pic' => 'image/x-pict', 'pkg' => 'application/octet-stream', 'pki' => 'application/pkixcmp', 'pkipath' => 'application/pkix-pkipath', 'plb' => 'application/vnd.3gpp.pic-bw-large', 'plc' => 'application/vnd.mobius.plc', 'plf' => 'application/vnd.pocketlearn', 'pls' => 'application/pls+xml', 'pml' => 'application/vnd.ctc-posml', 'png' => 'image/png', 'pnm' => 'image/x-portable-anymap', 'portpkg' => 'application/vnd.macports.portpkg', 'pot' => 'application/vnd.ms-powerpoint', 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', 'ppd' => 'application/vnd.cups-ppd', 'ppm' => 'image/x-portable-pixmap', 'pps' => 'application/vnd.ms-powerpoint', 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'ppt' => 'application/vnd.ms-powerpoint', 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pqa' => 'application/vnd.palm', 'prc' => 'application/x-mobipocket-ebook', 'pre' => 'application/vnd.lotus-freelance', 'prf' => 'application/pics-rules', 'ps' => 'application/postscript', 'psb' => 'application/vnd.3gpp.pic-bw-small', 'psd' => 'image/vnd.adobe.photoshop', 'psf' => 'application/x-font-linux-psf', 'pskcxml' => 'application/pskc+xml', 'ptid' => 'application/vnd.pvi.ptid1', 'pub' => 'application/x-mspublisher', 'pvb' => 'application/vnd.3gpp.pic-bw-var', 'pwn' => 'application/vnd.3m.post-it-notes', 'pya' => 'audio/vnd.ms-playready.media.pya', 'pyv' => 'video/vnd.ms-playready.media.pyv', 'qam' => 'application/vnd.epson.quickanime', 'qbo' => 'application/vnd.intu.qbo', 'qfx' => 'application/vnd.intu.qfx', 'qps' => 'application/vnd.publishare-delta-tree', 'qt' => 'video/quicktime', 'qwd' => 'application/vnd.quark.quarkxpress', 'qwt' => 'application/vnd.quark.quarkxpress', 'qxb' => 'application/vnd.quark.quarkxpress', 'qxd' => 'application/vnd.quark.quarkxpress', 'qxl' => 'application/vnd.quark.quarkxpress', 'qxt' => 'application/vnd.quark.quarkxpress', 'ra' => 'audio/x-pn-realaudio', 'ram' => 'audio/x-pn-realaudio', 'rar' => 'application/x-rar-compressed', 'ras' => 'image/x-cmu-raster', 'rb' => 'text/plain', 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', 'rdf' => 'application/rdf+xml', 'rdz' => 'application/vnd.data-vision.rdz', 'rep' => 'application/vnd.businessobjects', 'res' => 'application/x-dtbresource+xml', 'resx' => 'text/xml', 'rgb' => 'image/x-rgb', 'rif' => 'application/reginfo+xml', 'rip' => 'audio/vnd.rip', 'rl' => 'application/resource-lists+xml', 'rlc' => 'image/vnd.fujixerox.edmics-rlc', 'rld' => 'application/resource-lists-diff+xml', 'rm' => 'application/vnd.rn-realmedia', 'rmi' => 'audio/midi', 'rmp' => 'audio/x-pn-realaudio-plugin', 'rms' => 'application/vnd.jcp.javame.midlet-rms', 'rnc' => 'application/relax-ng-compact-syntax', 'roff' => 'text/troff', 'rp9' => 'application/vnd.cloanto.rp9', 'rpss' => 'application/vnd.nokia.radio-presets', 'rpst' => 'application/vnd.nokia.radio-preset', 'rq' => 'application/sparql-query', 'rs' => 'application/rls-services+xml', 'rsd' => 'application/rsd+xml', 'rss' => 'application/rss+xml', 'rtf' => 'application/rtf', 'rtx' => 'text/richtext', 's' => 'text/x-asm', 'saf' => 'application/vnd.yamaha.smaf-audio', 'sbml' => 'application/sbml+xml', 'sc' => 'application/vnd.ibm.secure-container', 'scd' => 'application/x-msschedule', 'scm' => 'application/vnd.lotus-screencam', 'scq' => 'application/scvp-cv-request', 'scs' => 'application/scvp-cv-response', 'scurl' => 'text/vnd.curl.scurl', 'sda' => 'application/vnd.stardivision.draw', 'sdc' => 'application/vnd.stardivision.calc', 'sdd' => 'application/vnd.stardivision.impress', 'sdkd' => 'application/vnd.solent.sdkm+xml', 'sdkm' => 'application/vnd.solent.sdkm+xml', 'sdp' => 'application/sdp', 'sdw' => 'application/vnd.stardivision.writer', 'see' => 'application/vnd.seemail', 'seed' => 'application/vnd.fdsn.seed', 'sema' => 'application/vnd.sema', 'semd' => 'application/vnd.semd', 'semf' => 'application/vnd.semf', 'ser' => 'application/java-serialized-object', 'setpay' => 'application/set-payment-initiation', 'setreg' => 'application/set-registration-initiation', 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', 'sfs' => 'application/vnd.spotfire.sfs', 'sgl' => 'application/vnd.stardivision.writer-global', 'sgm' => 'text/sgml', 'sgml' => 'text/sgml', 'sh' => 'application/x-sh', 'shar' => 'application/x-shar', 'shf' => 'application/shf+xml', 'sig' => 'application/pgp-signature', 'silo' => 'model/mesh', 'sis' => 'application/vnd.symbian.install', 'sisx' => 'application/vnd.symbian.install', 'sit' => 'application/x-stuffit', 'sitx' => 'application/x-stuffitx', 'skd' => 'application/vnd.koan', 'skm' => 'application/vnd.koan', 'skp' => 'application/vnd.koan', 'skt' => 'application/vnd.koan', 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', 'slt' => 'application/vnd.epson.salt', 'sm' => 'application/vnd.stepmania.stepchart', 'smf' => 'application/vnd.stardivision.math', 'smi' => 'application/smil+xml', 'smil' => 'application/smil+xml', 'snd' => 'audio/basic', 'snf' => 'application/x-font-snf', 'so' => 'application/octet-stream', 'spc' => 'application/x-pkcs7-certificates', 'spf' => 'application/vnd.yamaha.smaf-phrase', 'spl' => 'application/x-futuresplash', 'spot' => 'text/vnd.in3d.spot', 'spp' => 'application/scvp-vp-response', 'spq' => 'application/scvp-vp-request', 'spx' => 'audio/ogg', 'src' => 'application/x-wais-source', 'srt' => 'application/octet-stream', 'sru' => 'application/sru+xml', 'srx' => 'application/sparql-results+xml', 'sse' => 'application/vnd.kodak-descriptor', 'ssf' => 'application/vnd.epson.ssf', 'ssml' => 'application/ssml+xml', 'st' => 'application/vnd.sailingtracker.track', 'stc' => 'application/vnd.sun.xml.calc.template', 'std' => 'application/vnd.sun.xml.draw.template', 'stf' => 'application/vnd.wt.stf', 'sti' => 'application/vnd.sun.xml.impress.template', 'stk' => 'application/hyperstudio', 'stl' => 'application/vnd.ms-pki.stl', 'str' => 'application/vnd.pg.format', 'stw' => 'application/vnd.sun.xml.writer.template', 'sub' => 'image/vnd.dvb.subtitle', 'sus' => 'application/vnd.sus-calendar', 'susp' => 'application/vnd.sus-calendar', 'sv4cpio' => 'application/x-sv4cpio', 'sv4crc' => 'application/x-sv4crc', 'svc' => 'application/vnd.dvb.service', 'svd' => 'application/vnd.svd', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 'swa' => 'application/x-director', 'swf' => 'application/x-shockwave-flash', 'swi' => 'application/vnd.aristanetworks.swi', 'sxc' => 'application/vnd.sun.xml.calc', 'sxd' => 'application/vnd.sun.xml.draw', 'sxg' => 'application/vnd.sun.xml.writer.global', 'sxi' => 'application/vnd.sun.xml.impress', 'sxm' => 'application/vnd.sun.xml.math', 'sxw' => 'application/vnd.sun.xml.writer', 't' => 'text/troff', 'tao' => 'application/vnd.tao.intent-module-archive', 'tar' => 'application/x-tar', 'tcap' => 'application/vnd.3gpp2.tcap', 'tcl' => 'application/x-tcl', 'teacher' => 'application/vnd.smart.teacher', 'tei' => 'application/tei+xml', 'teicorpus' => 'application/tei+xml', 'tex' => 'application/x-tex', 'texi' => 'application/x-texinfo', 'texinfo' => 'application/x-texinfo', 'text' => 'text/plain', 'tfi' => 'application/thraud+xml', 'tfm' => 'application/x-tex-tfm', 'thmx' => 'application/vnd.ms-officetheme', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'tmo' => 'application/vnd.tmobile-livetv', 'torrent' => 'application/x-bittorrent', 'tpl' => 'application/vnd.groove-tool-template', 'tpt' => 'application/vnd.trid.tpt', 'tr' => 'text/troff', 'tra' => 'application/vnd.trueapp', 'trm' => 'application/x-msterminal', 'tsd' => 'application/timestamped-data', 'tsv' => 'text/tab-separated-values', 'ttc' => 'application/x-font-ttf', 'ttf' => 'application/x-font-ttf', 'ttl' => 'text/turtle', 'twd' => 'application/vnd.simtech-mindmapper', 'twds' => 'application/vnd.simtech-mindmapper', 'txd' => 'application/vnd.genomatix.tuxedo', 'txf' => 'application/vnd.mobius.txf', 'txt' => 'text/plain', 'u32' => 'application/x-authorware-bin', 'udeb' => 'application/x-debian-package', 'ufd' => 'application/vnd.ufdl', 'ufdl' => 'application/vnd.ufdl', 'umj' => 'application/vnd.umajin', 'unityweb' => 'application/vnd.unity', 'uoml' => 'application/vnd.uoml+xml', 'uri' => 'text/uri-list', 'uris' => 'text/uri-list', 'urls' => 'text/uri-list', 'ustar' => 'application/x-ustar', 'utz' => 'application/vnd.uiq.theme', 'uu' => 'text/x-uuencode', 'uva' => 'audio/vnd.dece.audio', 'uvd' => 'application/vnd.dece.data', 'uvf' => 'application/vnd.dece.data', 'uvg' => 'image/vnd.dece.graphic', 'uvh' => 'video/vnd.dece.hd', 'uvi' => 'image/vnd.dece.graphic', 'uvm' => 'video/vnd.dece.mobile', 'uvp' => 'video/vnd.dece.pd', 'uvs' => 'video/vnd.dece.sd', 'uvt' => 'application/vnd.dece.ttml+xml', 'uvu' => 'video/vnd.uvvu.mp4', 'uvv' => 'video/vnd.dece.video', 'uvva' => 'audio/vnd.dece.audio', 'uvvd' => 'application/vnd.dece.data', 'uvvf' => 'application/vnd.dece.data', 'uvvg' => 'image/vnd.dece.graphic', 'uvvh' => 'video/vnd.dece.hd', 'uvvi' => 'image/vnd.dece.graphic', 'uvvm' => 'video/vnd.dece.mobile', 'uvvp' => 'video/vnd.dece.pd', 'uvvs' => 'video/vnd.dece.sd', 'uvvt' => 'application/vnd.dece.ttml+xml', 'uvvu' => 'video/vnd.uvvu.mp4', 'uvvv' => 'video/vnd.dece.video', 'uvvx' => 'application/vnd.dece.unspecified', 'uvx' => 'application/vnd.dece.unspecified', 'vcd' => 'application/x-cdlink', 'vcf' => 'text/x-vcard', 'vcg' => 'application/vnd.groove-vcard', 'vcs' => 'text/x-vcalendar', 'vcx' => 'application/vnd.vcx', 'vis' => 'application/vnd.visionary', 'viv' => 'video/vnd.vivo', 'vor' => 'application/vnd.stardivision.writer', 'vox' => 'application/x-authorware-bin', 'vrml' => 'model/vrml', 'vsd' => 'application/vnd.visio', 'vsf' => 'application/vnd.vsf', 'vss' => 'application/vnd.visio', 'vst' => 'application/vnd.visio', 'vsw' => 'application/vnd.visio', 'vtu' => 'model/vnd.vtu', 'vxml' => 'application/voicexml+xml', 'w3d' => 'application/x-director', 'wad' => 'application/x-doom', 'wav' => 'audio/x-wav', 'wax' => 'audio/x-ms-wax', 'wbmp' => 'image/vnd.wap.wbmp', 'wbs' => 'application/vnd.criticaltools.wbs+xml', 'wbxml' => 'application/vnd.wap.wbxml', 'wcm' => 'application/vnd.ms-works', 'wdb' => 'application/vnd.ms-works', 'weba' => 'audio/webm', 'webm' => 'video/webm', 'webp' => 'image/webp', 'wg' => 'application/vnd.pmi.widget', 'wgt' => 'application/widget', 'wks' => 'application/vnd.ms-works', 'wm' => 'video/x-ms-wm', 'wma' => 'audio/x-ms-wma', 'wmd' => 'application/x-ms-wmd', 'wmf' => 'application/x-msmetafile', 'wml' => 'text/vnd.wap.wml', 'wmlc' => 'application/vnd.wap.wmlc', 'wmls' => 'text/vnd.wap.wmlscript', 'wmlsc' => 'application/vnd.wap.wmlscriptc', 'wmv' => 'video/x-ms-wmv', 'wmx' => 'video/x-ms-wmx', 'wmz' => 'application/x-ms-wmz', 'woff' => 'application/x-font-woff', 'wpd' => 'application/vnd.wordperfect', 'wpl' => 'application/vnd.ms-wpl', 'wps' => 'application/vnd.ms-works', 'wqd' => 'application/vnd.wqd', 'wri' => 'application/x-mswrite', 'wrl' => 'model/vrml', 'wsdl' => 'application/wsdl+xml', 'wspolicy' => 'application/wspolicy+xml', 'wtb' => 'application/vnd.webturbo', 'wvx' => 'video/x-ms-wvx', 'x32' => 'application/x-authorware-bin', 'x3d' => 'application/vnd.hzn-3d-crossword', 'xap' => 'application/x-silverlight-app', 'xar' => 'application/vnd.xara', 'xbap' => 'application/x-ms-xbap', 'xbd' => 'application/vnd.fujixerox.docuworks.binder', 'xbm' => 'image/x-xbitmap', 'xdf' => 'application/xcap-diff+xml', 'xdm' => 'application/vnd.syncml.dm+xml', 'xdp' => 'application/vnd.adobe.xdp+xml', 'xdssc' => 'application/dssc+xml', 'xdw' => 'application/vnd.fujixerox.docuworks', 'xenc' => 'application/xenc+xml', 'xer' => 'application/patch-ops-error+xml', 'xfdf' => 'application/vnd.adobe.xfdf', 'xfdl' => 'application/vnd.xfdl', 'xht' => 'application/xhtml+xml', 'xhtml' => 'application/xhtml+xml', 'xhvml' => 'application/xv+xml', 'xif' => 'image/vnd.xiff', 'xla' => 'application/vnd.ms-excel', 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', 'xlc' => 'application/vnd.ms-excel', 'xlm' => 'application/vnd.ms-excel', 'xls' => 'application/vnd.ms-excel', 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlt' => 'application/vnd.ms-excel', 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'xlw' => 'application/vnd.ms-excel', 'xml' => 'application/xml', 'xo' => 'application/vnd.olpc-sugar', 'xop' => 'application/xop+xml', 'xpi' => 'application/x-xpinstall', 'xpm' => 'image/x-xpixmap', 'xpr' => 'application/vnd.is-xpr', 'xps' => 'application/vnd.ms-xpsdocument', 'xpw' => 'application/vnd.intercon.formnet', 'xpx' => 'application/vnd.intercon.formnet', 'xsl' => 'application/xml', 'xslt' => 'application/xslt+xml', 'xsm' => 'application/vnd.syncml+xml', 'xspf' => 'application/xspf+xml', 'xul' => 'application/vnd.mozilla.xul+xml', 'xvm' => 'application/xv+xml', 'xvml' => 'application/xv+xml', 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-xyz', 'yaml' => 'text/yaml', 'yang' => 'application/yang', 'yin' => 'application/yin+xml', 'yml' => 'text/yaml', 'zaz' => 'application/vnd.zzazz.deck+xml', 'zip' => 'application/zip', 'zir' => 'application/vnd.zul', 'zirz' => 'application/vnd.zul', 'zmm' => 'application/vnd.handheld-entertainment+xml' ]; public static function getInstance() { if (!self::$instance) { self::$instance = new self(); } return self::$instance; } public function fromExtension($extension) { $extension = strtolower($extension); return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null; } public function fromFilename($filename) { return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); } } path = $filePath; $this->maxLength = $maxLength; $this->offset = $offset; $this->open(); } public function __destruct() { $this->close(); } public function open() { if (!$this->isRemoteFile($this->path) && !is_readable($this->path)) { throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to read resource: ' . $this->path . '.'); } $this->stream = fopen($this->path, 'r'); if (!$this->stream) { throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to open resource: ' . $this->path . '.'); } } public function close() { if (is_resource($this->stream)) { fclose($this->stream); } } public function getContents() { return stream_get_contents($this->stream, $this->maxLength, $this->offset); } public function getFileName() { return basename($this->path); } public function getFilePath() { return $this->path; } public function getSize() { return filesize($this->path); } public function getMimetype() { return Mimetypes::getInstance()->fromFilename($this->path) ?: 'text/plain'; } protected function isRemoteFile($pathToFile) { return preg_match('/^(https?|ftp):\/\/.*/', $pathToFile) === 1; } } app = $app; $this->client = $client; $this->accessToken = $accessToken; $this->graphVersion = $graphVersion; } public function start($endpoint, FacebookFile $file) { $params = [ 'upload_phase' => 'start', 'file_size' => $file->getSize(), ]; $response = $this->sendUploadRequest($endpoint, $params); return new FacebookTransferChunk($file, $response['upload_session_id'], $response['video_id'], $response['start_offset'], $response['end_offset']); } public function transfer($endpoint, FacebookTransferChunk $chunk, $allowToThrow = false) { $params = [ 'upload_phase' => 'transfer', 'upload_session_id' => $chunk->getUploadSessionId(), 'start_offset' => $chunk->getStartOffset(), 'video_file_chunk' => $chunk->getPartialFile(), ]; try { $response = $this->sendUploadRequest($endpoint, $params); } catch (FacebookResponseException $e) { $preException = $e->getPrevious(); if ($allowToThrow || !$preException instanceof FacebookResumableUploadException) { throw $e; } return $chunk; } return new FacebookTransferChunk($chunk->getFile(), $chunk->getUploadSessionId(), $chunk->getVideoId(), $response['start_offset'], $response['end_offset']); } public function finish($endpoint, $uploadSessionId, $metadata = []) { $params = array_merge($metadata, [ 'upload_phase' => 'finish', 'upload_session_id' => $uploadSessionId, ]); $response = $this->sendUploadRequest($endpoint, $params); return $response['success']; } private function sendUploadRequest($endpoint, $params = []) { $request = new FacebookRequest($this->app, $this->accessToken, 'POST', $endpoint, $params, null, $this->graphVersion); return $this->client->sendRequest($request)->getDecodedBody(); } } app = $app; $this->client = $client; $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; } public function getLastRequest() { return $this->lastRequest; } public function debugToken($accessToken) { $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; $params = ['input_token' => $accessToken]; $this->lastRequest = new FacebookRequest( $this->app, $this->app->getAccessToken(), 'GET', '/debug_token', $params, null, $this->graphVersion ); $response = $this->client->sendRequest($this->lastRequest); $metadata = $response->getDecodedBody(); return new AccessTokenMetadata($metadata); } public function getAuthorizationUrl($redirectUrl, $state, array $scope = [], array $params = [], $separator = '&') { $params += [ 'client_id' => $this->app->getId(), 'state' => $state, 'response_type' => 'code', 'sdk' => 'php-sdk-' . Facebook::VERSION, 'redirect_uri' => $redirectUrl, 'scope' => implode(',', $scope) ]; return static::BASE_AUTHORIZATION_URL . '/' . $this->graphVersion . '/dialog/oauth?' . http_build_query($params, null, $separator); } public function getAccessTokenFromCode($code, $redirectUri = '') { $params = [ 'code' => $code, 'redirect_uri' => $redirectUri, ]; return $this->requestAnAccessToken($params); } public function getLongLivedAccessToken($accessToken) { $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; $params = [ 'grant_type' => 'fb_exchange_token', 'fb_exchange_token' => $accessToken, ]; return $this->requestAnAccessToken($params); } public function getCodeFromLongLivedAccessToken($accessToken, $redirectUri = '') { $params = [ 'redirect_uri' => $redirectUri, ]; $response = $this->sendRequestWithClientParams('/oauth/client_code', $params, $accessToken); $data = $response->getDecodedBody(); if (!isset($data['code'])) { throw new FacebookSDKException('Code was not returned from Graph.', 401); } return $data['code']; } protected function requestAnAccessToken(array $params) { $response = $this->sendRequestWithClientParams('/oauth/access_token', $params); $data = $response->getDecodedBody(); if (!isset($data['access_token'])) { throw new FacebookSDKException('Access token was not returned from Graph.', 401); } $expiresAt = 0; if (isset($data['expires'])) { $expiresAt = time() + $data['expires']; } elseif (isset($data['expires_in'])) { $expiresAt = time() + $data['expires_in']; } return new AccessToken($data['access_token'], $expiresAt); } protected function sendRequestWithClientParams($endpoint, array $params, $accessToken = null) { $params += $this->getClientParams(); $accessToken = $accessToken ?: $this->app->getAccessToken(); $this->lastRequest = new FacebookRequest( $this->app, $accessToken, 'GET', $endpoint, $params, null, $this->graphVersion ); return $this->client->sendRequest($this->lastRequest); } protected function getClientParams() { return [ 'client_id' => $this->app->getId(), 'client_secret' => $this->app->getSecret(), ]; } } metadata = $metadata['data']; $this->castTimestampsToDateTime(); } public function getField($field, $default = null) { if (isset($this->metadata[$field])) { return $this->metadata[$field]; } return $default; } public function getProperty($field, $default = null) { return $this->getField($field, $default); } public function getChildProperty($parentField, $field, $default = null) { if (!isset($this->metadata[$parentField])) { return $default; } if (!isset($this->metadata[$parentField][$field])) { return $default; } return $this->metadata[$parentField][$field]; } public function getErrorProperty($field, $default = null) { return $this->getChildProperty('error', $field, $default); } public function getMetadataProperty($field, $default = null) { return $this->getChildProperty('metadata', $field, $default); } public function getAppId() { return $this->getField('app_id'); } public function getApplication() { return $this->getField('application'); } public function isError() { return $this->getField('error') !== null; } public function getErrorCode() { return $this->getErrorProperty('code'); } public function getErrorMessage() { return $this->getErrorProperty('message'); } public function getErrorSubcode() { return $this->getErrorProperty('subcode'); } public function getExpiresAt() { return $this->getField('expires_at'); } public function getIsValid() { return $this->getField('is_valid'); } public function getIssuedAt() { return $this->getField('issued_at'); } public function getMetadata() { return $this->getField('metadata'); } public function getSso() { return $this->getMetadataProperty('sso'); } public function getAuthType() { return $this->getMetadataProperty('auth_type'); } public function getAuthNonce() { return $this->getMetadataProperty('auth_nonce'); } public function getProfileId() { return $this->getField('profile_id'); } public function getScopes() { return $this->getField('scopes'); } public function getUserId() { return $this->getField('user_id'); } public function validateAppId($appId) { if ($this->getAppId() !== $appId) { throw new FacebookSDKException('Access token metadata contains unexpected app ID.', 401); } } public function validateUserId($userId) { if ($this->getUserId() !== $userId) { throw new FacebookSDKException('Access token metadata contains unexpected user ID.', 401); } } public function validateExpiration() { if (!$this->getExpiresAt() instanceof \DateTime) { return; } if ($this->getExpiresAt()->getTimestamp() < time()) { throw new FacebookSDKException('Inspection of access token metadata shows that the access token has expired.', 401); } } private function convertTimestampToDateTime($timestamp) { $dt = new \DateTime(); $dt->setTimestamp($timestamp); return $dt; } private function castTimestampsToDateTime() { foreach (static::$dateProperties as $key) { if (isset($this->metadata[$key]) && $this->metadata[$key] !== 0) { $this->metadata[$key] = $this->convertTimestampToDateTime($this->metadata[$key]); } } } } value = $accessToken; if ($expiresAt) { $this->setExpiresAtFromTimeStamp($expiresAt); } } public function getAppSecretProof($appSecret) { return hash_hmac('sha256', $this->value, $appSecret); } public function getExpiresAt() { return $this->expiresAt; } public function isAppAccessToken() { return strpos($this->value, '|') !== false; } public function isLongLived() { if ($this->expiresAt) { return $this->expiresAt->getTimestamp() > time() + (60 * 60 * 2); } if ($this->isAppAccessToken()) { return true; } return false; } public function isExpired() { if ($this->getExpiresAt() instanceof \DateTime) { return $this->getExpiresAt()->getTimestamp() < time(); } if ($this->isAppAccessToken()) { return false; } return null; } public function getValue() { return $this->value; } public function __toString() { return $this->getValue(); } protected function setExpiresAtFromTimeStamp($timeStamp) { $dt = new \DateTime(); $dt->setTimestamp($timeStamp); $this->expiresAt = $dt; } } app = $facebookApp; if (!$rawSignedRequest) { return; } $this->rawSignedRequest = $rawSignedRequest; $this->parse(); } public function getRawSignedRequest() { return $this->rawSignedRequest; } public function getPayload() { return $this->payload; } public function get($key, $default = null) { if (isset($this->payload[$key])) { return $this->payload[$key]; } return $default; } public function getUserId() { return $this->get('user_id'); } public function hasOAuthData() { return $this->get('oauth_token') || $this->get('code'); } public function make(array $payload) { $payload['algorithm'] = isset($payload['algorithm']) ? $payload['algorithm'] : 'HMAC-SHA256'; $payload['issued_at'] = isset($payload['issued_at']) ? $payload['issued_at'] : time(); $encodedPayload = $this->base64UrlEncode(json_encode($payload)); $hashedSig = $this->hashSignature($encodedPayload); $encodedSig = $this->base64UrlEncode($hashedSig); return $encodedSig . '.' . $encodedPayload; } protected function parse() { list($encodedSig, $encodedPayload) = $this->split(); $sig = $this->decodeSignature($encodedSig); $hashedSig = $this->hashSignature($encodedPayload); $this->validateSignature($hashedSig, $sig); $this->payload = $this->decodePayload($encodedPayload); $this->validateAlgorithm(); } protected function split() { if (strpos($this->rawSignedRequest, '.') === false) { throw new FacebookSDKException('Malformed signed request.', 606); } return explode('.', $this->rawSignedRequest, 2); } protected function decodeSignature($encodedSig) { $sig = $this->base64UrlDecode($encodedSig); if (!$sig) { throw new FacebookSDKException('Signed request has malformed encoded signature data.', 607); } return $sig; } protected function decodePayload($encodedPayload) { $payload = $this->base64UrlDecode($encodedPayload); if ($payload) { $payload = json_decode($payload, true); } if (!is_array($payload)) { throw new FacebookSDKException('Signed request has malformed encoded payload data.', 607); } return $payload; } protected function validateAlgorithm() { if ($this->get('algorithm') !== 'HMAC-SHA256') { throw new FacebookSDKException('Signed request is using the wrong algorithm.', 605); } } protected function hashSignature($encodedData) { $hashedSig = hash_hmac( 'sha256', $encodedData, $this->app->getSecret(), $raw_output = true ); if (!$hashedSig) { throw new FacebookSDKException('Unable to hash signature from encoded payload data.', 602); } return $hashedSig; } protected function validateSignature($hashedSig, $sig) { if (\hash_equals($hashedSig, $sig)) { return; } throw new FacebookSDKException('Signed request has an invalid signature.', 602); } public function base64UrlDecode($input) { $urlDecodedBase64 = strtr($input, '-_', '+/'); $this->validateBase64($urlDecodedBase64); return base64_decode($urlDecodedBase64); } public function base64UrlEncode($input) { return strtr(base64_encode($input), '+/', '-_'); } protected function validateBase64($input) { if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $input)) { throw new FacebookSDKException('Signed request contains malformed base64 encoding.', 608); } } } batchRequest = $batchRequest; $request = $response->getRequest(); $body = $response->getBody(); $httpStatusCode = $response->getHttpStatusCode(); $headers = $response->getHeaders(); parent::__construct($request, $body, $httpStatusCode, $headers); $responses = $response->getDecodedBody(); $this->setResponses($responses); } public function getResponses() { return $this->responses; } public function setResponses(array $responses) { $this->responses = []; foreach ($responses as $key => $graphResponse) { $this->addResponse($key, $graphResponse); } } public function addResponse($key, $response) { $originalRequestName = isset($this->batchRequest[$key]['name']) ? $this->batchRequest[$key]['name'] : $key; $originalRequest = isset($this->batchRequest[$key]['request']) ? $this->batchRequest[$key]['request'] : null; $httpResponseBody = isset($response['body']) ? $response['body'] : null; $httpResponseCode = isset($response['code']) ? $response['code'] : null; $httpResponseHeaders = isset($response['headers']) ? $this->normalizeBatchHeaders($response['headers']) : []; $this->responses[$originalRequestName] = new FacebookResponse( $originalRequest, $httpResponseBody, $httpResponseCode, $httpResponseHeaders ); } public function getIterator() { return new ArrayIterator($this->responses); } public function offsetSet($offset, $value) { $this->addResponse($offset, $value); } public function offsetExists($offset) { return isset($this->responses[$offset]); } public function offsetUnset($offset) { unset($this->responses[$offset]); } public function offsetGet($offset) { return isset($this->responses[$offset]) ? $this->responses[$offset] : null; } private function normalizeBatchHeaders(array $batchHeaders) { $headers = []; foreach ($batchHeaders as $header) { $headers[$header['name']] = $header['value']; } return $headers; } } setApp($app); $this->setAccessToken($accessToken); $this->setMethod($method); $this->setEndpoint($endpoint); $this->setParams($params); $this->setETag($eTag); $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; } public function setAccessToken($accessToken) { $this->accessToken = $accessToken; if ($accessToken instanceof AccessToken) { $this->accessToken = $accessToken->getValue(); } return $this; } public function setAccessTokenFromParams($accessToken) { $existingAccessToken = $this->getAccessToken(); if (!$existingAccessToken) { $this->setAccessToken($accessToken); } elseif ($accessToken !== $existingAccessToken) { throw new FacebookSDKException('Access token mismatch. The access token provided in the FacebookRequest and the one provided in the URL or POST params do not match.'); } return $this; } public function getAccessToken() { return $this->accessToken; } public function getAccessTokenEntity() { return $this->accessToken ? new AccessToken($this->accessToken) : null; } public function setApp(FacebookApp $app = null) { $this->app = $app; } public function getApp() { return $this->app; } public function getAppSecretProof() { if (!$accessTokenEntity = $this->getAccessTokenEntity()) { return null; } return $accessTokenEntity->getAppSecretProof($this->app->getSecret()); } public function validateAccessToken() { $accessToken = $this->getAccessToken(); if (!$accessToken) { throw new FacebookSDKException('You must provide an access token.'); } } public function setMethod($method) { $this->method = strtoupper($method); } public function getMethod() { return $this->method; } public function validateMethod() { if (!$this->method) { throw new FacebookSDKException('HTTP method not specified.'); } if (!in_array($this->method, ['GET', 'POST', 'DELETE'])) { throw new FacebookSDKException('Invalid HTTP method specified.'); } } public function setEndpoint($endpoint) { $params = FacebookUrlManipulator::getParamsAsArray($endpoint); if (isset($params['access_token'])) { $this->setAccessTokenFromParams($params['access_token']); } $filterParams = ['access_token', 'appsecret_proof']; $this->endpoint = FacebookUrlManipulator::removeParamsFromUrl($endpoint, $filterParams); return $this; } public function getEndpoint() { return $this->endpoint; } public function getHeaders() { $headers = static::getDefaultHeaders(); if ($this->eTag) { $headers['If-None-Match'] = $this->eTag; } return array_merge($this->headers, $headers); } public function setHeaders(array $headers) { $this->headers = array_merge($this->headers, $headers); } public function setETag($eTag) { $this->eTag = $eTag; } public function setParams(array $params = []) { if (isset($params['access_token'])) { $this->setAccessTokenFromParams($params['access_token']); } unset($params['access_token'], $params['appsecret_proof']); $params = $this->sanitizeFileParams($params); $this->dangerouslySetParams($params); return $this; } public function dangerouslySetParams(array $params = []) { $this->params = array_merge($this->params, $params); return $this; } public function sanitizeFileParams(array $params) { foreach ($params as $key => $value) { if ($value instanceof FacebookFile) { $this->addFile($key, $value); unset($params[$key]); } } return $params; } public function addFile($key, FacebookFile $file) { $this->files[$key] = $file; } public function resetFiles() { $this->files = []; } public function getFiles() { return $this->files; } public function containsFileUploads() { return !empty($this->files); } public function containsVideoUploads() { foreach ($this->files as $file) { if ($file instanceof FacebookVideo) { return true; } } return false; } public function getMultipartBody() { $params = $this->getPostParams(); return new RequestBodyMultipart($params, $this->files); } public function getUrlEncodedBody() { $params = $this->getPostParams(); return new RequestBodyUrlEncoded($params); } public function getParams() { $params = $this->params; $accessToken = $this->getAccessToken(); if ($accessToken) { $params['access_token'] = $accessToken; $params['appsecret_proof'] = $this->getAppSecretProof(); } return $params; } public function getPostParams() { if ($this->getMethod() === 'POST') { return $this->getParams(); } return []; } public function getGraphVersion() { return $this->graphVersion; } public function getUrl() { $this->validateMethod(); $graphVersion = FacebookUrlManipulator::forceSlashPrefix($this->graphVersion); $endpoint = FacebookUrlManipulator::forceSlashPrefix($this->getEndpoint()); $url = $graphVersion . $endpoint; if ($this->getMethod() !== 'POST') { $params = $this->getParams(); $url = FacebookUrlManipulator::appendParamsToUrl($url, $params); } return $url; } public static function getDefaultHeaders() { return [ 'User-Agent' => 'fb-php-' . Facebook::VERSION, 'Accept-Encoding' => '*', ]; } } curl = curl_init(); } public function setopt($key, $value) { curl_setopt($this->curl, $key, $value); } public function setoptArray(array $options) { curl_setopt_array($this->curl, $options); } public function exec() { return curl_exec($this->curl); } public function errno() { return curl_errno($this->curl); } public function error() { return curl_error($this->curl); } public function getinfo($type) { return curl_getinfo($this->curl, $type); } public function version() { return curl_version(); } public function close() { curl_close($this->curl); } } stream = stream_context_create($options); } public function getResponseHeaders() { return $this->responseHeaders; } public function fileGetContents($url) { $rawResponse = file_get_contents($url, false, $this->stream); $this->responseHeaders = $http_response_header ?: []; return $rawResponse; } } facebookStream = $facebookStream ?: new FacebookStream(); } public function send($url, $method, $body, array $headers, $timeOut) { $options = [ 'http' => [ 'method' => $method, 'header' => $this->compileHeader($headers), 'content' => $body, 'timeout' => $timeOut, 'ignore_errors' => true ], 'ssl' => [ 'verify_peer' => true, 'verify_peer_name' => true, 'allow_self_signed' => true, 'cafile' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', ], ]; $this->facebookStream->streamContextCreate($options); $rawBody = $this->facebookStream->fileGetContents($url); $rawHeaders = $this->facebookStream->getResponseHeaders(); if ($rawBody === false || empty($rawHeaders)) { throw new FacebookSDKException('Stream returned an empty response', 660); } $rawHeaders = implode("\r\n", $rawHeaders); return new GraphRawResponse($rawHeaders, $rawBody); } public function compileHeader(array $headers) { $header = []; foreach ($headers as $k => $v) { $header[] = $k . ': ' . $v; } return implode("\r\n", $header); } } facebookCurl = $facebookCurl ?: new FacebookCurl(); } public function send($url, $method, $body, array $headers, $timeOut) { $this->openConnection($url, $method, $body, $headers, $timeOut); $this->sendRequest(); if ($curlErrorCode = $this->facebookCurl->errno()) { throw new FacebookSDKException($this->facebookCurl->error(), $curlErrorCode); } list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody(); $this->closeConnection(); return new GraphRawResponse($rawHeaders, $rawBody); } public function openConnection($url, $method, $body, array $headers, $timeOut) { $options = [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $this->compileRequestHeaders($headers), CURLOPT_URL => $url, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => $timeOut, CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_CAINFO => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', ]; if ($method !== "GET") { $options[CURLOPT_POSTFIELDS] = $body; } $this->facebookCurl->init(); $this->facebookCurl->setoptArray($options); } public function closeConnection() { $this->facebookCurl->close(); } public function sendRequest() { $this->rawResponse = $this->facebookCurl->exec(); } public function compileRequestHeaders(array $headers) { $return = []; foreach ($headers as $key => $value) { $return[] = $key . ': ' . $value; } return $return; } public function extractResponseHeadersAndBody() { $parts = explode("\r\n\r\n", $this->rawResponse); $rawBody = array_pop($parts); $rawHeaders = implode("\r\n\r\n", $parts); return [trim($rawHeaders), trim($rawBody)]; } } guzzleClient = $guzzleClient ?: new Client(); } public function send($url, $method, $body, array $headers, $timeOut) { $options = [ 'headers' => $headers, 'body' => $body, 'timeout' => $timeOut, 'connect_timeout' => 10, 'verify' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', ]; $request = $this->guzzleClient->createRequest($method, $url, $options); try { $rawResponse = $this->guzzleClient->send($request); } catch (RequestException $e) { $rawResponse = $e->getResponse(); if ($e->getPrevious() instanceof RingException || !$rawResponse instanceof ResponseInterface) { throw new FacebookSDKException($e->getMessage(), $e->getCode()); } } $rawHeaders = $this->getHeadersAsString($rawResponse); $rawBody = $rawResponse->getBody(); $httpStatusCode = $rawResponse->getStatusCode(); return new GraphRawResponse($rawHeaders, $rawBody, $httpStatusCode); } public function getHeadersAsString(ResponseInterface $response) { $headers = $response->getHeaders(); $rawHeaders = []; foreach ($headers as $name => $values) { $rawHeaders[] = $name . ": " . implode(", ", $values); } return implode("\r\n", $rawHeaders); } } 0) { $query = '?' . http_build_query($params, null, '&'); } } $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : ''; $host = isset($parts['host']) ? $parts['host'] : ''; $port = isset($parts['port']) ? ':' . $parts['port'] : ''; $path = isset($parts['path']) ? $parts['path'] : ''; $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : ''; return $scheme . $host . $port . $path . $query . $fragment; } public static function appendParamsToUrl($url, array $newParams = []) { if (empty($newParams)) { return $url; } if (strpos($url, '?') === false) { return $url . '?' . http_build_query($newParams, null, '&'); } list($path, $query) = explode('?', $url, 2); $existingParams = []; parse_str($query, $existingParams); $newParams = array_merge($newParams, $existingParams); ksort($newParams); return $path . '?' . http_build_query($newParams, null, '&'); } public static function getParamsAsArray($url) { $query = parse_url($url, PHP_URL_QUERY); if (!$query) { return []; } $params = []; parse_str($query, $params); return $params; } public static function mergeUrlParams($urlToStealFrom, $urlToAddTo) { $newParams = static::getParamsAsArray($urlToStealFrom); if (!$newParams) { return $urlToAddTo; } return static::appendParamsToUrl($urlToAddTo, $newParams); } public static function forceSlashPrefix($string) { if (!$string) { return $string; } return strpos($string, '/') === 0 ? $string : '/' . $string; } public static function baseGraphUrlEndpoint($urlToTrim) { return '/' . preg_replace('/^https:\/\/.+\.facebook\.com(\/v.+?)?\//', '', $urlToTrim); } } getHttpScheme() . '://' . $this->getHostName() . $this->getServerVar('REQUEST_URI'); } protected function getHttpScheme() { return $this->isBehindSsl() ? 'https' : 'http'; } protected function isBehindSsl() { $protocol = $this->getHeader('X_FORWARDED_PROTO'); if ($protocol) { return $this->protocolWithActiveSsl($protocol); } $protocol = $this->getServerVar('HTTPS'); if ($protocol) { return $this->protocolWithActiveSsl($protocol); } return (string)$this->getServerVar('SERVER_PORT') === '443'; } protected function protocolWithActiveSsl($protocol) { $protocol = strtolower((string)$protocol); return in_array($protocol, ['on', '1', 'https', 'ssl'], true); } protected function getHostName() { $header = $this->getHeader('X_FORWARDED_HOST'); if ($header && $this->isValidForwardedHost($header)) { $elements = explode(',', $header); $host = $elements[count($elements) - 1]; } elseif (!$host = $this->getHeader('HOST')) { if (!$host = $this->getServerVar('SERVER_NAME')) { $host = $this->getServerVar('SERVER_ADDR'); } } $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); $scheme = $this->getHttpScheme(); $port = $this->getCurrentPort(); $appendPort = ':' . $port; if (($scheme == 'http' && $port == '80') || ($scheme == 'https' && $port == '443')) { $appendPort = ''; } return $host . $appendPort; } protected function getCurrentPort() { $port = $this->getHeader('X_FORWARDED_PORT'); if ($port) { return (string)$port; } $protocol = (string)$this->getHeader('X_FORWARDED_PROTO'); if ($protocol === 'https') { return '443'; } return (string)$this->getServerVar('SERVER_PORT'); } protected function getServerVar($key) { return isset($_SERVER[$key]) ? $_SERVER[$key] : ''; } protected function getHeader($key) { return $this->getServerVar('HTTP_' . $key); } protected function isValidForwardedHost($header) { $elements = explode(',', $header); $host = $elements[count($elements) - 1]; return preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $host) && 0 < strlen($host) && strlen($host) < 254 && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $host); } } sessionPrefix . $key])) { return $_SESSION[$this->sessionPrefix . $key]; } return null; } public function set($key, $value) { $_SESSION[$this->sessionPrefix . $key] = $value; } } sessionData[$key]) ? $this->sessionData[$key] : null; } public function set($key, $value) { $this->sessionData[$key] = $value; } } response = $response; $this->responseData = $response->getDecodedBody(); $errorMessage = $this->get('message', 'Unknown error from Graph.'); $errorCode = $this->get('code', -1); parent::__construct($errorMessage, $errorCode, $previousException); } public static function create(FacebookResponse $response) { $data = $response->getDecodedBody(); if (!isset($data['error']['code']) && isset($data['code'])) { $data = ['error' => $data]; } $code = isset($data['error']['code']) ? $data['error']['code'] : null; $message = isset($data['error']['message']) ? $data['error']['message'] : 'Unknown error from Graph.'; if (isset($data['error']['error_subcode'])) { switch ($data['error']['error_subcode']) { case 458: case 459: case 460: case 463: case 464: case 467: return new static($response, new FacebookAuthenticationException($message, $code)); case 1363030: case 1363019: case 1363037: case 1363033: case 1363021: case 1363041: return new static($response, new FacebookResumableUploadException($message, $code)); } } switch ($code) { case 100: case 102: case 190: return new static($response, new FacebookAuthenticationException($message, $code)); case 1: case 2: return new static($response, new FacebookServerException($message, $code)); case 4: case 17: case 341: return new static($response, new FacebookThrottleException($message, $code)); case 506: return new static($response, new FacebookClientException($message, $code)); } if ($code == 10 || ($code >= 200 && $code <= 299)) { return new static($response, new FacebookAuthorizationException($message, $code)); } if (isset($data['error']['type']) && $data['error']['type'] === 'OAuthException') { return new static($response, new FacebookAuthenticationException($message, $code)); } return new static($response, new FacebookOtherException($message, $code)); } private function get($key, $default = null) { if (isset($this->responseData['error'][$key])) { return $this->responseData['error'][$key]; } return $default; } public function getHttpStatusCode() { return $this->response->getHttpStatusCode(); } public function getSubErrorCode() { return $this->get('error_subcode', -1); } public function getErrorType() { return $this->get('type', ''); } public function getRawResponse() { return $this->response->getBody(); } public function getResponseData() { return $this->responseData; } public function getResponse() { return $this->response; } } id = (string) $id; $this->secret = $secret; } public function getId() { return $this->id; } public function getSecret() { return $this->secret; } public function getAccessToken() { return new AccessToken($this->id . '|' . $this->secret); } public function serialize() { return implode('|', [$this->id, $this->secret]); } public function unserialize($serialized) { list($id, $secret) = explode('|', $serialized); $this->__construct($id, $secret); } } add($requests); } public function add($request, $options = null) { if (is_array($request)) { foreach ($request as $key => $req) { $this->add($req, $key); } return $this; } if (!$request instanceof FacebookRequest) { throw new \InvalidArgumentException('Argument for add() must be of type array or FacebookRequest.'); } if (null === $options) { $options = []; } elseif (!is_array($options)) { $options = ['name' => $options]; } $this->addFallbackDefaults($request); $attachedFiles = $this->extractFileAttachments($request); $name = isset($options['name']) ? $options['name'] : null; unset($options['name']); $requestToAdd = [ 'name' => $name, 'request' => $request, 'options' => $options, 'attached_files' => $attachedFiles, ]; $this->requests[] = $requestToAdd; return $this; } public function addFallbackDefaults(FacebookRequest $request) { if (!$request->getApp()) { $app = $this->getApp(); if (!$app) { throw new FacebookSDKException('Missing FacebookApp on FacebookRequest and no fallback detected on FacebookBatchRequest.'); } $request->setApp($app); } if (!$request->getAccessToken()) { $accessToken = $this->getAccessToken(); if (!$accessToken) { throw new FacebookSDKException('Missing access token on FacebookRequest and no fallback detected on FacebookBatchRequest.'); } $request->setAccessToken($accessToken); } } public function extractFileAttachments(FacebookRequest $request) { if (!$request->containsFileUploads()) { return null; } $files = $request->getFiles(); $fileNames = []; foreach ($files as $file) { $fileName = uniqid(); $this->addFile($fileName, $file); $fileNames[] = $fileName; } $request->resetFiles(); return implode(',', $fileNames); } public function getRequests() { return $this->requests; } public function prepareRequestsForBatch() { $this->validateBatchRequestCount(); $params = [ 'batch' => $this->convertRequestsToJson(), 'include_headers' => true, ]; $this->setParams($params); } public function convertRequestsToJson() { $requests = []; foreach ($this->requests as $request) { $options = []; if (null !== $request['name']) { $options['name'] = $request['name']; } $options += $request['options']; $requests[] = $this->requestEntityToBatchArray($request['request'], $options, $request['attached_files']); } return json_encode($requests); } public function validateBatchRequestCount() { $batchCount = count($this->requests); if ($batchCount === 0) { throw new FacebookSDKException('There are no batch requests to send.'); } elseif ($batchCount > 50) { throw new FacebookSDKException('You cannot send more than 50 batch requests at a time.'); } } public function requestEntityToBatchArray(FacebookRequest $request, $options = null, $attachedFiles = null) { if (null === $options) { $options = []; } elseif (!is_array($options)) { $options = ['name' => $options]; } $compiledHeaders = []; $headers = $request->getHeaders(); foreach ($headers as $name => $value) { $compiledHeaders[] = $name . ': ' . $value; } $batch = [ 'headers' => $compiledHeaders, 'method' => $request->getMethod(), 'relative_url' => $request->getUrl(), ]; $body = $request->getUrlEncodedBody()->getBody(); if ($body) { $batch['body'] = $body; } $batch += $options; if (null !== $attachedFiles) { $batch['attached_files'] = $attachedFiles; } return $batch; } public function getIterator() { return new ArrayIterator($this->requests); } public function offsetSet($offset, $value) { $this->add($value, $offset); } public function offsetExists($offset) { return isset($this->requests[$offset]); } public function offsetUnset($offset) { unset($this->requests[$offset]); } public function offsetGet($offset) { return isset($this->requests[$offset]) ? $this->requests[$offset] : null; } } validateLength($length); return $this->binToHex(random_bytes($length), $length); } } validateLength($length); $binaryString = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); if ($binaryString === false) { throw new FacebookSDKException( static::ERROR_MESSAGE . 'mcrypt_create_iv() returned an error.' ); } return $this->binToHex($binaryString, $length); } } validateLength($length); $stream = fopen('/dev/urandom', 'rb'); if (!is_resource($stream)) { throw new FacebookSDKException( static::ERROR_MESSAGE . 'Unable to open stream to /dev/urandom.' ); } if (!defined('HHVM_VERSION')) { stream_set_read_buffer($stream, 0); } $binaryString = fread($stream, $length); fclose($stream); if (!$binaryString) { throw new FacebookSDKException( static::ERROR_MESSAGE . 'Stream to /dev/urandom returned no data.' ); } return $this->binToHex($binaryString, $length); } } validateLength($length); $wasCryptographicallyStrong = false; $binaryString = openssl_random_pseudo_bytes($length, $wasCryptographicallyStrong); if ($binaryString === false) { throw new FacebookSDKException(static::ERROR_MESSAGE . 'openssl_random_pseudo_bytes() returned an unknown error.'); } if ($wasCryptographicallyStrong !== true) { throw new FacebookSDKException(static::ERROR_MESSAGE . 'openssl_random_pseudo_bytes() returned a pseudo-random string but it was not cryptographically secure and cannot be used.'); } return $this->binToHex($binaryString, $length); } } getenv(static::APP_ID_ENV_NAME), 'app_secret' => getenv(static::APP_SECRET_ENV_NAME), 'default_graph_version' => static::DEFAULT_GRAPH_VERSION, 'enable_beta_mode' => false, 'http_client_handler' => null, 'persistent_data_handler' => null, 'pseudo_random_string_generator' => null, 'url_detection_handler' => null, ], $config); if (!$config['app_id']) { throw new FacebookSDKException('Required "app_id" key not supplied in config and could not find fallback environment variable "' . static::APP_ID_ENV_NAME . '"'); } if (!$config['app_secret']) { throw new FacebookSDKException('Required "app_secret" key not supplied in config and could not find fallback environment variable "' . static::APP_SECRET_ENV_NAME . '"'); } $this->app = new FacebookApp($config['app_id'], $config['app_secret']); $this->client = new FacebookClient( HttpClientsFactory::createHttpClient($config['http_client_handler']), $config['enable_beta_mode'] ); $this->pseudoRandomStringGenerator = PseudoRandomStringGeneratorFactory::createPseudoRandomStringGenerator( $config['pseudo_random_string_generator'] ); $this->setUrlDetectionHandler($config['url_detection_handler'] ?: new FacebookUrlDetectionHandler()); $this->persistentDataHandler = PersistentDataFactory::createPersistentDataHandler( $config['persistent_data_handler'] ); if (isset($config['default_access_token'])) { $this->setDefaultAccessToken($config['default_access_token']); } $this->defaultGraphVersion = $config['default_graph_version']; } public function getApp() { return $this->app; } public function getClient() { return $this->client; } public function getOAuth2Client() { if (!$this->oAuth2Client instanceof OAuth2Client) { $app = $this->getApp(); $client = $this->getClient(); $this->oAuth2Client = new OAuth2Client($app, $client, $this->defaultGraphVersion); } return $this->oAuth2Client; } public function getLastResponse() { return $this->lastResponse; } public function getUrlDetectionHandler() { return $this->urlDetectionHandler; } private function setUrlDetectionHandler(UrlDetectionInterface $urlDetectionHandler) { $this->urlDetectionHandler = $urlDetectionHandler; } public function getDefaultAccessToken() { return $this->defaultAccessToken; } public function setDefaultAccessToken($accessToken) { if (is_string($accessToken)) { $this->defaultAccessToken = new AccessToken($accessToken); return; } if ($accessToken instanceof AccessToken) { $this->defaultAccessToken = $accessToken; return; } throw new \InvalidArgumentException('The default access token must be of type "string" or Facebook\AccessToken'); } public function getDefaultGraphVersion() { return $this->defaultGraphVersion; } public function getRedirectLoginHelper() { return new FacebookRedirectLoginHelper( $this->getOAuth2Client(), $this->persistentDataHandler, $this->urlDetectionHandler, $this->pseudoRandomStringGenerator ); } public function getJavaScriptHelper() { return new FacebookJavaScriptHelper($this->app, $this->client, $this->defaultGraphVersion); } public function getCanvasHelper() { return new FacebookCanvasHelper($this->app, $this->client, $this->defaultGraphVersion); } public function getPageTabHelper() { return new FacebookPageTabHelper($this->app, $this->client, $this->defaultGraphVersion); } public function get($endpoint, $accessToken = null, $eTag = null, $graphVersion = null) { return $this->sendRequest( 'GET', $endpoint, $params = [], $accessToken, $eTag, $graphVersion ); } public function post($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) { return $this->sendRequest( 'POST', $endpoint, $params, $accessToken, $eTag, $graphVersion ); } public function delete($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) { return $this->sendRequest( 'DELETE', $endpoint, $params, $accessToken, $eTag, $graphVersion ); } public function next(GraphEdge $graphEdge) { return $this->getPaginationResults($graphEdge, 'next'); } public function previous(GraphEdge $graphEdge) { return $this->getPaginationResults($graphEdge, 'previous'); } public function getPaginationResults(GraphEdge $graphEdge, $direction) { $paginationRequest = $graphEdge->getPaginationRequest($direction); if (!$paginationRequest) { return null; } $this->lastResponse = $this->client->sendRequest($paginationRequest); $subClassName = $graphEdge->getSubClassName(); $graphEdge = $this->lastResponse->getGraphEdge($subClassName, false); return count($graphEdge) > 0 ? $graphEdge : null; } public function sendRequest($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) { $accessToken = $accessToken ?: $this->defaultAccessToken; $graphVersion = $graphVersion ?: $this->defaultGraphVersion; $request = $this->request($method, $endpoint, $params, $accessToken, $eTag, $graphVersion); return $this->lastResponse = $this->client->sendRequest($request); } public function sendBatchRequest(array $requests, $accessToken = null, $graphVersion = null) { $accessToken = $accessToken ?: $this->defaultAccessToken; $graphVersion = $graphVersion ?: $this->defaultGraphVersion; $batchRequest = new FacebookBatchRequest( $this->app, $requests, $accessToken, $graphVersion ); return $this->lastResponse = $this->client->sendBatchRequest($batchRequest); } public function newBatchRequest($accessToken = null, $graphVersion = null) { $accessToken = $accessToken ?: $this->defaultAccessToken; $graphVersion = $graphVersion ?: $this->defaultGraphVersion; return new FacebookBatchRequest( $this->app, [], $accessToken, $graphVersion ); } public function request($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) { $accessToken = $accessToken ?: $this->defaultAccessToken; $graphVersion = $graphVersion ?: $this->defaultGraphVersion; return new FacebookRequest( $this->app, $accessToken, $method, $endpoint, $params, $eTag, $graphVersion ); } public function fileToUpload($pathToFile) { return new FacebookFile($pathToFile); } public function videoToUpload($pathToFile) { return new FacebookVideo($pathToFile); } public function uploadVideo($target, $pathToFile, $metadata = [], $accessToken = null, $maxTransferTries = 5, $graphVersion = null) { $accessToken = $accessToken ?: $this->defaultAccessToken; $graphVersion = $graphVersion ?: $this->defaultGraphVersion; $uploader = new FacebookResumableUploader($this->app, $this->client, $accessToken, $graphVersion); $endpoint = '/'.$target.'/videos'; $file = $this->videoToUpload($pathToFile); $chunk = $uploader->start($endpoint, $file); do { $chunk = $this->maxTriesTransfer($uploader, $endpoint, $chunk, $maxTransferTries); } while (!$chunk->isLastChunk()); return [ 'video_id' => $chunk->getVideoId(), 'success' => $uploader->finish($endpoint, $chunk->getUploadSessionId(), $metadata), ]; } private function maxTriesTransfer(FacebookResumableUploader $uploader, $endpoint, FacebookTransferChunk $chunk, $retryCountdown) { $newChunk = $uploader->transfer($endpoint, $chunk, $retryCountdown < 1); if ($newChunk !== $chunk) { return $newChunk; } $retryCountdown--; return $this->maxTriesTransfer($uploader, $endpoint, $chunk, $retryCountdown); } } addProvider(new FooProvider()); $generator->addProvider(new BarProvider()); $this->assertEquals('barfoo', $generator->format('fooFormatter')); } public function testGetFormatterReturnsCallable() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $this->assertTrue(is_callable($generator->getFormatter('fooFormatter'))); } public function testGetFormatterReturnsCorrectFormatter() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $expected = array($provider, 'fooFormatter'); $this->assertEquals($expected, $generator->getFormatter('fooFormatter')); } public function testGetFormatterThrowsExceptionOnIncorrectProvider() { $generator = new Generator; $generator->getFormatter('fooFormatter'); } public function testGetFormatterThrowsExceptionOnIncorrectFormatter() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $generator->getFormatter('barFormatter'); } public function testFormatCallsFormatterOnProvider() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $this->assertEquals('foobar', $generator->format('fooFormatter')); } public function testFormatTransfersArgumentsToFormatter() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $this->assertEquals('bazfoo', $generator->format('fooFormatterWithArguments', array('foo'))); } public function testParseReturnsSameStringWhenItContainsNoCurlyBraces() { $generator = new Generator(); $this->assertEquals('fooBar#?', $generator->parse('fooBar#?')); } public function testParseReturnsStringWithTokensReplacedByFormatters() { $generator = new Generator(); $provider = new FooProvider(); $generator->addProvider($provider); $this->assertEquals('This is foobar a text with foobar', $generator->parse('This is {{fooFormatter}} a text with {{ fooFormatter }}')); } public function testMagicGetCallsFormat() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $this->assertEquals('foobar', $generator->fooFormatter); } public function testMagicCallCallsFormat() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $this->assertEquals('foobar', $generator->fooFormatter()); } public function testMagicCallCallsFormatWithArguments() { $generator = new Generator; $provider = new FooProvider(); $generator->addProvider($provider); $this->assertEquals('bazfoo', $generator->fooFormatterWithArguments('foo')); } public function testSeed() { $generator = new Generator; $generator->seed(0); $mtRandWithSeedZero = mt_rand(); $generator->seed(0); $this->assertEquals($mtRandWithSeedZero, mt_rand(), 'seed(0) should be deterministic.'); $generator->seed(); $mtRandWithoutSeed = mt_rand(); $this->assertNotEquals($mtRandWithSeedZero, $mtRandWithoutSeed, 'seed() should be different than seed(0)'); $generator->seed(); $this->assertNotEquals($mtRandWithoutSeed, mt_rand(), 'seed() should not be deterministic.'); } } class FooProvider { public function fooFormatter() { return 'foobar'; } public function fooFormatterWithArguments($value = '') { return 'baz' . $value; } } class BarProvider { public function fooFormatter() { return 'barfoo'; } } assertSame(null, $generator->value); } public function testGeneratorReturnsDefaultValueForAnyPropertyGet() { $generator = new DefaultGenerator(123); $this->assertSame(123, $generator->foo); $this->assertNotSame(null, $generator->bar); } public function testGeneratorReturnsDefaultValueForAnyMethodCall() { $generator = new DefaultGenerator(123); $this->assertSame(123, $generator->foobar()); } } addProvider(new PhoneNumber($faker)); $this->faker = $faker; } public function testPhoneNumberFormat() { $pattern = "/((\+38)(((\(\d{3}\))\d{7}|(\(\d{4}\))\d{6})|(\d{8})))|0\d{9}/"; $phoneNumber = $this->faker->phoneNumber; $this->assertSame( preg_match($pattern, $phoneNumber), 1, 'Phone number format ' . $phoneNumber . ' is wrong!' ); } } addProvider(new Address($faker)); $this->faker = $faker; } public function testPostCodeIsValid() { $main = '[0-9]{5}'; $pattern = "/^($main)|($main-[0-9]{3})+$/"; $postcode = $this->faker->postcode; $this->assertRegExp($pattern, $postcode, 'Post code ' . $postcode . ' is wrong!'); } public function testEmptySuffixes() { $this->assertEmpty($this->faker->citySuffix, 'City suffix should be empty!'); $this->assertEmpty($this->faker->streetSuffix, 'Street suffix should be empty!'); } public function testStreetCyrOnly() { $pattern = "/[0-9А-ЩЯІЇЄЮа-щяіїєюьIVXCM][0-9А-ЩЯІЇЄЮа-щяіїєюь \'-.]*[А-Яа-я.]/u"; $streetName = $this->faker->streetName; $this->assertSame( preg_match($pattern, $streetName), 1, 'Street name ' . $streetName . ' is wrong!' ); } public function testCityNameCyrOnly() { $pattern = "/[А-ЩЯІЇЄЮа-щяіїєюь][0-9А-ЩЯІЇЄЮа-щяіїєюь \'-]*[А-Яа-я]/u"; $city = $this->faker->city; $this->assertSame( preg_match($pattern, $city), 1, 'City name ' . $city . ' is wrong!' ); } public function testRegionNameCyrOnly() { $pattern = "/[А-ЩЯІЇЄЮ][А-ЩЯІЇЄЮа-щяіїєюь]*а$/u"; $regionName = $this->faker->region; $this->assertSame( preg_match($pattern, $regionName), 1, 'Region name ' . $regionName . ' is wrong!' ); } public function testCountryCyrOnly() { $pattern = "/[А-ЩЯІЇЄЮа-щяіїєюьIVXCM][А-ЩЯІЇЄЮа-щяіїєюь \'-]*[А-Яа-я.]/u"; $country = $this->faker->country; $this->assertSame( preg_match($pattern, $country), 1, 'Country name ' . $country . ' is wrong!' ); } } addProvider(new Payment($faker)); $this->faker = $faker; } public function testVatIsValid() { $vat = $this->faker->vat(); $unspacedVat = $this->faker->vat(false); $this->assertRegExp('/^(AT U\d{8})$/', $vat); $this->assertRegExp('/^(ATU\d{8})$/', $unspacedVat); } } assertNotNull($faker->name(), 'Localized Name Provider ' . $matches[1] . ' does not throw errors'); } } public function testLocalizedAddressProvidersDoNotThrowErrors() { foreach (glob(__DIR__ . '/../../../src/Faker/Provider/*/Address.php') as $localizedAddress) { preg_match('#/([a-zA-Z_]+)/Address\.php#', $localizedAddress, $matches); $faker = Factory::create($matches[1]); $this->assertNotNull($faker->address(), 'Localized Address Provider ' . $matches[1] . ' does not throw errors'); } } } assertTrue(is_integer(BaseProvider::randomDigit())); } public function testRandomDigitReturnsDigit() { $this->assertTrue(BaseProvider::randomDigit() >= 0); $this->assertTrue(BaseProvider::randomDigit() < 10); } public function testRandomDigitNotNullReturnsNotNullDigit() { $this->assertTrue(BaseProvider::randomDigitNotNull() > 0); $this->assertTrue(BaseProvider::randomDigitNotNull() < 10); } public function testRandomNumberThrowsExceptionWhenCalledWithAMax() { BaseProvider::randomNumber(5, 200); } public function testRandomNumberThrowsExceptionWhenCalledWithATooHighNumberOfDigits() { BaseProvider::randomNumber(10); } public function testRandomNumberReturnsInteger() { $this->assertTrue(is_integer(BaseProvider::randomNumber())); $this->assertTrue(is_integer(BaseProvider::randomNumber(5, false))); } public function testRandomNumberReturnsDigit() { $this->assertTrue(BaseProvider::randomNumber(3) >= 0); $this->assertTrue(BaseProvider::randomNumber(3) < 1000); } public function testRandomNumberAcceptsStrictParamToEnforceNumberSize() { $this->assertEquals(5, strlen((string) BaseProvider::randomNumber(5, true))); } public function testNumberBetween() { $min = 5; $max = 6; $this->assertGreaterThanOrEqual($min, BaseProvider::numberBetween($min, $max)); $this->assertGreaterThanOrEqual(BaseProvider::numberBetween($min, $max), $max); } public function testNumberBetweenAcceptsZeroAsMax() { $this->assertEquals(0, BaseProvider::numberBetween(0, 0)); } public function testRandomFloat() { $min = 4; $max = 10; $nbMaxDecimals = 8; $result = BaseProvider::randomFloat($nbMaxDecimals, $min, $max); $parts = explode('.', $result); $this->assertInternalType('float', $result); $this->assertGreaterThanOrEqual($min, $result); $this->assertLessThanOrEqual($max, $result); $this->assertLessThanOrEqual($nbMaxDecimals, strlen($parts[1])); } public function testRandomLetterReturnsString() { $this->assertTrue(is_string(BaseProvider::randomLetter())); } public function testRandomLetterReturnsSingleLetter() { $this->assertEquals(1, strlen(BaseProvider::randomLetter())); } public function testRandomLetterReturnsLowercaseLetter() { $lowercaseLetters = 'abcdefghijklmnopqrstuvwxyz'; $this->assertTrue(strpos($lowercaseLetters, BaseProvider::randomLetter()) !== false); } public function testRandomAsciiReturnsString() { $this->assertTrue(is_string(BaseProvider::randomAscii())); } public function testRandomAsciiReturnsSingleCharacter() { $this->assertEquals(1, strlen(BaseProvider::randomAscii())); } public function testRandomAsciiReturnsAsciiCharacter() { $lowercaseLetters = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertTrue(strpos($lowercaseLetters, BaseProvider::randomAscii()) !== false); } public function testRandomElementReturnsNullWhenArrayEmpty() { $this->assertNull(BaseProvider::randomElement(array())); } public function testRandomElementReturnsElementFromArray() { $elements = array('23', 'e', 32, '#'); $this->assertContains(BaseProvider::randomElement($elements), $elements); } public function testRandomElementReturnsElementFromAssociativeArray() { $elements = array('tata' => '23', 'toto' => 'e', 'tutu' => 32, 'titi' => '#'); $this->assertContains(BaseProvider::randomElement($elements), $elements); } public function testShuffleReturnsStringWhenPassedAStringArgument() { $this->assertInternalType('string', BaseProvider::shuffle('foo')); } public function testShuffleReturnsArrayWhenPassedAnArrayArgument() { $this->assertInternalType('array', BaseProvider::shuffle(array(1, 2, 3))); } public function testShuffleThrowsExceptionWhenPassedAnInvalidArgument() { BaseProvider::shuffle(false); } public function testShuffleArraySupportsEmptyArrays() { $this->assertEquals(array(), BaseProvider::shuffleArray(array())); } public function testShuffleArrayReturnsAnArrayOfTheSameSize() { $array = array(1, 2, 3, 4, 5); $this->assertSameSize($array, BaseProvider::shuffleArray($array)); } public function testShuffleArrayReturnsAnArrayWithSameElements() { $array = array(2, 4, 6, 8, 10); $shuffleArray = BaseProvider::shuffleArray($array); $this->assertContains(2, $shuffleArray); $this->assertContains(4, $shuffleArray); $this->assertContains(6, $shuffleArray); $this->assertContains(8, $shuffleArray); $this->assertContains(10, $shuffleArray); } public function testShuffleArrayReturnsADifferentArrayThanTheOriginal() { $arr = array(1, 2, 3, 4, 5); $shuffledArray = BaseProvider::shuffleArray($arr); $this->assertNotEquals($arr, $shuffledArray); } public function testShuffleArrayLeavesTheOriginalArrayUntouched() { $arr = array(1, 2, 3, 4, 5); BaseProvider::shuffleArray($arr); $this->assertEquals($arr, array(1, 2, 3, 4, 5)); } public function testShuffleStringSupportsEmptyStrings() { $this->assertEquals('', BaseProvider::shuffleString('')); } public function testShuffleStringReturnsAnStringOfTheSameSize() { $string = 'abcdef'; $this->assertEquals(strlen($string), strlen(BaseProvider::shuffleString($string))); } public function testShuffleStringReturnsAnStringWithSameElements() { $string = 'acegi'; $shuffleString = BaseProvider::shuffleString($string); $this->assertContains('a', $shuffleString); $this->assertContains('c', $shuffleString); $this->assertContains('e', $shuffleString); $this->assertContains('g', $shuffleString); $this->assertContains('i', $shuffleString); } public function testShuffleStringReturnsADifferentStringThanTheOriginal() { $string = 'abcdef'; $shuffledString = BaseProvider::shuffleString($string); $this->assertNotEquals($string, $shuffledString); } public function testShuffleStringLeavesTheOriginalStringUntouched() { $string = 'abcdef'; BaseProvider::shuffleString($string); $this->assertEquals($string, 'abcdef'); } public function testNumerifyReturnsSameStringWhenItContainsNoHashSign() { $this->assertEquals('fooBar?', BaseProvider::numerify('fooBar?')); } public function testNumerifyReturnsStringWithHashSignsReplacedByDigits() { $this->assertRegExp('/foo\dBa\dr/', BaseProvider::numerify('foo#Ba#r')); } public function testNumerifyReturnsStringWithPercentageSignsReplacedByDigits() { $this->assertRegExp('/foo\dBa\dr/', BaseProvider::numerify('foo%Ba%r')); } public function testNumerifyReturnsStringWithPercentageSignsReplacedByNotNullDigits() { $this->assertNotEquals('0', BaseProvider::numerify('%')); } public function testNumerifyCanGenerateALargeNumberOfDigits() { $largePattern = str_repeat('#', 20); $this->assertEquals(20, strlen(BaseProvider::numerify($largePattern))); } public function testLexifyReturnsSameStringWhenItContainsNoQuestionMark() { $this->assertEquals('fooBar#', BaseProvider::lexify('fooBar#')); } public function testLexifyReturnsStringWithQuestionMarksReplacedByLetters() { $this->assertRegExp('/foo[a-z]Ba[a-z]r/', BaseProvider::lexify('foo?Ba?r')); } public function testBothifyCombinesNumerifyAndLexify() { $this->assertRegExp('/foo[a-z]Ba\dr/', BaseProvider::bothify('foo?Ba#r')); } public function testAsciifyReturnsSameStringWhenItContainsNoStarSign() { $this->assertEquals('fooBar?', BaseProvider::asciify('fooBar?')); } public function testAsciifyReturnsStringWithStarSignsReplacedByAsciiChars() { $this->assertRegExp('/foo.Ba.r/', BaseProvider::asciify('foo*Ba*r')); } public function regexifyBasicDataProvider() { return array( array('azeQSDF1234', 'azeQSDF1234', 'does not change non regex chars'), array('foo(bar){1}', 'foobar', 'replaces regex characters'), array('', '', 'supports empty string'), array('/^foo(bar){1}$/', 'foobar', 'ignores regex delimiters') ); } public function testRegexifyBasicFeatures($input, $output, $message) { $this->assertEquals($output, BaseProvider::regexify($input), $message); } public function regexifyDataProvider() { return array( array('\d', 'numbers'), array('\w', 'letters'), array('(a|b)', 'alternation'), array('[aeiou]', 'basic character class'), array('[a-z]', 'character class range'), array('[a-z1-9]', 'multiple character class range'), array('a*b+c?', 'single character quantifiers'), array('a{2}', 'brackets quantifiers'), array('a{2,3}', 'min-max brackets quantifiers'), array('[aeiou]{2,3}', 'brackets quantifiers on basic character class'), array('[a-z]{2,3}', 'brackets quantifiers on character class range'), array('(a|b){2,3}', 'brackets quantifiers on alternation'), array('\.\*\?\+', 'escaped characters'), array('[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}', 'complex regex') ); } public function testRegexifySupportedRegexSyntax($pattern, $message) { $this->assertRegExp('/' . $pattern . '/', BaseProvider::regexify($pattern), 'Regexify supports ' . $message); } public function testOptionalReturnsProviderValueWhenCalledWithWeight1() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $this->assertNotNull($faker->optional(1)->randomDigit); } public function testOptionalReturnsNullWhenCalledWithWeight0() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $this->assertNull($faker->optional(0)->randomDigit); } public function testOptionalAllowsChainingPropertyAccess() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $faker->addProvider(new \ArrayObject(array(1))); $this->assertEquals(1, $faker->optional(1)->count); $this->assertNull($faker->optional(0)->count); } public function testOptionalAllowsChainingMethodCall() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $faker->addProvider(new \ArrayObject(array(1))); $this->assertEquals(1, $faker->optional(1)->count()); $this->assertNull($faker->optional(0)->count()); } public function testOptionalAllowsChainingProviderCallRandomlyReturnNull() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $values = array(); for ($i=0; $i < 10; $i++) { $values[]= $faker->optional()->randomDigit; } $this->assertContains(null, $values); } public function testUniqueAllowsChainingPropertyAccess() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $faker->addProvider(new \ArrayObject(array(1))); $this->assertEquals(1, $faker->unique()->count); } public function testUniqueAllowsChainingMethodCall() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $faker->addProvider(new \ArrayObject(array(1))); $this->assertEquals(1, $faker->unique()->count()); } public function testUniqueReturnsOnlyUniqueValues() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $values = array(); for ($i=0; $i < 10; $i++) { $values[]= $faker->unique()->randomDigit; } sort($values); $this->assertEquals(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), $values); } public function testUniqueThrowsExceptionWhenNoUniqueValueCanBeGenerated() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); for ($i=0; $i < 11; $i++) { $faker->unique()->randomDigit; } } public function testUniqueCanResetUniquesWhenPassedTrueAsArgument() { $faker = new \Faker\Generator(); $faker->addProvider(new \Faker\Provider\Base($faker)); $values = array(); for ($i=0; $i < 10; $i++) { $values[]= $faker->unique()->randomDigit; } $values[]= $faker->unique(true)->randomDigit; for ($i=0; $i < 9; $i++) { $values[]= $faker->unique()->randomDigit; } sort($values); $this->assertEquals(array(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9), $values); } public function testRandomElementsThrowsWhenRequestingTooManyKeys() { BaseProvider::randomElements(array('foo'), 2); } public function testRandomElements() { $this->assertCount(1, BaseProvider::randomElements(), 'Should work without any input'); $empty = BaseProvider::randomElements(array(), 0); $this->assertInternalType('array', $empty); $this->assertCount(0, $empty); $shuffled = BaseProvider::randomElements(array('foo', 'bar', 'baz'), 3); $this->assertContains('foo', $shuffled); $this->assertContains('bar', $shuffled); $this->assertContains('baz', $shuffled); } } addProvider(new Company($faker)); $this->faker = $faker; } public function testSiretReturnsAValidSiret() { $siret = $this->faker->siret(false); $this->assertRegExp("/^\d{14}$/", $siret); $this->assertTrue(Luhn::isValid($siret)); } public function testSiretReturnsAWellFormattedSiret() { $siret = $this->faker->siret(); $this->assertRegExp("/^\d{3}\s\d{3}\s\d{3}\s\d{5}$/", $siret); $siret = str_replace(' ', '', $siret); $this->assertTrue(Luhn::isValid($siret)); } public function testSirenReturnsAValidSiren() { $siren = $this->faker->siren(false); $this->assertRegExp("/^\d{9}$/", $siren); $this->assertTrue(Luhn::isValid($siren)); } public function testSirenReturnsAWellFormattedSiren() { $siren = $this->faker->siren(); $this->assertRegExp("/^\d{3}\s\d{3}\s\d{3}$/", $siren); $siren = str_replace(' ', '', $siren); $this->assertTrue(Luhn::isValid($siren)); } public function testCatchPhraseReturnsValidCatchPhrase() { $this->assertTrue(TestableCompany::isCatchPhraseValid($this->faker->catchPhrase())); } public function testIsCatchPhraseValidReturnsFalseWhenAWordsAppearsTwice() { $isCatchPhraseValid = TestableCompany::isCatchPhraseValid('La sécurité de rouler en toute sécurité'); $this->assertFalse($isCatchPhraseValid); } public function testIsCatchPhraseValidReturnsTrueWhenNoWordAppearsTwice() { $isCatchPhraseValid = TestableCompany::isCatchPhraseValid('La sécurité de rouler en toute simplicité'); $this->assertTrue($isCatchPhraseValid); } } class TestableCompany extends Company { public static function isCatchPhraseValid($catchPhrase) { return parent::isCatchPhraseValid($catchPhrase); } } assertRegExp('#^http://lorempixel.com/640/480/#', Image::imageUrl()); } public function testImageUrlAcceptsCustomWidthAndHeight() { $this->assertRegExp('#^http://lorempixel.com/800/400/#', Image::imageUrl(800, 400)); } public function testImageUrlAcceptsCustomCategory() { $this->assertRegExp('#^http://lorempixel.com/800/400/nature/#', Image::imageUrl(800, 400, 'nature')); } public function testImageUrlAcceptsCustomText() { $this->assertRegExp('#^http://lorempixel.com/800/400/nature/Faker#', Image::imageUrl(800, 400, 'nature', false, 'Faker')); } public function testImageUrlAddsARandomGetParameterByDefault() { $url = Image::imageUrl(800, 400); $splitUrl = preg_split('/\?/', $url); $this->assertEquals(count($splitUrl), 2); $this->assertRegexp('#\d{5}#', $splitUrl[1]); } public function testUrlWithDimensionsAndBadCategory() { Image::imageUrl(800, 400, 'bullhonky'); } public function testDownloadWithDefaults() { $file = Image::image(sys_get_temp_dir()); $this->assertFileExists($file); if (function_exists('getimagesize')) { list($width, $height, $type, $attr) = getimagesize($file); $this->assertEquals(640, $width); $this->assertEquals(480, $height); $this->assertEquals(constant('IMAGETYPE_JPEG'), $type); } else { $this->assertEquals('jpg', pathinfo($file, PATHINFO_EXTENSION)); } if (file_exists($file)) { unlink($file); } } } addProvider(new Text($generator)); $generator->seed(0); $lengths = array(10, 20, 50, 70, 90, 120, 150, 200, 500); foreach ($lengths as $length) { $this->assertLessThan($length, $generator->realText($length)); } } public function testTextMaxIndex() { $generator = new Generator(); $generator->addProvider(new Text($generator)); $generator->seed(0); $generator->realText(200, 11); } public function testTextMinIndex() { $generator = new Generator(); $generator->addProvider(new Text($generator)); $generator->seed(0); $generator->realText(200, 0); } public function testTextMinLength() { $generator = new Generator(); $generator->addProvider(new Text($generator)); $generator->seed(0); $generator->realText(9); } } addProvider(new Person($faker)); $this->faker = $faker; } public function provideSeedAndExpectedReturn() { return array( array(1, '720727', '720727-5798'), array(2, '710414', '710414-5664'), array(3, '591012', '591012-4519'), array(4, '180307', '180307-0356'), array(5, '820904', '820904-7748') ); } public function testPersonalIdentityNumberUsesBirthDateIfProvided($seed, $birthdate, $expected) { $faker = $this->faker; $faker->seed($seed); $pin = $faker->personalIdentityNumber(\DateTime::createFromFormat('ymd', $birthdate)); $this->assertEquals($expected, $pin); } public function testPersonalIdentityNumberGeneratesLuhnCompliantNumbers() { $pin = str_replace('-', '', $this->faker->personalIdentityNumber()); $this->assertTrue(Luhn::isValid($pin)); } public function testPersonalIdentityNumberGeneratesOddValuesForMales() { $pin = $this->faker->personalIdentityNumber(null, 'male'); $this->assertEquals(1, $pin{9} % 2); } public function testPersonalIdentityNumberGeneratesEvenValuesForFemales() { $pin = $this->faker->personalIdentityNumber(null, 'female'); $this->assertEquals(0, $pin{9} % 2); } } assertTrue($this->isUuid($uuid)); } public function testUuidExpectedSeed() { mt_srand(123); $this->assertEquals("8e2e0c84-50dd-367c-9e66-f3ab455c78d6", BaseProvider::uuid()); $this->assertEquals("073eb60a-902c-30ab-93d0-a94db371f6c8", BaseProvider::uuid()); } protected function isUuid($uuid) { return is_string($uuid) && (bool) preg_match('/^[a-f0-9]{8,8}-(?:[a-f0-9]{4,4}-){3,3}[a-f0-9]{12,12}$/i', $uuid); } } addProvider(new PhoneNumber($faker)); $this->faker = $faker; } public function testPhoneNumberReturnsPhoneNumberWithOrWithoutPrefix() { $this->assertRegExp('/^(9[1,2,3,6][0-9]{7})|(2[0-9]{8})|(\+351 [2][0-9]{8})|(\+351 9[1,2,3,6][0-9]{7})/', $this->faker->phoneNumber()); } public function testMobileNumberReturnsMobileNumberWithOrWithoutPrefix() { $this->assertRegExp('/^(9[1,2,3,6][0-9]{7})/', $this->faker->mobileNumber()); } } addProvider(new Person($faker)); $this->faker = $faker; } public function testTaxpayerIdentificationNumberIsValid() { $tin = $this->faker->taxpayerIdentificationNumber(); $this->assertTrue($this->isValidTin($tin), $tin); } public static function isValidTin($tin) { $regex = '(([1,2,3,5,6,8]{1}[0-9]{8})|((45)|(70)|(71)|(72)|(77)|(79)|(90|(98|(99))))[0-9]{7})'; if (is_null($tin) || !is_numeric($tin) || !strlen($tin) == 9 || preg_match("/$regex/", $tin) !== 1) { return false; } $n = str_split($tin); $cd = ($n[0] * 9 + $n[1] * 8 + $n[2] * 7 + $n[3] * 6 + $n[4] * 5 + $n[5] * 4 + $n[6] * 3 + $n[7] * 2) % 11; if ($cd === 0 || $cd === 1) { $cd = 0; } else { $cd = 11 - $cd; } if ($cd === intval($n[8])) { return true; } return false; } } addProvider(new Address($faker)); $this->faker = $faker; } public function testPostCodeIsValid() { $main = '[1-9]{1}[0-9]{2}[0,1,4,5,9]{1}'; $pattern = "/^($main)|($main-[0-9]{3})+$/"; $postcode = $this->faker->postcode(); $this->assertSame(preg_match($pattern, $postcode), 1, $postcode); } } assertEquals('Word word word word.', TestableLorem::text(24)); } public function testTextReturnsSentencesWhenAskedSizeLessThan100() { $this->assertEquals('This is a test sentence. This is a test sentence. This is a test sentence.', TestableLorem::text(99)); } public function testTextReturnsParagraphsWhenAskedSizeGreaterOrEqualThanThan100() { $this->assertEquals('This is a test paragraph. It has three sentences. Exactly three.', TestableLorem::text(100)); } public function testSentenceWithZeroNbWordsReturnsEmptyString() { $this->assertEquals('', Lorem::sentence(0)); } public function testSentenceWithNegativeNbWordsReturnsEmptyString() { $this->assertEquals('', Lorem::sentence(-1)); } public function testParagraphWithZeroNbSentencesReturnsEmptyString() { $this->assertEquals('', Lorem::paragraph(0)); } public function testParagraphWithNegativeNbSentencesReturnsEmptyString() { $this->assertEquals('', Lorem::paragraph(-1)); } public function testSentenceWithPositiveNbWordsReturnsAtLeastOneWord() { $sentence = Lorem::sentence(1); $this->assertGreaterThan(1, strlen($sentence)); $this->assertGreaterThanOrEqual(1, count(explode(' ', $sentence))); } public function testParagraphWithPositiveNbSentencesReturnsAtLeastOneWord() { $paragraph = Lorem::paragraph(1); $this->assertGreaterThan(1, strlen($paragraph)); $this->assertGreaterThanOrEqual(1, count(explode(' ', $paragraph))); } public function testWordssAsText() { $words = TestableLorem::words(2, true); $this->assertEquals('word word', $words); } public function testSentencesAsText() { $sentences = TestableLorem::sentences(2, true); $this->assertEquals('This is a test sentence. This is a test sentence.', $sentences); } public function testParagraphsAsText() { $paragraphs = TestableLorem::paragraphs(2, true); $expected = "This is a test paragraph. It has three sentences. Exactly three.\n\nThis is a test paragraph. It has three sentences. Exactly three."; $this->assertEquals($expected, $paragraphs); } } class TestableLorem extends Lorem { public static function word() { return 'word'; } public static function sentence($nbWords = 5, $variableNbWords = true) { return 'This is a test sentence.'; } public static function paragraph($nbSentences = 3, $variableNbSentences = true) { return 'This is a test paragraph. It has three sentences. Exactly three.'; } } assertInternalType('int', $timestamp); $this->assertTrue($timestamp >= 0); $this->assertTrue($timestamp <= time()); } public function testDateTime() { $date = DateTimeProvider::dateTime(); $this->assertInstanceOf('\DateTime', $date); $this->assertGreaterThanOrEqual(new \DateTime('@0'), $date); $this->assertLessThanOrEqual(new \DateTime(), $date); } public function testDateTimeAD() { $date = DateTimeProvider::dateTimeAD(); $this->assertInstanceOf('\DateTime', $date); $this->assertGreaterThanOrEqual(new \DateTime('0000-01-01 00:00:00'), $date); $this->assertLessThanOrEqual(new \DateTime(), $date); } public function testIso8601() { $date = DateTimeProvider::iso8601(); $this->assertRegExp('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-Z](\d{4})?$/', $date); $this->assertGreaterThanOrEqual(new \DateTime('@0'), new \DateTime($date)); $this->assertLessThanOrEqual(new \DateTime(), new \DateTime($date)); } public function testDate() { $date = DateTimeProvider::date(); $this->assertRegExp('/^\d{4}-\d{2}-\d{2}$/', $date); $this->assertGreaterThanOrEqual(new \DateTime('@0'), new \DateTime($date)); $this->assertLessThanOrEqual(new \DateTime(), new \DateTime($date)); } public function testTime() { $date = DateTimeProvider::time(); $this->assertRegExp('/^\d{2}:\d{2}:\d{2}$/', $date); } public function testDateTimeBetween($start, $end) { $date = DateTimeProvider::dateTimeBetween($start, $end); $this->assertInstanceOf('\DateTime', $date); $this->assertGreaterThanOrEqual(new \DateTime($start), $date); $this->assertLessThanOrEqual(new \DateTime($end), $date); } public function providerDateTimeBetween() { return array( array('-1 year', false), array('-1 year', null), array('-1 day', '-1 hour'), array('-1 day', 'now'), ); } public function testFixedSeedWithMaximumTimestamp() { $max = '2018-03-01 12:00:00'; mt_srand(1); $unixTime = DateTimeProvider::unixTime($max); $datetimeAD = DateTimeProvider::dateTimeAD($max); $dateTime1 = DateTimeProvider::dateTime($max); $dateTimeBetween = DateTimeProvider::dateTimeBetween('2014-03-01 06:00:00', $max); $date = DateTimeProvider::date('Y-m-d', $max); $time = DateTimeProvider::time('H:i:s', $max); $iso8601 = DateTimeProvider::iso8601($max); $dateTimeThisCentury = DateTimeProvider::dateTimeThisCentury($max); $dateTimeThisDecade = DateTimeProvider::dateTimeThisDecade($max); $dateTimeThisMonth = DateTimeProvider::dateTimeThisMonth($max); $amPm = DateTimeProvider::amPm($max); $dayOfMonth = DateTimeProvider::dayOfMonth($max); $dayOfWeek = DateTimeProvider::dayOfWeek($max); $month = DateTimeProvider::month($max); $monthName = DateTimeProvider::monthName($max); $year = DateTimeProvider::year($max); $dateTimeThisYear = DateTimeProvider::dateTimeThisYear($max); mt_srand(); mt_srand(1); $this->assertEquals($unixTime, DateTimeProvider::unixTime($max)); $this->assertEquals($datetimeAD, DateTimeProvider::dateTimeAD($max)); $this->assertEquals($dateTime1, DateTimeProvider::dateTime($max)); $this->assertEquals($dateTimeBetween, DateTimeProvider::dateTimeBetween('2014-03-01 06:00:00', $max)); $this->assertEquals($date, DateTimeProvider::date('Y-m-d', $max)); $this->assertEquals($time, DateTimeProvider::time('H:i:s', $max)); $this->assertEquals($iso8601, DateTimeProvider::iso8601($max)); $this->assertEquals($dateTimeThisCentury, DateTimeProvider::dateTimeThisCentury($max)); $this->assertEquals($dateTimeThisDecade, DateTimeProvider::dateTimeThisDecade($max)); $this->assertEquals($dateTimeThisMonth, DateTimeProvider::dateTimeThisMonth($max)); $this->assertEquals($amPm, DateTimeProvider::amPm($max)); $this->assertEquals($dayOfMonth, DateTimeProvider::dayOfMonth($max)); $this->assertEquals($dayOfWeek, DateTimeProvider::dayOfWeek($max)); $this->assertEquals($month, DateTimeProvider::month($max)); $this->assertEquals($monthName, DateTimeProvider::monthName($max)); $this->assertEquals($year, DateTimeProvider::year($max)); $this->assertEquals($dateTimeThisYear, DateTimeProvider::dateTimeThisYear($max)); mt_srand(); } } addProvider(new Barcode($faker)); $faker->seed(0); $this->faker = $faker; } public function testEan8() { $code = $this->faker->ean8(); $this->assertRegExp('/^\d{8}$/i', $code); $codeWitoutChecksum = substr($code, 0, -1); $checksum = substr($code, -1); $this->assertEquals(TestableBarcode::eanChecksum($codeWitoutChecksum), $checksum); } public function testEan13() { $code = $this->faker->ean13(); $this->assertRegExp('/^\d{13}$/i', $code); $codeWitoutChecksum = substr($code, 0, -1); $checksum = substr($code, -1); $this->assertEquals(TestableBarcode::eanChecksum($codeWitoutChecksum), $checksum); } } class TestableBarcode extends Barcode { public static function eanChecksum($input) { return parent::eanChecksum($input); } } addProvider(new Lorem($faker)); $faker->addProvider(new Person($faker)); $faker->addProvider(new Internet($faker)); $faker->addProvider(new Company($faker)); $this->faker = $faker; } public function localeDataProvider() { $providerPath = realpath(__DIR__ . '/../../../src/Faker/Provider'); $localePaths = array_filter(glob($providerPath . '/*', GLOB_ONLYDIR)); foreach ($localePaths as $path) { $parts = explode('/', $path); $locales[] = array($parts[count($parts) - 1]); } return $locales; } public function testEmailIsValid($locale) { $this->loadLocalProviders($locale); $pattern = '/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD'; $emailAddress = $this->faker->email(); $this->assertRegExp($pattern, $emailAddress); } public function testUsernameIsValid($locale) { $this->loadLocalProviders($locale); $pattern = '/^[A-Za-z0-9._]+$/'; $username = $this->faker->username(); $this->assertRegExp($pattern, $username); } public function loadLocalProviders($locale) { $providerPath = realpath(__DIR__ . '/../../../src/Faker/Provider'); if (file_exists($providerPath.'/'.$locale.'/Internet.php')) { $internet = "\\Faker\\Provider\\$locale\\Internet"; $this->faker->addProvider(new $internet($this->faker)); } if (file_exists($providerPath.'/'.$locale.'/Person.php')) { $person = "\\Faker\\Provider\\$locale\\Person"; $this->faker->addProvider(new $person($this->faker)); } if (file_exists($providerPath.'/'.$locale.'/Company.php')) { $company = "\\Faker\\Provider\\$locale\\Company"; $this->faker->addProvider(new $company($this->faker)); } } public function testPasswordIsValid() { $this->assertRegexp('/^.{6}$/', $this->faker->password(6, 6)); } public function testSlugIsValid() { $pattern = '/^[a-z0-9-]+$/'; $slug = $this->faker->slug(); $this->assertSame(preg_match($pattern, $slug), 1); } public function testUrlIsValid() { $url = $this->faker->url(); $this->assertNotFalse(filter_var($url, FILTER_VALIDATE_URL)); } public function testLocalIpv4() { $this->assertNotFalse(filter_var(Internet::localIpv4(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)); } public function testIpv4() { $this->assertNotFalse(filter_var($this->faker->ipv4(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)); } public function testIpv6() { $this->assertNotFalse(filter_var($this->faker->ipv6(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)); } public function testMacAddress() { $this->assertRegExp('/^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$/i', Internet::macAddress()); } } generator = new Generator(); $this->generator->addProvider(new Biased($this->generator)); $this->results = array_fill(1, self::MAX, 0); } public function performFake($function) { for($i = 0; $i < self::NUMBERS; $i++) { $this->results[$this->generator->biasedNumberBetween(1, self::MAX, $function)]++; } } public function testUnbiased() { $this->performFake(array('\Faker\Provider\Biased', 'unbiased')); foreach ($this->results as $number => $amount) { $assumed = (1 / self::MAX * $number) - (1 / self::MAX * ($number - 1)); $assumed /= 1; $this->assertGreaterThan(self::NUMBERS * $assumed * .95, $amount, "Value was more than 5 percent under the expected value"); $this->assertLessThan(self::NUMBERS * $assumed * 1.05, $amount, "Value was more than 5 percent over the expected value"); } } public function testLinearHigh() { $this->performFake(array('\Faker\Provider\Biased', 'linearHigh')); foreach ($this->results as $number => $amount) { $assumed = 0.5 * pow(1 / self::MAX * $number, 2) - 0.5 * pow(1 / self::MAX * ($number - 1), 2); $assumed /= pow(1, 2) * .5; $this->assertGreaterThan(self::NUMBERS * $assumed * .9, $amount, "Value was more than 10 percent under the expected value"); $this->assertLessThan(self::NUMBERS * $assumed * 1.1, $amount, "Value was more than 10 percent over the expected value"); } } public function testLinearLow() { $this->performFake(array('\Faker\Provider\Biased', 'linearLow')); foreach ($this->results as $number => $amount) { $assumed = -0.5 * pow(1 / self::MAX * $number, 2) - -0.5 * pow(1 / self::MAX * ($number - 1), 2); $assumed += 1 / self::MAX; $assumed /= pow(1, 2) * .5; $this->assertGreaterThan(self::NUMBERS * $assumed * .9, $amount, "Value was more than 10 percent under the expected value"); $this->assertLessThan(self::NUMBERS * $assumed * 1.1, $amount, "Value was more than 10 percent over the expected value"); } } } addProvider(new BaseProvider($faker)); $faker->addProvider(new DateTimeProvider($faker)); $faker->addProvider(new PersonProvider($faker)); $faker->addProvider(new PaymentProvider($faker)); $this->faker = $faker; } public function testCreditCardTypeReturnsValidVendorName() { $this->assertTrue(in_array($this->faker->creditCardType, array('Visa', 'MasterCard', 'American Express', 'Discover Card'))); } public function creditCardNumberProvider() { return array( array('Discover Card', '/^6011\d{12}$/'), array('Visa', '/^4\d{12,15}$/'), array('MasterCard', '/^5[1-5]\d{14}$/') ); } public function testCreditCardNumberReturnsValidCreditCardNumber($type, $regexp) { $cardNumber = $this->faker->creditCardNumber($type); $this->assertRegExp($regexp, $cardNumber); $this->assertTrue(Luhn::isValid($cardNumber)); } public function testCreditCardNumberCanFormatOutput() { $this->assertRegExp('/^6011-\d{4}-\d{4}-\d{4}$/', $this->faker->creditCardNumber('Discover Card', true)); } public function testCreditCardExpirationDateReturnsValidDateByDefault() { $expirationDate = $this->faker->creditCardExpirationDate; $this->assertTrue(intval($expirationDate->format('U')) > strtotime('now')); $this->assertTrue(intval($expirationDate->format('U')) < strtotime('+36 months')); } public function testRandomCard() { $cardDetails = $this->faker->creditCardDetails; $this->assertEquals(count($cardDetails), 4); $this->assertEquals(array('type', 'number', 'name', 'expirationDate'), array_keys($cardDetails)); } } addProvider(new Person($faker)); $faker->seed(1); $this->assertEquals('アオタ ミノル', $faker->kanaName); } public function testFirstKanaNameReturnsHaruka() { $faker = new Generator(); $faker->addProvider(new Person($faker)); $faker->seed(1); $this->assertEquals('ハルカ', $faker->firstKanaName); } public function testLastKanaNameReturnsNakajima() { $faker = new Generator(); $faker->addProvider(new Person($faker)); $faker->seed(1); $this->assertEquals('ナカジマ', $faker->lastKanaName); } } addProvider(new Person($faker)); $this->assertContains($faker->firstName($gender), $expected); } public function firstNameProvider() { return array( array(null, array('John', 'Jane')), array('foobar', array('John', 'Jane')), array('male', array('John')), array('female', array('Jane')), ); } public function testFirstNameMale() { $this->assertContains(Person::firstNameMale(), array('John')); } public function testFirstNameFemale() { $this->assertContains(Person::firstNameFemale(), array('Jane')); } public function testTitle($gender, $expected) { $faker = new Generator(); $faker->addProvider(new Person($faker)); $this->assertContains($faker->title($gender), $expected); } public function titleProvider() { return array( array(null, array('Mr.', 'Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')), array('foobar', array('Mr.', 'Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')), array('male', array('Mr.', 'Dr.', 'Prof.')), array('female', array('Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')), ); } public function testTitleMale() { $this->assertContains(Person::titleMale(), array('Mr.', 'Dr.', 'Prof.')); } public function testTitleFemale() { $this->assertContains(Person::titleFemale(), array('Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')); } public function testLastNameReturnsDoe() { $faker = new Generator(); $faker->addProvider(new Person($faker)); $this->assertEquals($faker->lastName(), 'Doe'); } public function testNameReturnsFirstNameAndLastName() { $faker = new Generator(); $faker->addProvider(new Person($faker)); $this->assertContains($faker->name(), array('John Doe', 'Jane Doe')); $this->assertContains($faker->name('foobar'), array('John Doe', 'Jane Doe')); $this->assertContains($faker->name('male'), array('John Doe')); $this->assertContains($faker->name('female'), array('Jane Doe')); } } addProvider(new Payment($faker)); $this->faker = $faker; } public function testVatIsValid() { $vat = $this->faker->vat(); $unspacedVat = $this->faker->vat(false); $this->assertRegExp('/^(BG \d{9,10})$/', $vat); $this->assertRegExp('/^(BG\d{9,10})$/', $unspacedVat); } } assertNotNull(UserAgent::userAgent()); } public function testFirefoxUserAgent() { $this->stringContains(' Firefox/', UserAgent::firefox()); } public function testSafariUserAgent() { $this->stringContains('Safari/', UserAgent::safari()); } public function testInternetExplorerUserAgent() { $this->assertStringStartsWith('Mozilla/5.0 (compatible; MSIE ', UserAgent::internetExplorer()); } public function testOperaUserAgent() { $this->assertStringStartsWith('Opera/', UserAgent::opera()); } public function testChromeUserAgent() { $this->stringContains('(KHTML, like Gecko) Chrome/', UserAgent::chrome()); } } assertContains(Miscellaneous::boolean(), array(true, false)); } public function testMd5() { $this->assertRegExp('/^[a-z0-9]{32}$/', Miscellaneous::md5()); } public function testSha1() { $this->assertRegExp('/^[a-z0-9]{40}$/', Miscellaneous::sha1()); } public function testSha256() { $this->assertRegExp('/^[a-z0-9]{64}$/', Miscellaneous::sha256()); } public function testLocale() { $this->assertRegExp('/^[a-z]{2,3}_[A-Z]{2}$/', Miscellaneous::locale()); } public function testCountryCode() { $this->assertRegExp('/^[A-Z]{2}$/', Miscellaneous::countryCode()); } public function testCountryISOAlpha3() { $this->assertRegExp('/^[A-Z]{3}$/', Miscellaneous::countryISOAlpha3()); } public function testLanguage() { $this->assertRegExp('/^[a-z]{2}$/', Miscellaneous::languageCode()); } public function testCurrencyCode() { $this->assertRegExp('/^[A-Z]{3}$/', Miscellaneous::currencyCode()); } } addProvider(new PhoneNumber($faker)); $this->faker = $faker; } public function testPhoneNumberFormat() { $number = $this->faker->phoneNumber; $this->assertRegExp('/^06\d{2} \d{7}|\+43 \d{4} \d{4}(-\d{2})?$/', $number); } } addProvider(new Person($faker)); $faker->addProvider(new Internet($faker)); $faker->addProvider(new Company($faker)); $this->faker = $faker; } public function testEmailIsValid() { $email = $this->faker->email(); $this->assertNotFalse(filter_var($email, FILTER_VALIDATE_EMAIL)); } } addProvider(new Payment($faker)); $this->faker = $faker; } public function testVatIsValid() { $vat = $this->faker->vat(); $unspacedVat = $this->faker->vat(false); $this->assertRegExp('/^(BE 0\d{9})$/', $vat); $this->assertRegExp('/^(BE0\d{9})$/', $unspacedVat); } } assertRegExp(static::TEST_STRING_REGEX, $faker->city); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->postcode); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->address); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->country); } public function testCompany($locale = null) { $faker = Faker\Factory::create($locale); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->company); } public function testDateTime($locale = null) { $faker = Faker\Factory::create($locale); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->century); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->timezone); } public function testInternet($locale = null) { $faker = Faker\Factory::create($locale); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->userName); $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->email); $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->safeEmail); $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->freeEmail); $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->companyEmail); } public function testPerson($locale = null) { $faker = Faker\Factory::create($locale); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->name); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->title); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->firstName); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->lastName); } public function testPhoneNumber($locale = null) { $faker = Faker\Factory::create($locale); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->phoneNumber); } public function testUserAgent($locale = null) { $faker = Faker\Factory::create($locale); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->userAgent); } public function testUuid($locale = null) { $faker = Faker\Factory::create($locale); $this->assertRegExp(static::TEST_STRING_REGEX, $faker->uuid); } public function localeDataProvider() { $locales = $this->getAllLocales(); $data = array(); foreach ($locales as $locale) { $data[] = array( $locale ); } return $data; } private function getAllLocales() { static $locales = array(); if ( ! empty($locales)) { return $locales; } $providerDir = __DIR__ .'/../../../src/Faker/Provider'; foreach (glob($providerDir .'/*_*/*.php') as $file) { $localisation = basename(dirname($file)); if (isset($locales[ $localisation ])) { continue; } $locales[ $localisation ] = $localisation; } return $locales; } } assertRegExp('/^#[a-f0-9]{6}$/i', Color::hexColor()); } public function testSafeHexColor() { $this->assertRegExp('/^#[a-f0-9]{6}$/i', Color::safeHexColor()); } public function testRgbColorAsArray() { $this->assertEquals(3, count(Color::rgbColorAsArray())); } public function testRgbColor() { $regexp = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])'; $this->assertRegExp('/^' . $regexp . ',' . $regexp . ',' . $regexp . '$/i', Color::rgbColor()); } public function testRgbCssColor() { $regexp = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])'; $this->assertRegExp('/^rgb\(' . $regexp . ',' . $regexp . ',' . $regexp . '\)$/i', Color::rgbCssColor()); } public function testSafeColorName() { $this->assertRegExp('/^[\w]+$/', Color::safeColorName()); } public function testColorName() { $this->assertRegExp('/^[\w]+$/', Color::colorName()); } } addProvider(new Address($faker)); $this->faker = $faker; } public function testLatitude() { $latitude = $this->faker->latitude(); $this->assertInternalType('float', $latitude); $this->assertGreaterThanOrEqual(-90, $latitude); $this->assertLessThanOrEqual(90, $latitude); } public function testLongitude() { $longitude = $this->faker->longitude(); $this->assertInternalType('float', $longitude); $this->assertGreaterThanOrEqual(-180, $longitude); $this->assertLessThanOrEqual(180, $longitude); } } addProvider(new Person($faker)); $this->faker = $faker; } public function testIfFirstNameMaleCanReturnData() { $firstNameMale = $this->faker->firstNameMale(); $this->assertNotEmpty($firstNameMale); } public function testIfLastNameMaleCanReturnData() { $lastNameMale = $this->faker->lastNameMale(); $this->assertNotEmpty($lastNameMale); } public function testIfFirstNameFemaleCanReturnData() { $firstNameFemale = $this->faker->firstNameFemale(); $this->assertNotEmpty($firstNameFemale); } public function testIfLastNameFemaleCanReturnData() { $lastNameFemale = $this->faker->lastNameFemale(); $this->assertNotEmpty($lastNameFemale); } } addProvider(new Company($faker)); $this->faker = $faker; } public function testCnpjFormatIsValid() { $cnpj = $this->faker->cnpj(false); $this->assertRegExp('/\d{8}\d{4}\d{2}/', $cnpj); $cnpj = $this->faker->cnpj(true); $this->assertRegExp('/\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}/', $cnpj); } } addProvider(new Person($faker)); $this->faker = $faker; } public function testCpfFormatIsValid() { $cpf = $this->faker->cpf(false); $this->assertRegExp('/\d{9}\d{2}/', $cpf); $cpf = $this->faker->cpf(true); $this->assertRegExp('/\d{3}\.\d{3}\.\d{3}-\d{2}/', $cpf); } public function testRgFormatIsValid() { $rg = $this->faker->rg(false); $this->assertRegExp('/\d{8}\d/', $rg); $rg = $this->faker->rg(true); $this->assertRegExp('/\d{2}\.\d{3}\.\d{3}-[0-9X]/', $rg); } } addProvider(new PhoneNumber($faker)); $this->faker = $faker; } public function testPhoneNumberReturnsNormalPhoneNumber() { $this->assertRegExp('/^0(?:[23][13-7]|7\d)\d{7}$/', $this->faker->phoneNumber()); } public function testTollFreePhoneNumberReturnsTollFreePhoneNumber() { $this->assertRegExp('/^08(?:0[1267]|70)\d{6}$/', $this->faker->tollFreePhoneNumber()); } public function testPremiumRatePhoneNumberReturnsPremiumRatePhoneNumber() { $this->assertRegExp('/^090[036]\d{6}$/', $this->faker->premiumRatePhoneNumber()); } } seed(1); $faker->addProvider(new DateTime($faker)); $faker->addProvider(new Person($faker)); $this->faker = $faker; } public function testCnpReturnsValidCnp() { $cnp = $this->faker->cnp; $this->assertTrue($this->isValidCnp($cnp)); } public function testCnpReturnsMaleCnp() { $cnp = $this->faker->cnp('m'); $this->assertRegExp('/^[1357]\d{12}$/', $cnp); } public function testCnpReturnsFemaleCnp() { $cnp = $this->faker->cnp('f'); $this->assertRegExp('/^[2468]\d{12}$/', $cnp); } public function testCnpReturns1800sCnp() { $cnp = $this->faker->cnp(null, 1800); $this->assertRegExp('/^[34]\d{12}$/', $cnp); } public function testCnpReturns1900sCnp() { $cnp = $this->faker->cnp(null, 1900); $this->assertRegExp('/^[12]\d{12}$/', $cnp); } public function testCnpReturns2000sCnp() { $cnp = $this->faker->cnp(null, 2000); $this->assertRegExp('/^[56]\d{12}$/', $cnp); } public function testCnpReturnsBrasovCnp() { $cnp = $this->faker->cnp(null, null, 'BV'); $this->assertRegExp('/^\d{7}08\d{4}$/', $cnp); } public function testCnpReturns2000sClujFemaleCnp() { $cnp = $this->faker->cnp('f', 2000, 'CJ'); $this->assertRegExp('/^6\d{6}12\d{4}$/', $cnp); } protected function isValidCnp($cnp) { if ( is_string($cnp) && (bool) preg_match(static::TEST_CNP_REGEX, $cnp) && checkdate(substr($cnp, 3, 2), substr($cnp, 5, 2), substr($cnp, 1, 2)) ){ $checkNumber = 279146358279; $checksum = 0; foreach (range(0, 11) as $digit) { $checksum += substr($cnp, $digit, 1) * substr($checkNumber, $digit, 1); } $checksum = $checksum % 11; if ( ($checksum < 10 && $checksum == substr($cnp, -1)) || ($checksum == 10 && substr($cnp, -1) == 1) ){ return true; } } return false; } } assertInternalType('string', $checkDigit); $this->assertEquals($checkDigit, Luhn::computeCheckDigit($partialNumber)); } public function validatorProvider() { return array( array('79927398710', false), array('79927398711', false), array('79927398712', false), array('79927398713', true), array('79927398714', false), array('79927398715', false), array('79927398716', false), array('79927398717', false), array('79927398718', false), array('79927398719', false), array(79927398713, true), array(79927398714, false), ); } public function testIsValid($number, $isValid) { $this->assertEquals($isValid, Luhn::isValid($number)); } } seed(1); $documentor = new Faker\Documentor($generator); ?> getFormatters() as $provider => $formatters): ?> ### `` $example): ?> // seed(5); echo ''; ?> boolean(25)): ?>
    streetAddress ?> city ?> postcode ?> state ?>
    boolean(33)): ?> bs ?> boolean(33)): ?> boolean(15)): ?>
    text(400) ?> ]]>
    addProvider(new $providerClassName($generator)); } return $generator; } protected static function getProviderClassname($provider, $locale = '') { if ($providerClass = self::findProviderClassname($provider, $locale)) { return $providerClass; } if ($providerClass = self::findProviderClassname($provider, static::DEFAULT_LOCALE)) { return $providerClass; } $providerClass = self::findProviderClassname($provider); if (class_exists($providerClass)) { return $providerClass; } throw new \InvalidArgumentException(sprintf('Unable to find provider "%s" with locale "%s"', $provider, $locale)); } protected static function findProviderClassname($provider, $locale = '') { $providerClass = 'Faker\\' . ($locale ? sprintf('Provider\%s\%s', $locale, $provider) : sprintf('Provider\%s', $provider)); if (class_exists($providerClass, true)) { return $providerClass; } } } class = $class; } public function getClass() { return $this->class->getName(); } public function setColumnFormatters($columnFormatters) { $this->columnFormatters = $columnFormatters; } public function getColumnFormatters() { return $this->columnFormatters; } public function mergeColumnFormattersWith($columnFormatters) { $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); } public function setModifiers(array $modifiers) { $this->modifiers = $modifiers; } public function getModifiers() { return $this->modifiers; } public function mergeModifiersWith(array $modifiers) { $this->modifiers = array_merge($this->modifiers, $modifiers); } public function guessColumnFormatters(\Faker\Generator $generator) { $formatters = array(); $nameGuesser = new \Faker\Guesser\Name($generator); $columnTypeGuesser = new ColumnTypeGuesser($generator); foreach ($this->class->getFieldNames() as $fieldName) { if ($this->class->isIdentifier($fieldName) || !$this->class->hasField($fieldName)) { continue; } if ($formatter = $nameGuesser->guessFormat($fieldName)) { $formatters[$fieldName] = $formatter; continue; } if ($formatter = $columnTypeGuesser->guessFormat($fieldName, $this->class)) { $formatters[$fieldName] = $formatter; continue; } } foreach ($this->class->getAssociationNames() as $assocName) { if ($this->class->isCollectionValuedAssociation($assocName)) { continue; } $relatedClass = $this->class->getAssociationTargetClass($assocName); $unique = $optional = false; $mappings = $this->class->getAssociationMappings(); foreach ($mappings as $mapping) { if ($mapping['targetEntity'] == $relatedClass) { if ($mapping['type'] == ClassMetadata::ONE_TO_ONE) { $unique = true; $optional = isset($mapping['joinColumns'][0]['nullable']) ? $mapping['joinColumns'][0]['nullable'] : false; break; } } } $index = 0; $formatters[$assocName] = function ($inserted) use ($relatedClass, &$index, $unique, $optional) { if ($unique && isset($inserted[$relatedClass])) { $related = null; if (isset($inserted[$relatedClass][$index]) || !$optional) { $related = $inserted[$relatedClass][$index]; } $index++; return $related; } elseif (isset($inserted[$relatedClass])) { return $inserted[$relatedClass][mt_rand(0, count($inserted[$relatedClass]) - 1)]; } return null; }; } return $formatters; } public function execute(ObjectManager $manager, $insertedEntities, $generateId = false) { $obj = $this->class->newInstance(); $this->fillColumns($obj, $insertedEntities); $this->callMethods($obj, $insertedEntities); if ($generateId) { $idsName = $this->class->getIdentifier(); foreach ($idsName as $idName) { $id = $this->generateId($obj, $idName, $manager); $this->class->reflFields[$idName]->setValue($obj, $id); } } $manager->persist($obj); return $obj; } private function fillColumns($obj, $insertedEntities) { foreach ($this->columnFormatters as $field => $format) { if (null !== $format) { $value = is_callable($format) ? $format($insertedEntities, $obj) : $format; $this->class->reflFields[$field]->setValue($obj, $value); } } } private function callMethods($obj, $insertedEntities) { foreach ($this->getModifiers() as $modifier) { $modifier($obj, $insertedEntities); } } private function generateId($obj, $column, EntityManagerInterface $manager) { $repository = $manager->getRepository(get_class($obj)); $result = $repository->createQueryBuilder('e') ->select(sprintf('e.%s', $column)) ->getQuery() ->getResult(); $ids = array_map('current', $result); $id = null; do { $id = rand(); } while (in_array($id, $ids)); return $id; } } generator = $generator; $this->manager = $manager; } public function addEntity($entity, $number, $customColumnFormatters = array(), $customModifiers = array(), $generateId = false) { if (!$entity instanceof \Faker\ORM\Doctrine\EntityPopulator) { if (null === $this->manager) { throw new \InvalidArgumentException("No entity manager passed to Doctrine Populator."); } $entity = new \Faker\ORM\Doctrine\EntityPopulator($this->manager->getClassMetadata($entity)); } $entity->setColumnFormatters($entity->guessColumnFormatters($this->generator)); if ($customColumnFormatters) { $entity->mergeColumnFormattersWith($customColumnFormatters); } $entity->mergeModifiersWith($customModifiers); $this->generateId[$entity->getClass()] = $generateId; $class = $entity->getClass(); $this->entities[$class] = $entity; $this->quantities[$class] = $number; } public function execute($entityManager = null) { if (null === $entityManager) { $entityManager = $this->manager; } if (null === $entityManager) { throw new \InvalidArgumentException("No entity manager passed to Doctrine Populator."); } $insertedEntities = array(); foreach ($this->quantities as $class => $number) { $generateId = $this->generateId[$class]; for ($i=0; $i < $number; $i++) { $insertedEntities[$class][]= $this->entities[$class]->execute($entityManager, $insertedEntities, $generateId); } $entityManager->flush(); } return $insertedEntities; } } generator = $generator; } public function guessFormat($fieldName, ClassMetadata $class) { $generator = $this->generator; $type = $class->getTypeOfField($fieldName); switch ($type) { case 'boolean': return function () use ($generator) { return $generator->boolean; }; case 'decimal': $size = isset($class->fieldMappings[$fieldName]['precision']) ? $class->fieldMappings[$fieldName]['precision'] : 2; return function () use ($generator, $size) { return $generator->randomNumber($size + 2) / 100; }; case 'smallint': return function () { return mt_rand(0, 65535); }; case 'integer': return function () { return mt_rand(0, intval('2147483647')); }; case 'bigint': return function () { return mt_rand(0, intval('18446744073709551615')); }; case 'float': return function () { return mt_rand(0, intval('4294967295'))/mt_rand(1, intval('4294967295')); }; case 'string': $size = isset($class->fieldMappings[$fieldName]['length']) ? $class->fieldMappings[$fieldName]['length'] : 255; return function () use ($generator, $size) { return $generator->text($size); }; case 'text': return function () use ($generator) { return $generator->text; }; case 'datetime': case 'date': case 'time': return function () use ($generator) { return $generator->datetime; }; default: return null; } } } class = $class; } public function __get($name) { return $this->{$name}; } public function __set($name, $value) { $this->{$name} = $value; } public function mergeColumnFormattersWith($columnFormatters) { $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); } public function mergeModifiersWith($modifiers) { $this->modifiers = array_merge($this->modifiers, $modifiers); } public function guessColumnFormatters($populator) { $formatters = []; $class = $this->class; $table = $this->getTable($class); $schema = $table->schema(); $pk = $schema->primaryKey(); $guessers = $populator->getGuessers() + ['ColumnTypeGuesser' => new ColumnTypeGuesser($populator->getGenerator())]; $isForeignKey = function ($column) use ($table) { foreach ($table->associations()->type('BelongsTo') as $assoc) { if ($column == $assoc->foreignKey()) { return true; } } return false; }; foreach ($schema->columns() as $column) { if ($column == $pk[0] || $isForeignKey($column)) { continue; } foreach ($guessers as $guesser) { if ($formatter = $guesser->guessFormat($column, $table)) { $formatters[$column] = $formatter; break; } } } return $formatters; } public function guessModifiers($populator) { $modifiers = []; $table = $this->getTable($this->class); $belongsTo = $table->associations()->type('BelongsTo'); foreach ($belongsTo as $assoc) { $modifiers['belongsTo' . $assoc->name()] = function ($data, $insertedEntities) use ($assoc) { $table = $assoc->target(); $foreignModel = $table->alias(); $foreignKeys = []; if (!empty($insertedEntities[$foreignModel])) { $foreignKeys = $insertedEntities[$foreignModel]; } else { $foreignKeys = $table->find('all') ->select(['id']) ->map(function ($row) { return $row->id; }) ->toArray(); } if (empty($foreignKeys)) { throw new \Exception(sprintf('%s belongsTo %s, which seems empty at this point.', $this->getTable($this->class)->table(), $assoc->table())); } $foreignKey = $foreignKeys[array_rand($foreignKeys)]; $primaryKey = $table->primaryKey(); $data[$assoc->foreignKey()] = $foreignKey; return $data; }; } return $modifiers; } public function execute($class, $insertedEntities, $options = []) { $table = $this->getTable($class); $entity = $table->newEntity(); foreach ($this->columnFormatters as $column => $format) { if (!is_null($format)) { $entity->{$column} = is_callable($format) ? $format($insertedEntities, $table) : $format; } } foreach ($this->modifiers as $modifier) { $entity = $modifier($entity, $insertedEntities); } if (!$entity = $table->save($entity, $options)) { throw new \RuntimeException("Failed saving $class record"); } $pk = $table->primaryKey(); return $entity->{$pk}; } public function setConnection($name) { $this->connectionName = $name; } protected function getTable($class) { $options = []; if (!empty($this->connectionName)) { $options['connection'] = $this->connectionName; } return TableRegistry::get($class, $options); } } generator = $generator; } public function getGenerator() { return $this->generator; } public function getGuessers() { return $this->guessers; } public function removeGuesser($name) { if ($this->guessers[$name]) { unset($this->guessers[$name]); } return $this; } public function addGuesser($class) { if (!is_object($class)) { $class = new $class($this->generator); } if (!method_exists($class, 'guessFormat')) { throw new \Exception('Missing required custom guesser method: ' . get_class($class) . '::guessFormat()'); } $this->guessers[get_class($class)] = $class; return $this; } public function addEntity($entity, $number, $customColumnFormatters = [], $customModifiers = []) { if (!$entity instanceof EntityPopulator) { $entity = new EntityPopulator($entity); } $entity->columnFormatters = $entity->guessColumnFormatters($this); if ($customColumnFormatters) { $entity->mergeColumnFormattersWith($customColumnFormatters); } $entity->modifiers = $entity->guessModifiers($this); if ($customModifiers) { $entity->mergeModifiers($customModifiers); } $class = $entity->class; $this->entities[$class] = $entity; $this->quantities[$class] = $number; return $this; } public function execute($options = []) { $insertedEntities = []; foreach ($this->quantities as $class => $number) { for ($i = 0; $i < $number; $i++) { $insertedEntities[$class][] = $this->entities[$class]->execute($class, $insertedEntities, $options); } } return $insertedEntities; } } generator = $generator; } public function guessFormat($column, $table) { $generator = $this->generator; $schema = $table->schema(); switch ($schema->columnType($column)) { case 'boolean': return function () use ($generator) { return $generator->boolean; }; case 'integer': return function () use ($generator) { return $generator->randomNumber(10); }; case 'biginteger': return function () use ($generator) { return $generator->randomNumber(20); }; case 'decimal': case 'float': return function () use ($generator) { return $generator->randomFloat(); }; case 'uuid': return function () use ($generator) { return $generator->uuid(); }; case 'string': $columnData = $schema->column($column); $length = $columnData['length']; return function () use ($generator, $length) { return $generator->text($length); }; case 'text': return function () use ($generator) { return $generator->text(); }; case 'date': case 'datetime': case 'timestamp': case 'time': return function () use ($generator) { return $generator->datetime(); }; case 'binary': default: return null; } } } class = $class; } public function getClass() { return $this->class; } public function setColumnFormatters($columnFormatters) { $this->columnFormatters = $columnFormatters; } public function getColumnFormatters() { return $this->columnFormatters; } public function mergeColumnFormattersWith($columnFormatters) { $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); } public function guessColumnFormatters(\Faker\Generator $generator) { $formatters = array(); $class = $this->class; $peerClass = $class::PEER; $tableMap = $peerClass::getTableMap(); $nameGuesser = new \Faker\Guesser\Name($generator); $columnTypeGuesser = new \Faker\ORM\Propel\ColumnTypeGuesser($generator); foreach ($tableMap->getColumns() as $columnMap) { if ($this->isColumnBehavior($columnMap)) { continue; } if ($columnMap->isForeignKey()) { $relatedClass = $columnMap->getRelation()->getForeignTable()->getClassname(); $formatters[$columnMap->getPhpName()] = function ($inserted) use ($relatedClass) { return isset($inserted[$relatedClass]) ? $inserted[$relatedClass][mt_rand(0, count($inserted[$relatedClass]) - 1)] : null; }; continue; } if ($columnMap->isPrimaryKey()) { continue; } if ($formatter = $nameGuesser->guessFormat($columnMap->getPhpName())) { $formatters[$columnMap->getPhpName()] = $formatter; continue; } if ($formatter = $columnTypeGuesser->guessFormat($columnMap)) { $formatters[$columnMap->getPhpName()] = $formatter; continue; } } return $formatters; } protected function isColumnBehavior(ColumnMap $columnMap) { foreach ($columnMap->getTable()->getBehaviors() as $name => $params) { $columnName = Base::toLower($columnMap->getName()); switch ($name) { case 'nested_set': $columnNames = array($params['left_column'], $params['right_column'], $params['level_column']); if (in_array($columnName, $columnNames)) { return true; } break; case 'timestampable': $columnNames = array($params['create_column'], $params['update_column']); if (in_array($columnName, $columnNames)) { return true; } break; } } return false; } public function setModifiers($modifiers) { $this->modifiers = $modifiers; } public function getModifiers() { return $this->modifiers; } public function mergeModifiersWith($modifiers) { $this->modifiers = array_merge($this->modifiers, $modifiers); } public function guessModifiers(\Faker\Generator $generator) { $modifiers = array(); $class = $this->class; $peerClass = $class::PEER; $tableMap = $peerClass::getTableMap(); foreach ($tableMap->getBehaviors() as $name => $params) { switch ($name) { case 'nested_set': $modifiers['nested_set'] = function ($obj, $inserted) use ($class, $generator) { if (isset($inserted[$class])) { $queryClass = $class . 'Query'; $parent = $queryClass::create()->findPk($generator->randomElement($inserted[$class])); $obj->insertAsLastChildOf($parent); } else { $obj->makeRoot(); } }; break; case 'sortable': $modifiers['sortable'] = function ($obj, $inserted) use ($class, $generator) { $maxRank = isset($inserted[$class]) ? count($inserted[$class]) : 0; $obj->insertAtRank(mt_rand(1, $maxRank + 1)); }; break; } } return $modifiers; } public function execute($con, $insertedEntities) { $obj = new $this->class(); foreach ($this->getColumnFormatters() as $column => $format) { if (null !== $format) { $obj->setByName($column, is_callable($format) ? $format($insertedEntities, $obj) : $format); } } foreach ($this->getModifiers() as $modifier) { $modifier($obj, $insertedEntities); } $obj->save($con); return $obj->getPrimaryKey(); } } generator = $generator; } public function addEntity($entity, $number, $customColumnFormatters = array(), $customModifiers = array()) { if (!$entity instanceof \Faker\ORM\Propel\EntityPopulator) { $entity = new \Faker\ORM\Propel\EntityPopulator($entity); } $entity->setColumnFormatters($entity->guessColumnFormatters($this->generator)); if ($customColumnFormatters) { $entity->mergeColumnFormattersWith($customColumnFormatters); } $entity->setModifiers($entity->guessModifiers($this->generator)); if ($customModifiers) { $entity->mergeModifiersWith($customModifiers); } $class = $entity->getClass(); $this->entities[$class] = $entity; $this->quantities[$class] = $number; } public function execute($con = null) { if (null === $con) { $con = $this->getConnection(); } $isInstancePoolingEnabled = \Propel::isInstancePoolingEnabled(); \Propel::disableInstancePooling(); $insertedEntities = array(); $con->beginTransaction(); foreach ($this->quantities as $class => $number) { for ($i=0; $i < $number; $i++) { $insertedEntities[$class][]= $this->entities[$class]->execute($con, $insertedEntities); } } $con->commit(); if ($isInstancePoolingEnabled) { \Propel::enableInstancePooling(); } return $insertedEntities; } protected function getConnection() { $class = key($this->entities); if (!$class) { throw new \RuntimeException('No class found from entities. Did you add entities to the Populator ?'); } $peer = $class::PEER; return \Propel::getConnection($peer::DATABASE_NAME, \Propel::CONNECTION_WRITE); } } generator = $generator; } public function guessFormat(ColumnMap $column) { $generator = $this->generator; if ($column->isTemporal()) { if ($column->isEpochTemporal()) { return function () use ($generator) { return $generator->dateTime; }; } else { return function () use ($generator) { return $generator->dateTimeAD; }; } } $type = $column->getType(); switch ($type) { case PropelColumnTypes::BOOLEAN: case PropelColumnTypes::BOOLEAN_EMU: return function () use ($generator) { return $generator->boolean; }; case PropelColumnTypes::NUMERIC: case PropelColumnTypes::DECIMAL: $size = $column->getSize(); return function () use ($generator, $size) { return $generator->randomNumber($size + 2) / 100; }; case PropelColumnTypes::TINYINT: return function () { return mt_rand(0, 127); }; case PropelColumnTypes::SMALLINT: return function () { return mt_rand(0, 32767); }; case PropelColumnTypes::INTEGER: return function () { return mt_rand(0, intval('2147483647')); }; case PropelColumnTypes::BIGINT: return function () { return mt_rand(0, intval('9223372036854775807')); }; case PropelColumnTypes::FLOAT: return function () { return mt_rand(0, intval('2147483647'))/mt_rand(1, intval('2147483647')); }; case PropelColumnTypes::DOUBLE: case PropelColumnTypes::REAL: return function () { return mt_rand(0, intval('9223372036854775807'))/mt_rand(1, intval('9223372036854775807')); }; case PropelColumnTypes::CHAR: case PropelColumnTypes::VARCHAR: case PropelColumnTypes::BINARY: case PropelColumnTypes::VARBINARY: $size = $column->getSize(); return function () use ($generator, $size) { return $generator->text($size); }; case PropelColumnTypes::LONGVARCHAR: case PropelColumnTypes::LONGVARBINARY: case PropelColumnTypes::CLOB: case PropelColumnTypes::CLOB_EMU: case PropelColumnTypes::BLOB: return function () use ($generator) { return $generator->text; }; case PropelColumnTypes::ENUM: $valueSet = $column->getValueSet(); return function () use ($generator, $valueSet) { return $generator->randomElement($valueSet); }; case PropelColumnTypes::OBJECT: case PropelColumnTypes::PHP_ARRAY: return null; } } } class = $class; } public function getClass() { return $this->class; } public function setColumnFormatters($columnFormatters) { $this->columnFormatters = $columnFormatters; } public function getColumnFormatters() { return $this->columnFormatters; } public function mergeColumnFormattersWith($columnFormatters) { $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); } public function guessColumnFormatters(\Faker\Generator $generator, Mandango $mandango) { $formatters = array(); $nameGuesser = new \Faker\Guesser\Name($generator); $columnTypeGuesser = new \Faker\ORM\Mandango\ColumnTypeGuesser($generator); $metadata = $mandango->getMetadata($this->class); foreach ($metadata['fields'] as $fieldName => $field) { if ($formatter = $nameGuesser->guessFormat($fieldName)) { $formatters[$fieldName] = $formatter; continue; } if ($formatter = $columnTypeGuesser->guessFormat($field)) { $formatters[$fieldName] = $formatter; continue; } } foreach (array_merge($metadata['referencesOne'], $metadata['referencesMany']) as $referenceName => $reference) { if (!isset($reference['class'])) { continue; } $referenceClass = $reference['class']; $formatters[$referenceName] = function ($insertedEntities) use ($referenceClass) { if (isset($insertedEntities[$referenceClass])) { return Base::randomElement($insertedEntities[$referenceClass]); } }; } return $formatters; } public function execute(Mandango $mandango, $insertedEntities) { $metadata = $mandango->getMetadata($this->class); $obj = $mandango->create($this->class); foreach ($this->columnFormatters as $column => $format) { if (null !== $format) { $value = is_callable($format) ? $format($insertedEntities, $obj) : $format; if (isset($metadata['fields'][$column]) || isset($metadata['referencesOne'][$column])) { $obj->set($column, $value); } if (isset($metadata['referencesMany'][$column])) { $adder = 'add'.ucfirst($column); $obj->$adder($value); } } } $mandango->persist($obj); return $obj; } } generator = $generator; $this->mandango = $mandango; } public function addEntity($entity, $number, $customColumnFormatters = array()) { if (!$entity instanceof \Faker\ORM\Mandango\EntityPopulator) { $entity = new \Faker\ORM\Mandango\EntityPopulator($entity); } $entity->setColumnFormatters($entity->guessColumnFormatters($this->generator, $this->mandango)); if ($customColumnFormatters) { $entity->mergeColumnFormattersWith($customColumnFormatters); } $class = $entity->getClass(); $this->entities[$class] = $entity; $this->quantities[$class] = $number; } public function execute() { $insertedEntities = array(); foreach ($this->quantities as $class => $number) { for ($i=0; $i < $number; $i++) { $insertedEntities[$class][]= $this->entities[$class]->execute($this->mandango, $insertedEntities); } } $this->mandango->flush(); return $insertedEntities; } } generator = $generator; } public function guessFormat($field) { $generator = $this->generator; switch ($field['type']) { case 'boolean': return function () use ($generator) { return $generator->boolean; }; case 'integer': return function () { return mt_rand(0, intval('4294967295')); }; case 'float': return function () { return mt_rand(0, intval('4294967295'))/mt_rand(1, intval('4294967295')); }; case 'string': return function () use ($generator) { return $generator->text(255); }; case 'date': return function () use ($generator) { return $generator->datetime; }; default: return null; } } } generator = $generator; } public function getFormatters() { $formatters = array(); $providers = array_reverse($this->generator->getProviders()); $providers[]= new \Faker\Provider\Base($this->generator); foreach ($providers as $provider) { $providerClass = get_class($provider); $formatters[$providerClass] = array(); $refl = new \ReflectionObject($provider); foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflmethod) { if ($reflmethod->getDeclaringClass()->getName() == 'Faker\Provider\Base' && $providerClass != 'Faker\Provider\Base') { continue; } $methodName = $reflmethod->name; if ($reflmethod->isConstructor()) { continue; } $parameters = array(); foreach ($reflmethod->getParameters() as $reflparameter) { $parameter = '$'. $reflparameter->getName(); if ($reflparameter->isDefaultValueAvailable()) { $parameter .= ' = ' . var_export($reflparameter->getDefaultValue(), true); } $parameters []= $parameter; } $parameters = $parameters ? '('. join(', ', $parameters) . ')' : ''; try { $example = $this->generator->format($methodName); } catch (\InvalidArgumentException $e) { $example = ''; } if (is_array($example)) { $example = "array('". join("', '", $example) . "')"; } elseif ($example instanceof \DateTime) { $example = "DateTime('" . $example->format('Y-m-d H:i:s') . "')"; } elseif ($example instanceof Generator || $example instanceof UniqueGenerator) { $example = ''; } else { $example = var_export($example, true); } $formatters[$providerClass][$methodName . $parameters] = $example; } } return $formatters; } } generator = $generator; } public function guessFormat($name) { $name = Base::toLower($name); $generator = $this->generator; if (preg_match('/^is[_A-Z]/', $name)) { return function () use ($generator) { return $generator->boolean; }; } if (preg_match('/(_a|A)t$/', $name)) { return function () use ($generator) { return $generator->dateTime; }; } switch ($name) { case 'first_name': case 'firstname': return function () use ($generator) { return $generator->firstName; }; case 'last_name': case 'lastname': return function () use ($generator) { return $generator->lastName; }; case 'username': case 'login': return function () use ($generator) { return $generator->userName; }; case 'email': return function () use ($generator) { return $generator->email; }; case 'phone_number': case 'phonenumber': case 'phone': return function () use ($generator) { return $generator->phoneNumber; }; case 'address': return function () use ($generator) { return $generator->address; }; case 'city': return function () use ($generator) { return $generator->city; }; case 'streetaddress': return function () use ($generator) { return $generator->streetAddress; }; case 'postcode': case 'zipcode': return function () use ($generator) { return $generator->postcode; }; case 'state': return function () use ($generator) { return $generator->state; }; case 'country': return function () use ($generator) { return $generator->country; }; case 'title': return function () use ($generator) { return $generator->sentence; }; case 'body': case 'summary': return function () use ($generator) { return $generator->text; }; } } } generator = $generator; $this->maxRetries = $maxRetries; } public function __get($attribute) { return $this->__call($attribute, array()); } public function __call($name, $arguments) { if (!isset($this->uniques[$name])) { $this->uniques[$name] = array(); } $i = 0; do { $res = call_user_func_array(array($this->generator, $name), $arguments); $i++; if ($i > $this->maxRetries) { throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries)); } } while (array_key_exists($res, $this->uniques[$name])); $this->uniques[$name][$res]= null; return $res; } } default = $default; } public function __get($attribute) { return $this->default; } public function __call($method, $attributes) { return $this->default; } } providers, $provider); } public function getProviders() { return $this->providers; } public function seed($seed = null) { if ($seed === null) { mt_srand(); } else { mt_srand($seed); } } public function format($formatter, $arguments = array()) { return call_user_func_array($this->getFormatter($formatter), $arguments); } public function getFormatter($formatter) { if (isset($this->formatters[$formatter])) { return $this->formatters[$formatter]; } foreach ($this->providers as $provider) { if (method_exists($provider, $formatter)) { $this->formatters[$formatter] = array($provider, $formatter); return $this->formatters[$formatter]; } } throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter)); } public function parse($string) { return preg_replace_callback('/\{\{\s?(\w+)\s?\}\}/u', array($this, 'callFormatWithMatches'), $string); } protected function callFormatWithMatches($matches) { return $this->format($matches[1]); } public function __get($attribute) { return $this->format($attribute); } public function __call($method, $attributes) { return $this->format($method, $attributes); } } generator->parse($format); } public static function streetPrefix() { return static::randomElement(static::$streetPrefix); } } generator->parse($format); } public static function companyPrefix() { return static::randomElement(static::$companyPrefix); } public static function companyName() { return static::randomElement(static::$companyName); } } numberBetween($min = 10000, $max = 100000000); } } generator->parse($format); } public static function country() { return static::randomElement(static::$country); } public static function postcode() { return static::toUpper(static::bothify(static::randomElement(static::$postcode))); } public static function regionSuffix() { return static::randomElement(static::$regionSuffix); } public static function region() { return static::randomElement(static::$region); } public static function citySuffix() { return static::randomElement(static::$citySuffix); } public function city() { return static::randomElement(static::$city); } public static function streetSuffix() { return static::randomElement(static::$streetSuffix); } public static function street() { return static::randomElement(static::$street); } } format('ymd'); $genderAndCenturyId = (string) static::numberBetween(1, 6); $randomDigits = (string) static::numerify('#####'); return $dateAsString . $genderAndCenturyId . $randomDigits; } } generator->parse($format); } public static function companyPrefix() { return static::randomElement(static::$companyPrefixes); } public static function companyNameElement() { return static::randomElement(static::$companyElements); } public static function companyNameSuffix() { return static::randomElement(static::$companyNameSuffixes); } public static function businessIdentificationNumber(\DateTime $registrationDate = null) { if (!$registrationDate) { $registrationDate = \Faker\Provider\DateTime::dateTimeThisYear(); } $dateAsString = $registrationDate->format('ym'); $legalEntityType = (string) static::numberBetween(4, 6); $legalEntityAdditionalType = (string) static::numberBetween(0, 3); $randomDigits = (string) static::numerify('######'); return $dateAsString . $legalEntityType . $legalEntityAdditionalType . $randomDigits; } } > 8) | (($tLo & 0xff000000) >> 24); $tMi = (($tMi & 0x00ff) << 8) | (($tMi & 0xff00) >> 8); $tHi = (($tHi & 0x00ff) << 8) | (($tHi & 0xff00) >> 8); } $tHi &= 0x0fff; $tHi |= (3 << 12); $uuid = sprintf( '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x', $tLo, $tMi, $tHi, $csHi, $csLo, $byte[10], $byte[11], $byte[12], $byte[13], $byte[14], $byte[15] ); return $uuid; } } city() . static::area(); } public static function postcode() { $prefix = str_pad(mt_rand(1, 85), 2, 0, STR_PAD_LEFT); $suffix = '00'; return $prefix . mt_rand(10, 88) . $suffix; } } 'Ain'), array('02' => 'Aisne'), array('03' => 'Allier'), array('04' => 'Alpes-de-Haute-Provence'), array('05' => 'Hautes-Alpes'), array('06' => 'Alpes-Maritimes'), array('07' => 'Ardèche'), array('08' => 'Ardennes'), array('09' => 'Ariège'), array('10' => 'Aube'), array('11' => 'Aude'), array('12' => 'Aveyron'), array('13' => 'Bouches-du-Rhône'), array('14' => 'Calvados'), array('15' => 'Cantal'), array('16' => 'Charente'), array('17' => 'Charente-Maritime'), array('18' => 'Cher'), array('19' => 'Corrèze'), array('2A' => 'Corse-du-Sud'), array('2B' => 'Haute-Corse'), array('21' => "Côte-d'Or"), array('22' => "Côtes-d'Armor"), array('23' => 'Creuse'), array('24' => 'Dordogne'), array('25' => 'Doubs'), array('26' => 'Drôme'), array('27' => 'Eure'), array('28' => 'Eure-et-Loir'), array('29' => 'Finistère'), array('30' => 'Gard'), array('31' => 'Haute-Garonne'), array('32' => 'Gers'), array('33' => 'Gironde'), array('34' => 'Hérault'), array('35' => 'Ille-et-Vilaine'), array('36' => 'Indre'), array('37' => 'Indre-et-Loire'), array('38' => 'Isère'), array('39' => 'Jura'), array('40' => 'Landes'), array('41' => 'Loir-et-Cher'), array('42' => 'Loire'), array('43' => 'Haute-Loire'), array('44' => 'Loire-Atlantique'), array('45' => 'Loiret'), array('46' => 'Lot'), array('47' => 'Lot-et-Garonne'), array('48' => 'Lozère'), array('49' => 'Maine-et-Loire'), array('50' => 'Manche'), array('51' => 'Marne'), array('52' => 'Haute-Marne'), array('53' => 'Mayenne'), array('54' => 'Meurthe-et-Moselle'), array('55' => 'Meuse'), array('56' => 'Morbihan'), array('57' => 'Moselle'), array('58' => 'Nièvre'), array('59' => 'Nord'), array('60' => 'Oise'), array('61' => 'Orne'), array('62' => 'Pas-de-Calais'), array('63' => 'Puy-de-Dôme'), array('64' => 'Pyrénées-Atlantiques'), array('65' => 'Hautes-Pyrénées'), array('66' => 'Pyrénées-Orientales'), array('67' => 'Bas-Rhin'), array('68' => 'Haut-Rhin'), array('69' => 'Rhône'), array('70' => 'Haute-Saône'), array('71' => 'Saône-et-Loire'), array('72' => 'Sarthe'), array('73' => 'Savoie'), array('74' => 'Haute-Savoie'), array('75' => 'Paris'), array('76' => 'Seine-Maritime'), array('77' => 'Seine-et-Marne'), array('78' => 'Yvelines'), array('79' => 'Deux-Sèvres'), array('80' => 'Somme'), array('81' => 'Tarn'), array('82' => 'Tarn-et-Garonne'), array('83' => 'Var'), array('84' => 'Vaucluse'), array('85' => 'Vendée'), array('86' => 'Vienne'), array('87' => 'Haute-Vienne'), array('88' => 'Vosges'), array('89' => 'Yonne'), array('90' => 'Territoire de Belfort'), array('91' => 'Essonne'), array('92' => 'Hauts-de-Seine'), array('93' => 'Seine-Saint-Denis'), array('94' => 'Val-de-Marne'), array('95' => "Val-d'Oise"), array('971' => 'Guadeloupe'), array('972' => 'Martinique'), array('973' => 'Guyane'), array('974' => 'La Réunion'), array('976' => 'Mayotte') ); public static function streetPrefix() { return static::randomElement(static::$streetPrefix); } public static function region() { return static::randomElement(static::$regions); } public static function department() { return static::randomElement(static::$departments); } public static function departmentName() { $randomDepartmentName = array_values(static::department()); return $randomDepartmentName[0]; } public static function departmentNumber() { $randomDepartmentNumber = array_keys(static::department()); return $randomDepartmentNumber[0]; } } generator->parse($format)); if ($this->isCatchPhraseValid($catchPhrase)) { break; } } while (true); return $catchPhrase; } public function siret($formatted = true) { $siret = $this->siren(false); $nicFormat = static::randomElement(static::$siretNicFormats); $siret .= $this->numerify($nicFormat); $siret .= Luhn::computeCheckDigit($siret); if ($formatted) { $siret = substr($siret, 0, 3) . ' ' . substr($siret, 3, 3) . ' ' . substr($siret, 6, 3) . ' ' . substr($siret, 9, 5); } return $siret; } public function siren($formatted = true) { $siren = $this->numerify('%#######'); $siren .= Luhn::computeCheckDigit($siren); if ($formatted) { $siren = substr($siren, 0, 3) . ' ' . substr($siren, 3, 3) . ' ' . substr($siren, 6, 3); } return $siren; } protected static $wordsWhichShouldNotAppearTwice = array('sécurité', 'simpl'); protected static function isCatchPhraseValid($catchPhrase) { foreach (static::$wordsWhichShouldNotAppearTwice as $word) { $beginPos = strpos($catchPhrase, $word); $endPos = strrpos($catchPhrase, $word); if ($beginPos !== false && $beginPos != $endPos) { return false; } } return true; } } generator->parse(static::randomElement(static::$lastNameFormat)); } public static function lastNameMale() { return static::randomElement(static::$lastNameMale); } public static function lastNameFemale() { return static::randomElement(static::$lastNameFemale); } } format('w')]; } public static function dayOfMonth($max = 'now') { return static::dateTime($max)->format('j'); } public function formattedDate() { $format = static::randomElement(static::$formattedDateFormat); return $this->generator->parse($format); } } generator->parse($format)); } public function ico() { $ico = static::numerify('#######'); $split = str_split($ico); $prod = 0; foreach (array(8, 7, 6, 5, 4, 3, 2) as $i => $p) { $prod += $p * $split[$i]; } $mod = $prod % 11; if ($mod === 0 || $mod === 10) { return "{$ico}1"; } elseif ($mod === 1) { return "{$ico}0"; } return $ico . (11 - $mod); } } generator->parse(static::randomElement(static::$lastNameFormat)); } public static function lastNameMale() { return static::randomElement(static::$lastNameMale); } public static function lastNameFemale() { return static::randomElement(static::$lastNameFemale); } } 5) { throw new \InvalidArgumentException('indexSize must be at most 5'); } $words = $this->getConsecutiveWords($indexSize); $result = array(); $resultLength = 0; $next = static::randomKey($words); while ($resultLength < $maxNbChars && isset($words[$next])) { $word = static::randomElement($words[$next]); $currentWords = explode(' ', $next); $currentWords[] = $word; array_shift($currentWords); $next = implode(' ', $currentWords); if ($resultLength == 0 && !preg_match('/^\p{Arabic}/u', $word)) { continue; } $result[] = $word; $resultLength += strlen($word) + 1; } array_pop($result); $result = implode(' ', $result); return $result.'.'; } protected static $baseText = <<<'EOT' ذكر سلفنا الصالح - رضي الله عنهم - أن جزيرة من جزائر الهند التي تحت خط الاستواء، وهي الجزيرة التي يتولد بها الإنسان من غير أم ولا أب، وبها شجر يثمر نساء، وهي التي ذكر المسعودي أنها جزيرة الوقواق لان تلك الجزيرة اعدل بقاع الأرض هواء؛ أتممها لشروق النور الأعلى عليها استعدادً، وان كان ذلك خلاف ما يراه جمهور الفلاسفة وكبار الأطباء، فانهم يرون إن اعدل ما في المعمورة الإقليم الرابع، فان كانوا قالوا ذلك لأنه صح عندهم انه ليس على خط الاستواء عمارة لمانع من الموانع الأرضية، فلقولهم: أن الإقليم الرابع اعدل بقاع الأرض وجه، وان كانوا إنما أرادوا بذلك إن ما على خط الاستواء شديد الحرارة، كالذي يصرح به أكثرهم فهو خطأ يقوم البرهان على خلافه. وذلك أنه قد تبرهن في العلوم الطبيعية أنه لا سبب لتكون الحرارة إلا الحركة أو ملاقاة الأجسام الحارة والإضاءة؛ وتبين فيها أيضاً إن الشمس بذاتها غير حارة ولا متكيفة بشيء من هذه الكيفيات المزاجية؛ وقد تبين فيها أيضاً إن الأجسام التي تقبل الإضاءة أتم القبول، هي الأجسام الصقيلة غير الشفافة، ويليها في قبول ذلك الأجسام الكثيفة غير الصقيلة، فأما الأجسام الشفافة التي لاشيء فيها من الكثافة فلا تقبل الضوء بوجه. وهذا وحده مما برهنه الشيخ أبو علي خاصة، ولم يذكره من تقدمه، فإذا صحت هذه المقدمات، فاللازم عنها أن الشمس لا تسخن الأرض كما تسخن الأجسام الحارة أجسام أخر تماسها، لان الشمس في ذاتها غير حارة ولا الأرض أيضاً تسخن بالحركة لأنها ساكنة وعلى حالة واحدة في شروق الشمس عليها وفي وقت مغيبها عنها وأحوالها في التسخين والتبريد، ظاهرة الاختلاف للحس في هذين الوقتين. ولا الشمس أيضاً تسخن الهواء أولاً ثم تسخن بعد ذلك الأرض بتوسط سخونة الهواء، وكيف يكون ذلك ونحن نجد أن ما قرب من الهواء من الأرض في وقت الحر، أسخن كثيراً من الهواء الذي يبعد منه علواً؟ فبقي أن تسخين الشمس للأرض إنما هو على سبيل الإضاءة لا غير، فان الحرارة تتبع الضوء أبداً: حتى إن الضوء إذا افرط في المرأة المقعرة، أشعل ما حاذاها. وقد ثبت في علوم التعاليم بالبراهين القطعية، أن الشمس كروية الشكل، وأن الأرض كذلك، وأن الشمس أعظم من الأرض كثيراً، وأن الذي يستضيء من الشمس أبداً هو أعظم من نصفها، وأن هذا النصف المضيء من الأرض في كل وقت أشد ما يكون الضوء في وسطه، لأنه أبعد المواضع من المظلمة، ولأنه يقابل من الشمس أجزاءاً أكثر، وما قرب من المحيط كان أقل ضوءاً حتى ينتهي إلى الظلمة عند محيط الدائرة الذي ما أضاء موقعه من الأرض قط، وإنما يكون الموضع وسط دائرة الضياء إذا كانت الشمس على سمت رؤوس الساكنين فيه، وحينئذ تكون الحرارة في ذلك الموضع أشد ما يكون فان كان الموضع مما تبعد الشمس عن مسامتة رؤوس أهله، كان شديد البرودة جداً، وان كان مما تدوم فيه المسامتة كان شديد الحرارة، وقد ثبت في علم الهيئة أن بقاع الأرض التي على خط الاستواء لا تسامت الشمس رؤوس أهلها سوى مرتين في العام: عند حلولها برأس الحمل؛ وعند حلولها برأس الميزان. وهي في سائر العام ستة أشهر جنوباً منهم، وستة أشهر شمالاً منهم: فليس عندهم حر مفرط، ولا برد مفرط. وأحوالهم بسبب ذلك متشابهة. وهذا القول يحتاج إلى بيان أكثر من هذا، لا يليق بما نحن بسبيله؛ وإنما نبهناك عليه، لأنه من الأمور التي تشهد بصحة ما ذكر من تجويز تولد الإنسان بتلك البقعة من غير أم ولا أب. فمنهم من بت الحكم وجزم القضية بأن حي بن يقظان من جملة من تكون في تلك البقعة من غير أم ولا أب، ومنهم من أنكر ذلك وروى من أمره خبراً نقصه عليك، فقال: انه كان بازاء تلك الجزيرة، جزيرة عظيمة متسعة الأكتاف، كثيرة الفوائد، عامرة بالناس، يملكها رجل منهم شديد الأنفة والغيرة، وكانت له أخت ذات جمال وحسن باهر فعضلها ومنعها الأزواج إذا لم يجد لها كفواً. وكان له قريب يسمى يقظان فتزوجها سراً على وجه جائز في مذهبهم المشهور في زمنهم. ثم إنها حملت منه ووضعت طفلاً. فلما خافت أن يفتضح أمرها وينكشف سرها، وضعته في تابوت أحكمت زمه بعد أن أروته من الرضاع؛ وخرجت به في أول الليل في جملة من خدمها وثقاتها إلى ساحل البحر، وقلبها يحترق صبابةً به، وخوفاً عليه، ثم إنها ودعته وقالت: "اللهم انك خلقت هذا الطفل ولم يكن شيئاً مذكوراً، ورزقته في ظلمات الأحشاء، وتكفلت به حتى تم واستوى. وأنا قد سلمته إلى لطفك، ورجوت له فضلك، خوفاً من هذا الملك الغشوم الجبار العنيد. فكن له، ولا تسلمه، يا أرحم الراحمين" ثم قذفت به في اليم. فصادف ذلك جري الماء بقوة المد، فاحتمله من ليلته إلى ساحل الجزيرة الأخرى المتقدم ذكرها. وكان المد يصل في ذلك الوقت إلى موضع لا يصل إليه بعد علم. فأدخله الماء بقوته إلى أجمة ملتفة الشجر عذبة التربة، مستورة عن الرياح والمطر، محجوبة عن الشمس تزاور عنها إذا طلعت، وتميل إذا غربت. ثم أخذ الماء في الجزر. وبقي التابوت في ذلك الموضع، وعلت الرمال بهبوب الرياح، وتراكمت بعد ذلك حتى سدت مدخل الماء إلى تلك الأجمة. فكان المد لا ينتهي إليها، وكانت مسامير التابوت قد فلقت، وألواحه قد اضطربت عند رمي الماء في تلك الأجمة. فلما أشتد الجوع بذلك الطفل، بكى واستغاث وعالج الحركة، فوقع صوته في أذن ظبية فقدت طلاها، خرج من كناسه فحمله العقاب، فلما سمعت الصوت ظنته ولدها. فتتبعت الصوت وهي تتخيل طلاها حتى وصلت إلى التابوت، ففحصت عنه بأظلافها وهو ينوء ويئن من داخله، حتى طار عن التابوت لوح من أعلاه. فحنت الظبية وحنت عليه ورئفت به، وألقمه حلمتها وأروته لبناً سائغاً. ومازالت تتعهده وتربيه وتدفع عنه الأذى. هذا ما كان من ابتداء أمره عند من ينكره التولد. ونحن نصف هنا كيف تربى وكيف أنتقل في أحواله حتى يبلغ المبلغ العظيم. وأما الذين زعموا أنه تولد من الأرض فانهم قالوا إن بطناً من أرض تلك الجزيرة تخمرت فيه طينه على مر السنين والأعوام، حتى امتزج فيها الحار بالبارد، والرطب باليابس، امتزاج تكافؤ وتعادل في القوى. وكانت هذه الطينة المتخمرة كبيرة جداً وكان بعضها يفضل بعضاً في اعتدال المزاج والتهيؤ لتكون الأمشاج. وكان الوسط منها أعدل ما فيها وأتمه مشابهة بمزاج الإنسان: فتمخضت تلك الطينة، وحدث فيها شبه نفاخات الغليان لشدة لزوجتها: وحدث في الوسط منها لزوجة ونفاخة صغيرة جداً، منقسمة بقسمين، بينها حجاب رقيق، ممتلئة بجسم لطيف هوائي في غاية من الاعتدال اللائق به، فتعلق به عند ذلك الروح الذي هو من أمر الله تعالى وتشبث به تشبثاً يعسر انفصاله عنه عند الحس وعند العقل؛ إذ قد تبين أن هذا الروح دائم الفيضان من عند الله عز وجل، وأنه بمنزلة نور الشمس الذي هو دائم الفيضان على العالم. فمن الأجسام ما لا يستضيء به، وهو الهواء الشفاف جداً؛ ومنها ما يستضيء به بعض الاستضاءة، وهي الأجسام الكثيفة غير الصقيلة وهذه تختلف في قبول الضياء، وتختلف بحسب ذلك ألوانها، ومنها ما يستضيء به غاية الاستضاءة وهي الأجسام الصقيلة كالمرأة ونحوها. فإذا كانت هذه المرأة مقعرة على شكل مخصوص، حدث فيها النار لإفراط الضياء. الذي هو الروح، الذي هو من أمر الله تعالى، فياض أبداً على جميع الموجودات؛ فمنها ما لا يظهر أثره فيه اعدم الأستعداد، وهي الجمادات التي لا حياة لها، وهذه بمنزلة الهواء في المثال المتقدم، ومنها ما يظهر أثره فيه، وهي أنواع النبات بحسب استعداداتها وهذه بمنزلة الأجسام الكثيفة في المثال المتقدم؛ ومنها ما يظهر أثره فيه ظهوراً كثيراً، وهي الأجسام الصقيلة في المثال المتقدم. ومن هذه الأجسام الصقيلة ما يزيد على شدة قبوله لضياء الشمس أنه يحكي صورة الشمس، ومثالها. وكذلك أيضاً من الحيوان ما يزيد على شدة قبوله للروح أنه يحكي الروح ويتصور بصورته وهو الإنسان خاصة. واليه الإشارة بقوله صلى الله عليه وسلم: "إن الله خلق أدم على صورته". فان قويت في هذه الصورة حتى تتلاشى جميع الصور في حقها، وتبقى هي وحدها، وتحرق سبحات نورها كل ما أدركته، كانت حينئذ بمنزلة المرأة المنعكسة على نفسها المحرقة لسوها وهذا لا يكون إلا للأنبياء صلوات الله عليهم أجمعين. وهذا كله مبين في مواضعه اللائقة به، فليرجع إلى تمام ما حكوه من وصف ذلك التخلق. قالوا: فلما تعلق هذا الروح بتلك القرارة، خضعت له جميع القوى وسجدت له وسخرت بأمر الله تعالى في كمالها، فتكون بازاء تلك القرارة نفاخة أخرى منقسمة إلى ثلاث قرارت بينهما حجب لطيفة، ومسالك نافذة، وامتلأت بمثل ذلك الهوائي الذي امتلأت منه القرارة الأولى؛ إلا أنه ألطف منه. وفي هذه البطون الثلاثة المنقسمة من واحد، طائفة من تلك القوى التي خضعت له وتوكلت بحراستها والقيام عليها، وإنهاء ما يطرأ فيها من دقيق الأشياء وجليلها إلى الروح الأول المتعلق بالقرارة الأولى. وتكون بازاء هذه القرارة من الجهة المقابلة للقراءة الثانية، نفاخة ثالثة مملوءة جسماً هوائياً، إلا أنه أغلظ من الأولين وسكن في هذه القرارة فريق من تلك القوى الخاضعة، وتوكلت بحفظها و القيام عليها؛ فكانت هذه القرارة الأولى والثانية والثالثة، أول ما تخلق من تلك الطينة المتحمرة على الترتيب الذي ذكرناه. واحتاج بعضها إلى بعض: فالأولى منها حاجتها إلى الآخرين، حاجة استخدام وتسخير. والأخريان حاجتهما إلى الأولى حاجة المرؤوس إلى الرئيس، والمدبر إلى المدبر؛ وكلاهما لما يتخلق بعدهما من الأعضاء رئيس لا مرؤوس. وأحدهما، وهو الثاني، أتمم رئاسة من الثالث فالأول منهما لما تعلق به الروح، واشتعلت حرارته تشكل بشكل النار لصنوبري وتشكل أيضاً الجسم الغليظ المحدق به على شكله، وتكون لحماً صلباً، وصار عليه غلاف صفيق يحفظه وسمي العضو كله قلباً واحتاج لما يتبع الحرارة من التحليل وافناء الرطوبات إلى شيء يمده ويغذوه، ويخلف ما تحلل منه على الدوام، وإلا لم يطل بقاؤه، واحتاج أيضاً إلى تحسس بما يلائمه فيجذبه، وبما يخالفه فيدفعه. فتكفل له العضو الواحد بما فيه من القوى التي أصلها منه بحاجته الواحدة، وتكفل له العضو الآخر بحاجته الأخرى. وكان المتكفل بالحس هو الدماغو المتكفل بالغذاء هو الكبد؛ واحتاج كل واحد من هذين إليه في أن يمدها بحرارته، وبالقوى المخصوصة بهما التي أصلها منه ، فانتسجت بينهما لذلك كله مسالك وطرق: بعضها أوسع من بعض بحسب ما تدعواليه الضرورة، فكانت الشرايين و العروق. وصفه الطبيعيون في خلقة الجنين في الرحم، لم يغادروا من ذلك شيئاً، إلى أن كمل خلقه، وتمت أعضاؤه، وحصل في حد خروج الجنين من البطن، واستعانوا في وصف كمال ذلك بتلك الطينة الكبيرة المتخمرة، وأنها كانت قد تهيأت لان يتخلق منها كل ما يحتاج إليه في خلق الإنسان من الأغشية المجللة لجملة بدنه وغيرها فلما كمل انشقت عنه تلك الأغشية، بشبه المخاض، وتصدع باقي الطينة إذ كان قد لحقه الجفاف. ثم استغاث ذلك الطفل عند فناء مادة غذائه واشتداد جوعه، فلبته ظبية فقدت طلاها. ثم استوى عبد ما وصفه هؤلاء بعد هذا الموضع، وما وصفه الطائفة الأولى في معنى التربية؛ فقالوا جميعاً: إن الظبية التي تكفلت به وافقت خصباً ومرعى أثيثاً، فكثر لحمها وكثر لبنها، حتى قام بغذاء ذلك الطفل أحسن قيام. وكانت معه لا تبعد عنه إلا لضرورة الرعي. وألف الطفل تلك الظبية حتى كان بحيث إذا هي أبطأت عنه اشتد بكاؤه فطارت إليه. ولم يكن بتلك الجزيرة شيء من السباع العادية، فتربى الطفل ونما واغتذى بلبن تلك الظبية إلى أن تم له حولان، وتدرج في المشي وأثغر فكان يتبع تلك الظبية، وكانت هي ترفق به و ترحمه وتحمله إلى مواضع فيها شجر مثمر فكانت تطعمه ما تساقط من ثمراتها الحلوة النضيجة؛ وما كان منها صلب القشر كسرته له بطواحنها؛ ومتى عاد إلى اللبن أروته، ومتى ظمئ إلى الماء أرودته، متى ضحا ظللته؛ ومتى خصر أدفأته. وإذا جن الليل صرفته إلى مكان الأول وجللته بنفسها وبريش كان هناك؛ مما ملئ به التابوت أولاً في وقت وضع الطفل فيه. وكان في غدوهما ورواحهما قد ألفهما ربرب يسرح ويبيت معهما حيث مبيتهما. فما زال الطفل مع الظباء على تلك الحال: يحكي نغمتها بصوته حتى لا يكاد يفرق بينهما؛ وكذلك كان يحكي جميع ما يسمعه من أصوات الطير وأنواع سائر الحيوان محاكاة شديدة لقوة انفعاله لما يريده ما كانت محاكاته لأصوات الظباء في الاستصراخ والاستئلاف والاستدعاء والاستدفاع. إذ للحيوانات في هذه الأحوال المختلفة أصوات مختلفة فألفته الوحوش وألفها؛ ولم تنكره ولا أنكرها. فلما ثبت في نفسه أمثلة الأشياء بعد مغيبها عن مشاهدته، حدث له نزوغ إلى بعضها؛ وكراهية لبعض. وكان في ذلك كله ينظر إلى جميع الحيوانات فيراها كاسية بالاوبار و الأشعار و أنواع الريش، وكان يرى ما لها من العدو وقوة البطش، وما لها من الأسلحة المعدة لمدافعة من ينازعها، مثل القرون و الأنياب و الحوافر و الصياصي و المخالب. ثم يرجع إلى نفسه، فيرى ما به من العري وعدم السلاح، وضعف العدو، وقلة البطش، عندما كانت تنازعه الوحوش أكل الثمرات، وتستبد بها دونه، وتغلبه عليها، فلا يستطيع المدافعة عن نفسه، ولا الفرار عن شيء منها. وكان يرى أترابه من أولاد الظباء، قد تبتت لها قرون، بعد أن لم تكن، وصارت قوية بعد ضعفها في العدو. ولم ير لنفسه شيئاً من ذلك فكان يفكر في ذلك ولا يدري ما سببه. وكان ينظر إلى ذوي العاهات والخلق الناقص فلا يجد لنفسه شبيهاً فيهم. وكان أيضاً ينظر إلى مخارج الفضول من سائر الحيوانات، فيراها مستورة: أما مخرج أغلظ الفضلتين فبالاذناب، وأما مخرج وأما مخرج أرقهما فبالاوبار وما أشبههما. ولأنها كانت أيضاً اخفى قضباناً منه. فكان ذلك ما يكربه ويسؤه. فلما طال همه في ذلك كله، وهو قد قارب سبعة اعوام، ويئس من أن يكمل له ما قد أضر به نقصه، اتخذ من أوراق الشجر العريضة شيئاً جعل بعضه خلفه و بعضه قدمه، وعمل من الخوض والحلفاء شبه حزام على وسطه، علق به تلك الأوراق فلم يلبث إلا يسيراً حتى ذوى ذلك الورق وجف وتساقط. فما زال يتخذ غيره ويخصف بعضه ببعض طاقات مضاعفة، وربما كان ذلك أطول لبقائه إلا انه على كل حال قصير المدة. واتخذ من أغصان الشجر عصياً وسوى أطرافها وعدل متنها. وكان بها على الوحوش المنازعة له، فيحمل على الضعيف منها، ويقاوم القوي منها، فنبل بذلك قدره عند نفسه بعض نباله، ورأى أن ليده فضلاً كثيراً على أيديها: إذ أمكن له بها ستر عورته واتخاذ العصي التي يدافع بها عن حوزته، ما استغنى به عما أراده من الذنب والعذاب الطبيعي. وفي خلال ذلك ترعرع واربى على السبع سنين، وطال به العناء في تجديد الأوراق التي كان يستتر بها. فكانت نفسه عند ذلك تنازعه إلى اتخاذ ذنب من ذنوب الوحوش الميتة ليعلقه على نفسه، إلا أنه كان يرى أحياء الوحوش تتحامى ميتها وتفر عنه فلا يتأتى له الأقدام على ذلك الفعل، إلى أن صادف في الأيام نسراً ميتاً فهدي إلى نيل أمله منه، واغتنم الفرصة في، إذ لم ير للوحوش عنه نفرةً فأقدم عليه، وقطع جناحيه وذنبه صحاحاً كما هي، وفتح ريشها وسواها، وسلخ عنه سائر جلده، وفصله على قطعتين: ربط إحداهما على ظهره، وأخرى على سرته وما تحتها، وعلق الذنب من خلفه، وعلق الجناحين على عضديه، فأكسبه ذلك ستراً ودفئاً ومهابة في نفوس جميع الوحوش، حتى كانت لا تنازعه ولا تعارضه. فصار لايدنو إليه شيء منها سوى الظبية التي كانت أرضعته وربته: فانها لم تفارقه ولا فارقها، إلى أن اسنت وضعغت، فكان يرتاد بها المراعي الخصبة ويجتني لها الثمرات الحلوة، ويطعمها. ومازل الهزل والضعف يستولي عليها ويتوالى، إلى أن أدركها الموت، فسكنت حركاتها بالجملة، وتعطلت جميع أفعالها. فلما رأها الصبي على تلك الحالة، جزع جزعاً شديداً، وكادت نفسه تفيض أسفاً عليها. فكان يناديها بالصوت الذي كانت عادتها أن تجيبه عند سماعه، ويصيح بأشد ما يقدر عليه، فلا لها عند ذلك حركة ولا تغييراً. فكان ينظر إلى أذنيها والى عينيها فلا يرى بها آفة ظاهرة، وكذلك كان ينظر إلى جميع أعضائها فلا يرى بشيء منها آفة. فكان يطمع إن يعثر على موضع الآفة فيزيلها عنها، فترجع إلى ما كانت عليه فلم ياتت له شيء من ذلك ولا استطاعة. وكان الذي أرشده لهذا الرأي ما كان قد اعتبره في نفسه قبل ذلك: لانه كان يرى انه إذا غمض عينيه أو حجبهما بشيء لا يبصر حتى نزول ذلك العائق، وكذلك كان يرى انه اذا ادخل إصبعه في أذنيه وسدها لا يسمع شيئاً حتى يزول ذلك العارض، وإذا امسك أنفه بيده لا يشم شيئاً من الروائح حتى يفتح أنفه. فاعتقد من اجل ذلك إن جميع ماله من الادراكات و الأفعال قد تكون لها عوائق تعوقها، فإذا أزيلت العوائق عادت الأفعال. فلما نظر إلى جميع أعضاء الظاهرة ولم ير فيها آفة ظاهرة - وكان يرى مع ذلك العطلة قد اشتملها ولم يختص بها عضو دون عضو - وقع في خاطرة أن الآفة التي نزلت بها، إنما هي العضو غائب عن العيان مستكن في باطن الجسد، وان ذلك العضو لا يغني عنه في فعله شيء من هذه الأعضاء الظاهرة. فلما نزلت به الآفة عمت المضرة، وشملت العطلة، وطمع لو أنه عثر على ذلك العضو وأزال عنه ما يزال به لاستقامت أحواله وفاض على سائر البدن نفعه، وعادت الأفعال إلى ما كانت عليه. وكان قد شاهد قبل ذلك في الأشباح الميتة من الوحوش وسواها أن جميع أعضائها مصمتة لا تجويف فيها إلا القحف، والصدر، والبطن. فوقع في نفسه أن العضو الذي بتلك الصفة لن يعدو أحد هذه المواضع الثلاثة، وكان يغلب على ظنه غلبة قوية أنه إنما هو في الموضع المتوسط من هذه المواضع الثلاثة، إذ استقر في نفسه أن جميع الأعضاء محتاجة إليه، وأن الواجب بحسب ذلك أن يكون مسكنه في الوسط. وكان أيضاً إذا رجع إلى ذاته، شعر بمثل هذا العضو في صدره لانه كان يعترض سائراً اعضائه كاليد، والرجل، والأذن، والانف، والعين، ويقدر مفارقتها، فيتاى له أنه كان يستغني عنها، وكان يقدر في رأسه مثل ذلك ويظن أنه يستغني عنه، فإذا فكر في الشيء الذي يجده في صدره، لم يتأت له الاستغناء عنه طرفة عين. وكذلك كان عند محاربته للوحوش أكثر ما كان يتقي من صياصيهم على صدره، لشعوره بالشيء الذي فيه. فلما جزم الحكم بان العضو الذي نزلت به الآفة إنما هو في صدورها، اجمع على البحث عليه والتنقير عنه، لعله يظفر به، ويرى آفته فيزيلها ثم انه خاف أنه يكون نفس فعله هذا أعظم من الآفة التي نزلت بها أولاً فيكون سعيه عليها. ثم أنه تفكر: هل رأى من الوحوش وسواها، من ضار في مثل تلك الحال، ثم عاد إلى مثل حاله الأول؟ فلم يجد شيئاً! فحصل له من ذلك، اليأس من رجوعها إلى حالها الأولى إن هو تركها، وبقي له بعض الرجاء في رجوعها إلى تلك الحال إن هو وجد ذلك العضو وأزال الآفة عنه. فعزم على شق صدرها وتفتيش ما فيه، فاتخذ من كسور الأحجار الصلدة وشقوق القصب اليابسة، أشباه السكاكين، وشق بها بين أضلاعها حتى قطع اللحم الذي بين الأضلاع، وأفضى إلى الحجاب المستبطن للأضلاع فراه قوياً، فقوي ظنه مثل ذلك الحجاب لا يكون إلا لمثل ذلك العضو وطمع بأنه إذا تجاوزه ألفى مطلوبه فحاول شقه، فصعب عليه، لعدم الآلات، ولأنها لم تكن إلا من الحجارة والقصب، فاستجدها ثانية واستحدها وتلطف في خرق الحجاب حتى انخرق له، فأفضى إلى الرئة فظن أنها مطلوبه، فما زال يقلبها ويطلب موضع الآفة بها. وكان أولاً نصفها الذي هو في الجانب الواحد. فلما راها مائلة إلى جهة واحدة، وكان قد اعتقد أن ذلك العضو لا يكون إلا في الوسط في عرض البدن، كما في الوسط في طوله. فمازال يفتش في وسط الصدر حتى ألفى القلب وهو مجلل بغشاء في غاية القوة مربوط بعلائق في غاية الوثاقة، والرثة مطيفة به من الجهة التي بدأ بالشق منها، فقال في نفسه: إن كان لهذا العضو من الجهة الأخرى مثل ما له من الجهة فهو في حقيقة الوسط، ولا محالة أنه مطلوبي. لا سيما مع ما أرى له حسن الوضع، وجمال الشكل، وقلة التشتت، وقوة اللحم، وأنه محجوب بمثل هذا الحجاب الذي لم أر مثله لشيء من الأعضاء. فبحث عن الجانب الآخر من الصدر، فوجد فيه الحجاب المستبطن للأضلاع، ووجد الرئة كمثل ما وجد من هذه الجهة. فحكم بان ذلك العضو هو مطلوبه، فحاول هتك حجابه، وشق شغافه، فبكد واستكراه ما، قدر على ذلك، بعد استفراغ مجهوده. وجرد القلب فراه مصمتاً من كل جهة، فنظر هل يرى فيه آفة ظاهرة؟ فلم ير فيه شيئاً! فشد على يده، فتبين له أن فيه تجويفاً، فقال: لعل مطلوبي الأقصى إنما هو في داخل هذا العضو، وأنا حتى الآن لم أصل إليه. فشق عليه، فألقى فيه تجويفين اثنين احدهما من الجهة اليمنى والآخر من الجهة اليسرى، والذي من الجهة اليمنى مملوء بعقد منعقد، والذي من الجهة اليسرى خال لا شيء به. فقال: لن يعدو مطلوبي أن يكون مسكنه أحد هذين البيتين. ثم قال: أما هذا البيت الأيمن، فلا أرى فيه إلا هذا الدم المنعقد. ولا شك أنه لم ينعقد حتى صار الجسد كله إلى هذا الحال - إذ كان قد شاهد الدماء متى سالت وخرجت انعقدت وجمدت ولم يكن هذا إلا دماً كسائر الدماء - وأنا أرى أن هذا الدم موجود في سائر الأعضاء لا يختص به عضو دون أخر، وأنا ليس مطلوبي شيئاً بهذه الصفة إنما مطلوبي الشيء الذي يختص به هذا الموضع الذي أجدني لا أستغني عنه طرفة العين، واليه كان انبعاثي من أول. واما هذا الدم فكم مرة جرحتني الوحوش في المحاربة فسال مني كثير منه فما ضرني ذلك ولا افقدني شيئاً من أفعالي، فهذا بيت ليس فيه مطلوبي. وأما هذا البيت الأيسر فأراه خالياً لاشيء فيه، وما أرى ذلك لباطل، فاني رأيت كل عضو من الأعضاء إنما لفعل يختص به، فكيف يكون هذا البيت على ما شاهدت من شرفه باطلاً؟ ما أرى إلا أن مطلوبي كان فيه! فارتحل عنه وأخلاه. وعند ذلك، طرأ على هذا الجسد من العطلة ما طرأ، ففقد الإدراك وعدم الحراك. فلما رأى أن الساكن في ذلك البيت قد ارتحل قبل انهدامه وتركه وهو بحاله، تحقق أنه أحرى أن لا يعود إليه بعد أن حدث فيه من الخراب والتخريق ما حدث. فصار عنده الجسد كله خسيساً لا قدر له بالإضافة إلى ذلك الشيء الذي اعتقد في نفسه أنه يسكنه مدة ويرحل عنه بعد ذلك. فاقتصر على الفكرة في ذلك الشيء ما هو؟ وكيف هو؟ وما الذي ربطه بهذا الجسد؟ والى اين صار؟ ومن أي الأبواب خرج عند خروجه من الجسد؟ وما السبب الذي أزعجه إن كان خرج كارهاً؟ وما السبب الذي كره إليه الجسد، حتى فارقه إن كان خرج مختاراً؟ وتشتت فكره في ذلك كله، وسلا عن الجسد وطرحه، وعلم أن أمه التي عطفت عليه وأرضعته، إنما كانت ذلك الشيء المرتحل، وعنه كانت تصدر تلك الأفعال كلها، لا هذا الجسد العاطل وأن هذا الجسد بجملته، إنما هو كالآلة وبمنزلة العصي التي اتخذها هو لقتال الوحوش. فانتقلت علاقته عن الجسد إلى صاحب الجسد ومحركه، ولم يبق له شوق إلا إليه. وفي خلال ذلك نتن ذلك الجسد، وقامت منه روائح كريهة، فزادت نفرته عنه، وود أن لا يراه ثم انه سنح لنظره غرابان يقتتلان حتى صرع أحدهما الآخر ميتاً. ثم جعل الحي يبحث في الأرض حتى حفر حفرة فوارى فيها ذلك الميت بالتراب فقال في نفسه: ما أحسن ما صنع هذا الغراب في مواراة جيفة صاحبه وان كان قد أساء في قتله اياه! وأنا كنت أحق بالاهتداء إلى هذا الفعل بآمي! فحفر حفرة وألقى فيها جسد أمه، وحثا عليها التراب. وبقي يتفكر في ذلك الشيء المصرف للجسد لا يدري ما هو! غير أنه كان ينظر إلى أشخاص الظباء كلها، فيراها على شكل أمه، وعلى صورتها فكان يغلب على ظنه، أن كل واحد منها إنما يحركه ويصرفه شيء هو مثل الشيء الذي كان يحرك أمه ويصرفها، فكان يألف الظباء ويحن إليها لمكان ذلك الشبه. وبقي على ذلك برهة من الزمن، يتصفح أنواع الحيوان والنبات ويطوف بساحل تلك الجزيرة، ويتطلب هل يرى أو يجد لنفسه شبيهاً حسبما يرى لكل واحد من أشخاص الحيوان والنبات أشباهاً كثيرة، فلا يجد شيئاً من ذلك. وكان يرى البحر قد أحدق بالجزيرة من كل جهة، فيعتقد أنه ليس في الوجود أرض سوى جزيرته تلك. واتفق في بعض الاحيان أن انقدحت نار في أجمة قلخ على سبيل المحاكة. فلما بصر بها رأى منظراً هاله، وخلقاً لم يعهده قبل، فوقف يتعجب منها ملياً، ومازال يدنو منها شيئاً فشيئاً، فرأى ما للنار من الضوء الثاقب والفعل الغالب حتى لا تعلق بشيء إلا أتت عليه وأحالته إلى نفسها، فحمله، العجب بها، وبما ركب الله تعالى في طباعه من الجراءة و القوة، على أن يده إليها، وأراد أن يأخذ منها شيئاً فلما باشرها أحرقت يده فلم يستطع القبض عليها فاهتدى إلى أن يأخذ قبساً لم تستول النار على جميعه، فأخذ بطرفه السليم والنار في طرفه الآخر، فتاتي له ذلك وحمله إلى موضعه الذي كان يأوي إليه - وكان قد خلا في جحر استحسنه للسكنى قبل ذلك. ثم مازال يمد تلك النار بالحشيش والحطب الجزل، ويتعهدهاً ليلاً ونهاراً استحساناً منه وتعجباً منها. وكان يزيد انسه بها ليلاً، لأنها كانت تقوم له مقام الشمس في الضياء و الدفء، فعظم بها ولوعه، واعتقد أنها أفضل الأشياء التي لديه: وكان دائماً يراها تتحرك إلى جهة فوق وتطلب العلو، فغلب على ظنه أنها من جملة الجواهر السماوية التي كان يشاهدها. وكان يختبر قوتها في جميع الأشياء بأن يلقيها فيها، فيراها مستولية عليه أما بسرعة واما ببطء بحسب قوة استعداد الجسم الذي كان يلقيه للاحتراق أو ضعفه. وكان من جملة ما القى فيها على سبيل الاختبار لقوتها، شيء من أصناف الحيوانات البحرية - كان قد ألقاه البحر إلى ساحله - فلما أنضجت ذلك الحيوان وسطع قتاره تحركت شهوته إليه، فأكل منه شيئاً فاستطابه، فاعتاد بذلك أكل اللحم، فصرف الحيلة في صيد البر والبحر، حتى مهر في ذلك. وزادت محبته للنار، إذ تأتي له بها من وجوه الاغتذاء الطيب شيء لم يتأت له قبل ذلك. فلما اشتد شغفه بها لما رأى من أحسن آثارها وقوة اقتدارها، وقع في نفسه أن الشيء الذي ارتحل من قلب أمه الظبية التي أنشأته، كان من جوهر هذا الوجود أو من شيء يجانسه، وأكد ذلك في ظنه، ما كان يراه من حرارة الحيوان طول مدة حياته، وبرودته من بعد موته، وكل هذا دائم لا يختل، وما كان يجده في نفسه من شدة الحرارة عند صدره، بازاء الموضع الذي كان قد شق عليه من الظبية، فوقع في نفسه أنه لو أخذ حيواناً حياً وشق قلبه ونظر إلى ذلك التجويف الذي صادفه خالياً عندما شق عليه في أمه الظبية، لرأه في الحيوان الحي وهو مملوء بذلك الشيء الساكن فيه وتحقق هل هو من جوهر النار؟ وهل فيه شيء من الضوء والحرارة، آم لا؟ فعمد إلى بعد الوحوش واستوثق منه كتافاً وشقه على الصفة التي شق بها الظبية حتى وصل القلب. فقصد أولاً إلى الجهة اليسرى منه وشقها، فرأى ذلك الفراغ مملوءاً بهواء بخاري، يشبه الضباب الابيض، فأدخل إصبعه فيه، فوجده من الحرارة في حد كاد يحرقه، ومات ذلك الحيوان على الفور. فصح عنده أن ذلك البخار الحار هو الذي كان يحرك هذا الحيوان، وأن في كل شخص من أشخاص الحيوانات مثل ذلك، ومتى انفصل عن الحيوان مات. ثم تحركت في نفسه الشهوة للبحث عن سائر أعضاء الحيوان وترتيبها وأوضاعها وكميتها وكيفية ارتباط بعضها ببعض، وكيف تستمد من هذا البخار الحار حتى تستمر لها الحياة به، وكيف بقاء هذا البخار المدة التي يبقى، ومن أين يستمد، وكيف لا تنفذ حرارته؟ فتتبع ذلك كله بتشريح الحيوانات الأحياء و الاموات، ولم يزل ينعم النظر فيها ويجيد الفكرة، حتى بلغ في ذلك كله مبلغ كبار الطبيعيين، فتبين له أن كل شخص من أشخاص الحيوان، وان كان كثيراً بأعضائه وتفنن حواسه وحركاته فانه واحد بذلك الروح الذي مبدؤه من قرار واحد، وانقسامه وانقسامه في سائر الأعضاء منبعث منه. وأن جميع الأعضاء إنما هي خادمة له، أو مؤدية عنه، وأن منزلة ذلك الروح في تصريف الجسد، كمنزلة من يحارب الأعداء بالسلاح التام، ويصيد جميع صيد البر والبحر، فيمد لكل جنس آلة يصيده بها والتي يحارب بها تنقسم: إلى ما يدفع به نكيلة غيره، والى ما ينكي بها غيره. وكذلك آلات الصيد تنقسم: إلى ما يصلح لحيوان البحر، والى ما يصلح لحيوان البر، وكذلك الأشياء التي يشرح بها تنقسم: إلى ما يصلح للشق، والى ما يصلح للكسر، والى ما يصلح للثقب، والبدن الواحد، وهو يصرف ذلك أنحاء من التصريف بحسب ما تصلح له كل آلة، وبحسب الغايات التي تلتمس بذلك التصرف. كذلك؛ ذلك الروح الحيواني واحد، وإذا عمل بالة العين كان فعله أبصاراً، وإذا عمل بآلة الآذن كان فعله سمعاً، وإذا عمل بآلة الآنف كان فعله شماً، وإذا عمل بآلة اللسان كان فعله ذوقاً، وإذا عمل بالجلد واللحم كان فعله لمساً، وإذا عمل بالعضد كان فعله حركه، وإذا عمل بالكبد كان فعله غذاء واغتذاء. ولكل واحد من هذه، أعضاء تخدمه. ولا يتم لشيء من هذه فعل إلا بما يصل إليها من ذلك الروح، على الطريق التي تسمى عصباً. ومتى انقطعت تلك الطرق أو انسدت، تعطل فعل ذلك العضو. وهذه الأعصاب إنما تستمد الروح من بطون الدماغ يستمد الروح من القلب، والدماغ فيه أرواح كثير، لانه موضع تتوزع فيه أقسام كثيرة: فآي عضو عدم هذا الروح بسبب من الأسباب تعطل فعله وصار بمنزلة الآلة المطرحة، التي يصرفها الفاعل ولا ينتفع بها. فان خرج هذا الروح بجملته عن الجسد، أو فني، أو تحلل بوجه من الوجوه، تعطل الجسد كله، وصار إلى حالة الموت، فانتهى به إلى هذا من منشئه، وذلك أحد وعشرون عاماً. وفي خلال هذه المدة المذكورة تفنن في وجوه حيله، واكتسى بجلود الحيوانات التي كان يشرحها، واحتذى بها، واتخذ الخيوط من الأشعار ولحا قصب الخطمية والخباري والقنب، وكل نبات ذي خيط. وكان أصل اهتدائه إلى ذلك، أنه أخذ من الحلفاء وعمل خطاطيف من الشوك القوي والقصب المحدد على الحجارة. واهتدى إلى البناء بما رأى من فعل الخطاطيف فاتخذ مخزناً وبيتاً لفضلة غذائه، وحصن عليه بباب من القصب المربوط بعضه إلى بعض، لئلا يصل إليه شيء من الحيوانات عند مغيبه عن تلك الجهة في بعض شؤونه. واستألف جوانح الطير ليستعين بها في الصيد، واتخذ الدواجن ببيضها وفراخها، واتخذ من الصياصي البقر الوحشية شبه الاسنة، وركبها في القصب القوي، وفي عصي الزان وغيرها، واستعان في ذلك بالنار وبحروف الحجارة، حتى صارت شبه الرماح، واتخذ ترسه من جلود مضاعة: كل ذلك لما رأى من عدمه السلاح الطبيعي. ولما رأى أن يده تفي له بكل ما فاته من ذلك، وكان لا يقاومه شيء من الحيوانات على اختلاف أنواعها، إلا أنها كانت عنه فتعجزه هرباً، فكر في وجه الحيلة في ذلك، فلم ير شيئاً أنجع له من أن يتالف بعض الحيوانات الشديدة العدو، ويحسن إليها بأعداد الغذاء الذي يصلح لها، حتى يتأتى له الركوب عليها ومطاردة سائر الأصناف بها. وكان بتلك الجزيرة خيل البرية وحمر وحشية، فاتخذ منها ما يصلح له، وراضها حتى كمل بها غرضه، وعمل عليها من الشرك والجلود أمثال الشكائم والسروج فتاتي له بذلك ما امله من طرد الحيوانات التي صعبت عليه الحيلة في أخذها. وانما تفنن في هذه الأمور كلها ف وقت اشتغاله التشريح، وشهوته في وقوفه على خصائص أعضاء الحيوان، وبماذا تختلف، وذلك في المدة التي حددنا منتهاها بأحد وعشرين عاماً. ثم انه بعد ذلك أخذ في مآخذ أخر من النظر، فتصفح جميع الأجسام التي في عالم الكون والفساد: من الحيوانات على اختلاف أنواعها، والنبات والمعادن وأصناف الحجارة والتراب والماء والبخار والثلج والبرد، والدخان واللهيب والجمر، فرأى لها أصوافاً كثيرة وأفعالاً مختلفة، وحركات متفقة ومضادة، وأنعم النظر في ذلك والتثبت، فرأى أنها تتفق ببعض الصفات وتختلف ببعض، وأنها من الجهة التي تتفق بها واحدة، ومن الجهة التي تختلف فيها متغايرة ومتكثرة فكان تارة ينظر خصائص الأشياء وما يتفرد به بعضها عن بعض، فتكثر عنده كثرة تخرج عن الحصر، وينتشر له الوجود انتشار لا يضبط. كل عضو منها فيرى أنه يحتمل القسمة إلى أجزاء كثيرة جداً، فيحكم على ذاته بالكثرة، وكذلك على ذات كل شيء. ثم كان يرجع إلى نظر آخر من طريق ثان، فيرى أن أعضاءه، وان كانت كثيرة فهي متصلة كلها بعضها ببعض، لا انفصال بينها بوجه، فهي في الحكم الواحد، وأنها لا تختلف إلا بحسب اختلاف أفعالها، أن ذلك الاختلاف إنما هو بسبب ما يصل إليها من قوة الروح الحيواني، الذي انتهى إليه نظره أولاً، وأن ذلك الروح واحد ذاته، وهو حقيقة الذات، وسائر الأعضاء كلها كالآلات، فكانت تتحد عنده ذاته بهذا الطريق. ثم أنه كان ينتقل إلى جميع أنواع الحيوانات، فيرى كل شخص منها واحداً بهذا النوع من النظر. ثم كان ينظر إلى نوع منها: كالظباء والخيل وأصناف الطير صنفاً صنفاً، فكان يرى أشخاص كل نوع يشبه بعضه بعضاً في الأعضاء الظاهرة والباطنة الادراكات والحركات والمنازع، ولا يرى بينها اختلافاً إلا في أشياء يسيرة بالإضافة إلى ما اتفقت فيه. وكان يحكم بان الروح الذي لجميع ذلك النوع شيء واحد، وأنه لم يختلف إلا أنه انقسم على قلوب كثيرة، وأنه لو أمكن أن يجمع جميع الذي افترق في تلك القلوب منه ويجعل في وعاء واحد، لكان كله شيئاً واحداً، بمنزلة ماء واحد، أو شراب واحد، يفرق على أوان كثيرة، ثم يجمع بعد ذلك. فهو في حالتي تفريقه وجمعه شيء واحد، إنما الغرض له التكثر بوجه ما، فكان يرى النوع بهذا النظر واحداً، ويجعل كثرة أشخاصه بمنزلة كثيرة أعضاء الشخص الواحد، التي لم تكن كثرة في الحقيقة. ثم كان يحضر أنواع الحيوانات كلها في نفسه ويتأملها فيراها تتفق في أنها تحس، وتغتذي، وتتحرك بالإرادة إلى أي جهة شاءت، وكان قد علم أن هذه الأفعال هي أخص أفعال الروح الحيواني، وأن سائر الأشياء التي تختلف بها بعد هذا الاتفاق، ليست شديدة الاختصاص بالروح الحيواني. فظهر له بهذا التأمل، أن الروح الحيواني الذي لجميع جنس الحيوان واحد بالحقيقة، وان كان فيه اختلاف يسير، اختص به نوع دون نوع: بمنزلة ماء واحد مقسوم على أوان كثيرة، بعضه أبرد من بعض. وهو في أصله واحد وكل ما كان في طبقة واحدة من البرودة، فهو بمنزلة اختصاص ذلك الروح الحيواني بنوع واحد، وان عرض له التكثر بوجه ما. فكان يرى جنس الحيوان كله واحداً بهذا النوع من النظر. ثم كان يرجع إلى أنواع النبات على اختلافها. فيرى كل نوع منها تشبه أشخاصه بعضها بعضاً في الأغصان، والورق، والزهر والثمر، والأفعال فكان يقيسها بالحيوان، ويعلم أن لها شيئاً واحداً فيه: هو لها بمنزلة الروح الحيواني وأنها بذلك الشيء واحد. وكذلك كان ينظر إلى جنس النبات كله، فيحكم باتحاده بحسب ما يراه من اتفاق فعله في أنه يتغذى وينمو. ثم كان يجمع في نفسه جنس الحيوان وجنس النبات، فيراهما جميعاً متفقين في الاغتذاء والنمو، ألا أن الحيوان يزيد على النبات، بفضل الحس والادراك والتحرك؛ وربما ظهر في النبات شيء شبيه به، مثل تحول وجوه الزهر إلى جهة الشمس، وتحرك عروقه إلى الغذاء، بسبب شيء واحد مشترك بينهما، هو في أحدهما أتمم وأكمل، وفي الآخر قد عاقه عائق ما، وأن ذلك بمنزلة ماء واحد قسم بقسمين، أحدهما جامد والآخر سيال، فيتحد عنده النبات والحيوان. ثم ينظر إلى الأجسام التي لا تحس ولا تغتذي ولا تنمو، من الحجارة، والتراب، والماء، والهواء، واللهب، فيرى أنها أجسام مقدر لها الطول وعرض وعمق وأنها لاتختلف، إلا أن بعضها ذو لون وبعضها لا لون له وبعضها حار والآخر بارد، ونحو ذلك من الاختلافات وكان يرى أن الحار منها يصير بارداً، والبارد يصير حار وكان يرى الماء يصير بخاراً والبخار ماء، والأشياء المحترقة تصير جمراً، ورماداً، ولهيباً، ودخاناً، والدخان إذا وافق في صعوده قبة حجر انعقد فيه وصار بمنزلة سائر الأشياء الأرضية، فيظهر له بهذا التأمل، أن جميعها شيء واحد في الحقيقة، وان لحقتها الكثرة بوجه ما، فذلك مثل ما لحقت الكثرة للحيوان والنبات. ثم ينظر إلى الشيء الذي اتحد به عند النبات والحيوان، فيرى أنه جسم ما مثل هذه الأجسام: له طول وعرض وعمق، وهو إما حار واما بارد، كواحد من هذه الأجسام التي لا تحس ولا تتغذى، وانما خالفها بأفعاله التي تظهر عنه بالآلات الحيوانية والنباتية لا غير، ولعل تلك الأفعال ليست ذاتية، وانما تسري إليه من شيء آخر ولو سرت إلى هذه الأجسام الآخر، لكانت مثله فكان ينظر إليه بذاته مجرداً عن هذه الأفعال، التي تظهر ببادئ الرأي، أنها صادرة عنه، فكان يرى أنه ليس إلا جسماً من هذه الأجسام، فيظهر له بهذا التأمل، أن الأجسام كلها شيء واحد: حيها وجمادها، متحركها وساكنها، إلا أنه يظهر أن لبعضها أفعالاً بالات، ولا يدري هل تلك الأفعال ذاتية لها، أو سارية أليها من غيرها. وكان في هذه الحال لا يرى شيئاً غير الأجسام فكان بهذا الطريق يرى الوجود كله شيئاً واحداً، وبالنظر الأول كثرة لا تنحصر ولا تتناهى. وبقي بحكم هذه الحالة مدة. ثم انه تأمل جميع الأجسام حيها وجامدها. وهي التي هي عنده تارةً شيء واحد وتارةً كثيرة كثرة لا نهاية لها، فرأى إن كل واحد منها، لا يخلو من أحد أمرين: إما أن يتحرك إلى جهة العلو مثل الدخان واللهيب والهواء، إذا حصل تحت الماء واما أن يتحرك إلى الجهة المضادة لتلك الجهة، وهي جهة السفل، مثل الماء، وأجزاء الحيوان و النبات، وأن كل جسم من هذه الأجسام لن يعرى عن إحدى هاتين الحركتين وأنه لا يسكن إلا إذا منعه مانع يعوقه عن طريقه، مثل الحجر النازل يصادف وجه الأرض صلباً، فلا يمكن أن يخرقه، ولو أمكنه ذلك لما انثنى عن حركته فيما يظهر، ولذلك إذا رفعته، وجدته يتحامل عليك بميله إلى جهة السفل، طالباً للنزول. وكذلك الدخان في صعوده، لا ينثني إلا أن يصادف قبة صلبة تحبسه، فحينئذً ينعطف يميناً وشمالاً ثم إذا تخلص من تلك القبة، خرق الهواء صاعداً لأن الهواء لا يمكنه أن يحبسه. وكان يرى إن الهواء إذا ملئ به زق جلد، وربط ثم غوص تحت الماء طلب الصعود وتحامل على من يمسكه تحت الماء، ولا يزال يفعل ذلك حتى يوافي موضع الهواء، وذلك بخروجه من تحت الماء فحينئذً يسكن ويزول عنه ذلك التحامل والميل إلى جهة العلو الذي كان يوجد منه قبل ذلك. ونظر هل يجد جسماً يعرى عن إحدى هاتين الحركتين أو الميل إلى إحداهما في الوقت ما؟ فلم يجد ذلك في الأجسام التي لديه، وانما طلب ذلك، لانه طمع أن يجده، فيرى طبيعة الجسم من حيث هو جسم، دون أن تقترن به وصف من الأوصاف، التي هي منشأ التكثر. فلما أعياه ذلك ونظر إلى الأجسام التي هي أقل الأجسام حملاً للأوصاف فلم يرها تعرى عن أحد هذين الوصفين بوجه، وهما اللذان يعبر عنهما بالثقل والخفة فنظر إلى الثقل والخفة، هل هما للجسم من حيث هو جسم؟ أو هما لمعنى زائد على الجسمية؟ فظهر له أنهما لمعنى زائد على الجسمية لانهما لو كانا للجسم من حيث هو جسم، لما وجد إلا وهما له. ونحن نجد الثقيل لا توجد فيه الخفة، والخفيف لا يوجد فيه الثقل، وهما لا محالة جسمان ولكل واحد منهما معنى منفرد به عن الأخر زائد على جسميته. وذلك المعنى، الذي به غاير كل واحد منهما الآخر، ولولا ذلك لكانا شيئاً واحداً من جميع الوجوه. فتبين له أن حقيقة كل واحد من الثقيل والخفيف، مركبة من معنيين: أحدهما ما يقع فيه الاشتراك منهما جميعاً، وهو معنى الجسمية؛ والآخر ما تنفرد به حقيقة كل واحد منهما على الاخر، وهما أما الثقل في احدهما واما الخفة في الاخر، المقترنان بمعنى الجسمية، أي المعنى الذي يحرك أحدهما الأخر علواً والأخر سفلاً. وكذلك نظر إلى سائر الأجسام من الجمادات والأحياء، فرأى أن حقيقة وجود كل واحد منهما مركبة من معنى الجسمية، ومن شيء أخر زائد على الجسمية: أما واحد، واما أكثر من واحد؛ فلاحت له صور الأجسام على اختلافها وهو أول ما لاح له من العالم الروحاني، اذ هي صور لا تدرك بالحس، وانما تدرك بضرب ما من النظر العقلي. ولاح له في جملة ما لاح من ذلك، أن الروح الحيواني الذي مسكنه القلب - وهو الذي تقدم شرحه أولاً - لابد له أيضاً من معنى زائد على جسميته يصلح بذلك المعنى لأن يعمل هذه الأعمال الغريبة، التي تختص به من ضروب الاحساسات، وفنون الادراكات وأصناف الحركات، وذلك المعنى هو صورته وفضله الذي انفصل به عن سائر الأجسام، وهو الذي يعبر عنه النظار بالنفس الحيوانية. وكذلك ايضاً للشيء الذي يقوم للنبات مقام الحار الغريزي للحيوان، شيء يخصه هو صورته، وهو الذي يعبر عنه النظار بالنفس النباتية. وكذلك لجميع الأجسام الجمادات: وهي ما عدا الحيوان والنبات مما في عالم الكون والفساد شيء يخصها به، يفعل كل واحد منها فعله الذي يختص به مثل صنوف الحركات وضروب الكيفيات المحسوسة عنها، وذلك الشيء هو صورة كل واحد منها، وهو الذي يعبر النظار عنه بالطبيعة. فلما وقف بهذا النظر على ان حقيقة الروح الحيواني، الذي كان تشوقه اليه ابداً، مركبة من معنى الجسمية، ومن معنى أخر زائد على الجسمية، وان معنى الجسمية مشترك، ولسائر الأجسام، والمعنى الأخر المقترن به هو وحده، هان عنده معنى الجسمية فاطرحه، وتعلق فكره بالمعنى الثاني، وهو الذي يعبر عنه النفس؛ فتشوق إلى التحقق به فالتزم الفكرة فيه، وجعل مبدأ النظر في ذلك تصفح الأجسام كلها، لا من جهة ما هي أجسام، بل من وجهة ما هي ذوات صور تلزم عنها خواص، ينفصل بها بعضها ببعض. فتتبع ذلك وحصره في نفسه، فرأى جملة من الأجسام، تشترك في صورة ما يصدر عنها فعل ما، أو أفعال ما، ورأى فريقاً من تلك الجملة، مع أنه يشارك الجملة بتلك الصورة، يزيد عليها بصورة أخرى، يصدر عنها ما، ورأى طائفة من ذلك الفريق، مع أنها تشارك الفريق في الصورة الأولى والثانية، تزيد عليه بصوره ثالثة، تصدر عنها أفعال ما خاصة بها. مثال ذلك: إن الأجسام الأرضية، مثل التراب والحجارة والمعادن والنبات والحيوان، وسائر الأجسام الثقيلة، وهي جملة واحدة تشترك في صورة واحدة تصدر عنها الحركة إلى الأسفل، ما لم يعقها عائق عن النزول: ومتى تحركت إلى جهة العلو بالقسر ثم تركت، تحركت بصورتها إلى الأسفل. وفريق من هذه الجملة، وهو النبات والحيوان، مع مشاركة الجملة المتقدمة في تلك الصورة، يزيد عليها صورة أخرى، يصدر عنها التغذي والنمو. والتغذي: هو أن يخلف المتغذي، بدل ما تحلل منه، بان يحيل إلى ما التشبه بجوهره مادة قريبة منه، يجتذبها إلى نفسه. والنمو: هو الحركة في الأقطار الثلاثة، على نسبة محفوظة في الطول والعرض والعمق. فهذان الفعلان عامان للنبات والحيوان، وهما لا محالة صادران عن صورة مشتركة لهما، وهي المعبر عنها بالنفس النباتية. وطائفة من هذا الفريق، وهو الحيوان خاصة، مع مشاركته الفريق المتقدم في الصورة الأولى والثانية، تزيد عليه بصورة ثالثة، يصدر عنها الحس والتنقل من حين إلى أخر. ورأى أيضاً كل نوع من أنواع الحيوان، له خاصية ينحاز بها عن سائر الأنواع، وينفصل بها متميزاً عنها. فعلم إن ذلك صادر عن صورة له تخصه هي زائدة عن معنى الصورة المشتركة له ولسائر الحيوان، وكذلك لكل واحد من أنواع النبات مثل ذلك. فتبين له إن الأجسام المحسوسة التي في عالم الكون والفساد، بعضها تلتئم حقيقته من معان كثيرة، زائدة على معنى الجسمية، وبعضها من معان اقل؛ وعلم إن معرفة الأقل أسهل من معرفة الأكثر؛ فطلب أولاً الوقوف على الحقيقة لشيء الذي تلتئم حقيقته من اقل الأشياء، ورأى إن الحيوان والنبات، لا تلتئم حقائقها إلا من معان كثيرة، لتفنن أفعالها؛ فأخر التفكير في صورهما. وكذلك رأى إن أجزاء الأرض بعضها ابسط من بعض، فقصد منها إلى ابسط ما قدر عليه وكذلك رأى إن الماء شيء قليل التركيب، لقلة ما يصدر عن صورته من أفعال، وكذلك راى النار والهواء. وكان قد سبق إلى ظنه أولاً، أن هذه الأربعة يستحيل بعضها إلى بعض، وان لها شيئاً واحداً تشترك فيه، وهو معنى الجسمية، وان ذلك الشيء ينبغي إن يكون خلواً من المعاني التي تميز بها كل واحد من هذه الأربعة عن الأخر، فلا يمكن أن يتحرك إلى فوق ولا إلى اسفل، ولا إن يكون حاراً ولا يكون بارداً، ولا يكون رطباً، ولا يابساً، لان كل واحد من هذه الاوصاف، لا يعم جميع الأجسام، فليست إذن للجسم بما هو جسم. فإذا أمكن وجود جسم لا صورة فيه زائدة على الجسمية، فليس تكون فيه صفة من هذه الصفات، ولا يمكن إن تكون فيه صفة إلا وهي تعم سائر الأجسام المتصورة، بضروب الصور. فنظر هل يجد وصفاً واحداً يعم جميع الأجسام: حيها وجمادها، فلم يجد شيئاً يعم الأجسام كلها. إلا معنى الامتداد الموجود في جميعها في الأقطار الثلاثة، التي يعبر عنها بالطول، والعرض، والعمق، فعلم هذا المعنى هو للجسم من حيث هو جسم، لكنه لم يتأت له بالحس وجود جسم بهذه الصفة وحدها، حتى لا يكون فيه معنى زائد على الامتداد المذكور ويكون بالجملة خلواً من سائر الصور. ثم تفكر في هذا الامتداد إلى الأقطار الثلاثة، هل هو معنى الجسم بعينه، وليس ثم معنى أخر أو ليس الأمر كذلك، فرأى أن وراء هذا الامتداد معنى أخر، هو الذي يوجد فيه هذا الامتداد، وان الامتداد وحده لا يمكن إن يقوم بنفسه كما إن ذلك الشيء الممتد، لا يمكن أن تقوم دون امتداد. واعتبر ذلك ببعض هذه الأجسام المحسوسة ذوات الصور، كالطين مثلاً، كان له طول وعرض وعمق على قدر ما. ثم إن تلك الكرة بعينها لو أخذت وردت إلى شكل مكعب أو بيض، لتبدل ذلك الطول وذلك العرض وذلك العمق، وصارت على قدر أخر. غير الذي كانت عليه، والطين واحد بعينه لم يتبدل، غير أنه لا بد له من طول وعرض وعمق على أي قدر كان، ولا يمكن إن يعرى عنها؛ غير أنها لتعاقبها عليه، تبين له أنها معنى على حياله؛ ولكونه لا يعرى بالجملة عنها، تبين له أنها من حقيقة. فلاح له بهذا الاعتبار، إن الجسم، بما هو جسم، مركب على الحقيقة من معنين: أحدهما يقوم منه مقام الطين للكرة في هذا المثال. والأخر: يقوم مقام طول الكرة وعرضها وعمقها، أو المكعب، أو أي شكل كان له. وانه لا يفهم الجسم إلا مركباً من هذين المعنين، وان احدهما لا يستغني عن الأخر. ولكن الذي يمكن أن يتبدل ويتعاقب على أوجه كثيرة، وهو معنى الامتداد يشبه الصورة التي لسائر الأجسام ذوات الصور، والذي يثبت على حال واحدة، وهو الذي ينزل منزلة الطين في المثال المتقدم، يشبه معنى الجسمية التي لسائر الأجسام ذوات الصور. وهذا الشيء الذي هو بمنزلة الطين في هذا المثال هو الذي يسميه النظار المادة والهيولى وهي عارية عن الصورة جملة. فلما نظره إلى هذا الحد، وفارق المحسوس بعض مفارقة، وأشرف على تخوم العالم العقلي، استوحش وحن إلى ما ألفه من عالم الحس، فتقهقر قليلاً وترك الجسم على الإطلاق، إذ هو أمر لا يدركه الحس، ولا يقدر على تناوله. فاخذ أبسط الأجسام المحسوسة التي شاهدها، وهي تلك الأربعة التي كان قد وقف نظره عليها. فأول ما نظر إلى الماء فرأى انه إذا خلي وما تقتضيه صورته، ظهر منه برد محسوس، وطلب النزول إلى اسفل فإذا سخن أما بالنار واما بحرارة الشمس، زال عنه البرد أولاً وبقي فيه طلب النزول، فإذا أفرط عليه بالتسخين، زال عنه طلب النزول إلى اسفل. وصار يطلب الصعود إلى فوق. فزال عنه بالجملة الوصفان اللذان كانا أبداً يصدران عن صورته، ولم يعرف من صورته أكثر من صدور هذين الفعلين عنها. فلما زال هذان الفعلان بطل حكم الصورة، فزالت الصورة المائية عن ذلك الجسم عندما ظهرت منه أفعال من شأنها أن تصدر عن صورة أخرى، وحدثت له صورة أخرى، بعد أن لم تكن، وصدر عنه بها أفعال لم يكن من شأنها أن تصدر عنه وهو بصورته الأولى. فعلم بالضرورة أن كل حادث لا بد له من محدث. فارتسم في نفسه بهذا الاعتبار، فاعل للصورة، ارتساماً على العموم دون تفصيل. ثم أنه تتبع الصور التي كان قد عاينها قبل ذلك، صورة صورة، فرأى أنها كلها حادثة، وأنها لا بد لها من فاعل. ثم نظر إلى ذوات، الصور، فلم ير أنها شيء أكثر من استعداد الجسم لان يصدر عنه ذلك الفعل، مثل الماء، فانه إذا افرط عليه التسخين، استعد للحركة إلى فوق وصلح لها. فذلك الاستعداد هو صورته، إذ ليس ها هنا إلا جسم وأشياء تحس عنه، بعد أن لم تكن؛ فصلوح الجسم لبعض الحركات دون بعض، واستعداده بصورته، ولاح له مثل ذلك في جميع الصور، فتبين له أن الأفعال الصادرة عنها، ليست في الحقيقة لها، وانما هي لفاعل يفعل بها الأفعال المنسوبة إليها؛ وهذا المعنى الذي لاح له، هو قول الرسول الله عليه الصلاة والسلام: "كنت سمعه الذي يسمع به وبصره الذي يبصر به" وفي محكم التنزيل: "بسم الله الرحمن الرحيم" فان تقتلوهم ولكن الله قتلهم؛ وما رميت إذا رميت، ولكن الله رمى! صدق الله العظيم. فلما لاح له من أمر هذا الفاعل، ما لاح على الإجمال دون تفصيل، حدث له شوق حثيث إلى معرفته على التفصيل، ولانه لم يكن بعد فارق عالم الحس، جعل يطلب هذا الفاعل على جهة المحسوسات، وهو لا يعلم بعد هل هو واحد أو كثير؟ فتصفح جميع الأجسام التي لديه، وهي التي كانت فكرته أبداً فيها، فرأها كلها تتكون تارة وتفسد أخرى، وما لم يقف على فساد جملته، وقف على الفساد أجزائه مثل الماء والأرض، فانه راى أجزاءهما تفسد بالنار، وكذلك الهواء رآه يفسد بشدة البرد، حتى بتكون منه الثلج فيسيل ماء. وكذلك سائر الأجسام التي كانت لديه، ولم ير منها شيئاً بريئاً عن الحدوث والافتقار إلى الفاعل المختار، فاطرحها كلها وانتقلت فكرته إلى الأجسام السماوية. وانتهى إلى هذا النظر على رأس أربعة أسابيع من منشئه، وذلك ثمانية وعشرون عاماً. فعلم إن السماء وما فيها من كواكب الأجسام، لأنها ممتدة في الأقطار الثلاثة: الطول، والعرض، والعمق؛ لا ينفك شيء منها عن هذه الصفة، وكل ما لا ينفك عن هذه الصفة، فهو جسم؛ فهي إذن كلها أجسام. ثم تفكر هل هي ممتدة إلى ما لا نهاية، وذاهبة أبداً في الطول والعرض والعمق إلى ما لا نهاية، أو هي متناهية محدودة بحدود تنقطع عندها، ولا يمكن أن يكون وراءها شيء من الامتداد؟ فتحير بعد ذلك بعض الحيرة. ثم انه بقوة فطرته، وذكاء خاطره، راى أن جسماً لا نهاية له أمر باطل، وشيء لا يمكن، ومعنى لا يعقل، وتقوى هذا الحكم عنده بحجج كثيرة، سنحت له بينه وبين نفسه وذلك أنه قال: أما الجسم السماوي فهو متناه من الجهة التي تليني والناحية التي وقع عليها حسي، فهذا لا شك فيه لأنني أدركه ببصر، وأما الجهة التي تقابل هذه الجهة، وهي التي يداخلني فيها الشك، فاني أيضاً أعلم من المحال أن تمتد إلى غير نهاية، لأني إن تخيلت أن خطين اثنين، يبتدئان من هذه الجهة المتناهية، ويمران في سمك الجسم إلى غير نهاية حسب امتداد الجسم، ثم تخيلت أن أحد هذين الخطين، قطع منه جزء كبير من ناحية طرفه المتناهي، ثم أخذ ما بقي منه شيء واطبق الخط المقطوع منه على الخط الذي لم يقطع منه شيء، وذهب الذهن كذلك معهما إلى الجهة التي يقال إنها غير متناهية، فأما أن نجد خطين أبداً يمتدان إلى غير نهاية ولا ينقص أحدهما عن الأخر، فيكون الذي قطع منه جزء مساوياً للذي لم يقطع منه شيء وهو محال، كما أن الكل مثل الجزء المحال؛ واما أن لا يمتد الناقص معه ابداً، بل ينقطع دون مذهبه ويقف عن الامتدادمعه، فيكون متناهياً، فإذا رد عليه القدر الذي قطع منه أولاً، وقد كان متناهياً، صار كله أيضاً متناهياً، وحينئذ لا يقصر عن الخط الأخر الذي يقطع منه شيء، ولا يفضل عليه فيكون إذن مثله وهو متناه، فذلك أيضاً متناه، فالجسم الذي تفرض فيه هذه الخطوط متناه، وكل جسم يمكن أن تفرض فيه هذه الخطوط، فكل جسم متناه. فإذا فرضنا أن جسماً غير متناه، فقد فرضنا باطلاً ومحالاً. فلما صح عنده بفطرته الفائقة التي لمثل هذه الجهة، أن جسم السماء متناه، أراد أن يعرف على أي شكل هو، وكيفية انقطاعه بالسطوح التي تحده. فنظر أولاً إلى الشمس والقمر وسائر الكواكب، فرأها كلها تطلع من جهة المشرق، وتغرب من جهة المغرب، فما كان يمر على سمت رأسه، رأه يقطع دائرة عظمى، وما مال عن سمت رأسه إلى الشمال أو إلى الجنوب، رأه يقطع دائرة أصغر من تلك. وما كان أبعد عن سمت الرأس إلى أحد الجانبين، كانت دائرته أصغر من دائرة ما هو أقرب. حتى كانت أصغر الدوائر التي تتحرك عليها الكواكب، دائرتين اثنتين: إحداهما حول القطب الجنوبي، وهي مدار سهيل، والاخرى حول القطب الشمالي، وهي المدار الفرقدين. ولما كان مسكنه على خط الاستواء الذي وصفناه أولاً، كانت هذه الدوائر كلها على سطح آفة. ومتشابهة في الجنوب والشمال وكان القطبان معاً ظاهرين له، وكان يترقب إذا طلع كوكب من الكواكب على دائرة كبيرة، وطلع كوكب آخر على دائرة صغيرة، وكان طلوعهما معاً، فكان يرى غروبهما معاً. واطرد له في ذلك جميع الكواكب وفي جميع الأوقات، فتبين له بذلك أن الفلك على شكل الكرة، وقوى ذلك في اعتقاده، ما رآه من رجوع الشمس والقمر وسائر الكواكب إلى المشرق، بعد مغيبها بالمغرب، وما رآه أيضاً من أنها تظهر لبصره على قدر واحد من العظم في حال طلوعها وتوسطها وغروبها، وأنها لو كانت حركتها على غير شكل الكرة لكانت لا محالة في بعض الأوقات، أقرب إلى بصره منها في وقت آخر، ولو كانت كذلك، لكانت مقاديرها واعظامها تختلف عند بصره فيراها في حال القرب أعظم مما يراها في حال البعد، لاختلاف أبعادها عن مركزه حينئذ بخلافها على الأول. فلما لم يكن شيء من ذلك؛ تحقق عنده كروية الشكل. وما زال يتصفح حركة القمر، فيراها آخذه من المغرب إلى المشرق وحركات الكواكب السيارة كذلك، حتى تبين له قدر كبير من علم الهيئة، وظهر له أن حركتها لا تكون إلا بأفلاك كثيرة، كلها مضمنة في فلك واحد، هو أعلاها. وهو الذي يحرك الكل من المشرق إلى المغرب في اليوم والليلة. وشرح كيفية انتقاله. ومعرفة ذلك يطول؛ وهو مثبت في الكتب، ولا يحتاج منه في غرضنا إلا للقدر الذي أردناه. فلما انتهى إلى هذه المعرفة، ووقف على أن الفلك بجملته وما يحتوي عليه، كشيء واحد متصل بعضه ببعض، وأن جميع الأجسام التي كان ينظر فيها أولاً: كالأرض والماء والهواء والنبات والحيوان وما شاكلها، هي كلها في ضمنه وغير خارجة عنه، وأنه كله أشبه شيء بشخص من أشخاص الحيوان؛ وما فيه من الكواكب المنيرة هي بمنزلة حواس الحيوان؛ وما فيه من ضروب الأفلاك، المتصل بعضها ببعض، هي بمنزلة أعضاء الحيوان؛ وما في داخله من الكون والفساد هي بمنزلة ما في جوف الحيوان من أصناف الفضول والرطوبات، التي كثيراً ما يتكون فيها أيضاً حيوان، كما يتكون في العالم الأكبر. فلما تبين له أنه كله كشخص واحد في الحقيقة، واتحدت عنده أجزاؤه الكثيرة بنوع من النظر الذي اتحدت به عنده الأجسام التي في عالم الكون والفساد، تفكر في العالم بجملته، هل هو شيء حدث بعد إن لم يكن، وخرج إلى الوجود بعد العدم؟ أو هو أمر كان موجوداً فيما سلف، ولم يسبقه العدم بوجه من الوجوه؟ فتشك في ذلك ولم يترجح عنده أحد الحكمين على الآخر. وذلك أنه كان إذا أزمع على اعتقاد القدم، اعترضه عوارض كثيرة، من استحالة وجود ما لا نهاية له، بمثل الذي استحال عنده به وجود جسم لا نهاية وكذلك أيضاً كان يرى أن هذا الوجود لا يخلو من الحوادث، فهو لا يمكن تقدمه عليها، وما لا يمكن أن يتقدم على الحوادث، فهو أيضاً محدث. وإذا أزمع على اعتقاد الحدوث، اعترضته عوارض أخرى، وذلك أنه كان يرى أن معنى حدوثه، بعد أن لم يكن لا يفهم إلا على أن الزمان تقدمه، والزمان من جملة العالم وغير منفك عنه، فإذن لا يفهم تأخر العالم عن الزمان. وكذلك أيضاً كان يقول: إذا كان حادثاً، فلا بد له من محدث؛ وهذا المحدث الذي أحدثه، لم أحدثه الآن ولم يحدثه قبل ذلك، الطارئ طرأ عليه ولا شيء هناك غيره، أم لتغير حدث في ذاته؟ فان كان فما الذي احدث ذلك التغيير؟ وما زال يتفكر في ذلك عدة سنين. فتتعارض عنده الحجج، ولا يترجح عنده أحد الاعتقادين على الآخر. فلما أعياه ذلك، جعل يتفكر ما الذي يلزم عن كل واحد من الاعتقادين، فلعل اللازم عنهما يكون شيئاً واحداً. فرأى انه إن اعتقد حدوث العالم خروجه إلى الوجود بعد العدم، فاللازم عن ذلك ضرورة، انه لا يمكن أن يخرج إلى الوجود بنفسه، وانه لا بد له من فاعل يخرجه إلى الوجود، وان ذلك الفاعل لا يمكن إن يدرك بشيء من الحواس، لانه لو أدرك بشيء من الحواس لكان جسماً من الأجسام، ولو كان جسماً من الأجسام لكان من جملة العالم، وكان حادثاً واحتاج إلى محدث، ولو كان ذلك المحدث الثاني أيضاً جسماً، لحتاج إلى محدث ثالث، والثالث إلى رابع، ويتسلسل ذلك إلى غير نهايةً وهو باطل. فإذن لابد للعالم من فاعل ليس بجسم، وإذا لم يكن جسماً فليس إلى إدراكه لشيء من الحواس سبيل، الآن الحواس الخمس لا تدرك إلا الأجسام، وإذا لا يمكن أن يحس فلا يمكن أن يتخيل، لان التخيل ليس شيئاً إلا إحضار صور المحسوسات بعد غيبتها، وإذا لم يكن جسماً فصفات الأجسام كلها تستحيل عليه، وأول صفات الأجسام هو الامتداد في الطول والعرض والعمق، وهو منزه عن ذلك، وعن جميع ما يتبع هذا الوصف من صفات الأجسام. وإذا كان فاعلاً للعالم فهو لا محالة قادر عليه وعالم به "بسم الله الرحمن الرحيم" إلا يعلم من خلق، وهو اللطيف الخبير؟ صدق الله العظيم. وراى أيضاً انه إن اعتقد قدم العالم، وان العدم لم يسبقه، وانه لم يزل كما هو، فان اللازم عن ذلك أن حركته قديمة لا نهاية لها من جهة الابتداء، إذ لم يسبقها سكون يكون مبدؤها منه، وكل حركة فلابد لها من محرك ضرورة، والمحرك أما أن يكون قوة سارية في جسم من الأجسام - أما جسم المتحرك نفسه، واما جسم أخر خارج عنه - واما أن تكون قوة ليست سارية ولا شائعة قي جسم. وكل قوى سارية في جسم وشائعه فيه، فانها تنقسم بانقسامه، وتتضاعف بتضاعفه، مثل الثقل بالحجر مثلاً. المحرك إلى الأسفل. فانه إن قسم الحجر نصفين. وان زيد عليه أخر مثله، زاد في الثقل أخر مثله، فان أمكن أن يتزايد الحجر إلى غير نهاية، كتزايد هذا الثقل إلى غير نهاية، وان وصل الحجر إلى حد ما من العظم ووقف، وصل الثقل إلى ذلك الحد ووقف، لكنه قد تبرهن أن كل جسم فانه لا محالة متناه، فإذن كل قوة في الجسم فهي لا محالة متناهية. فان وجدناها قوة تفعل فعلاً لا نهاية له، فهي قوة ليست في جسم، وقد وجدنا الفلك يتحرك أبداً حركة لانهاية لها ولا انقطاع إذ فرضناه قديماً لا ابتداء له فالواجب على ذلك أن تكون القوة التي تحرك ليست في جسمه، ولا في جسم خارج عنه. فهي إذا لشيء بريء عن الأجسام، وغير موصوف بشيء من أوصاف الجسمية، وقد كان لاح له في نظره الأول في عالم الكون والفساد إن حقيقة وجود كل جسم، إنما هي من جهة صورته التي هي استعداده لضروب الحركات، وان وجوده الذي له من جهة مادته وجود ضعيف لا يكاد يدرك؛ فان وجود العالم كله إنما هو من جهة استعداده لتحريك هذا المحرك البريء عن المادة، وعن صفات الأجسام، المنزه عن أن يدركه حس، أو يتطرق إليه خيال، سبحانه، وإذا كان فاعلاً لحركات الفلك على اختلاف أنواعها، فعلاً لا تفاوت فيه ولا فتور فيه ولا قصور، فهو لا محالة قادر عليها وعالم بها. فانتهى نظره بهذا الطريق إلى ما انتهى إليه بالطريق الأول، ولم يضره في ذلك تشككه في قدم العالم أو حدوثه، وصح له على الوجهين جميعاً وجود فاعل غير الجسم، ولا متصل بجسم ولا منفصل عنه، ولا داخل فيه، ولا خارج عنه، إذ: الاتصال، والانفصال، والدخول، هي كلمات من صفات الأجسام، وهو منزه عنها. ولما كانت المادة في كل جسم مفتقرة إلى الصورة، إذ لا تقوم إلا بها ولا تثبت لها حقيقة دونها، وكانت الصورة لا يصح وجودها إلا من فعل هذا الفاعل تبين له افتقار جميع الموجودات في وجودها إلى هذا الفاعل وأنه لا قيام لشيء منها إلا به فهو إذن علة لها، وهي معلومة له، سواء كانت محدثة الوجود، بعد أن سبقها العدم، أو كانت الابتداء لها من جهة الزمان، ولم يسبقها العدم قط، فانها على كلا الحالتين معلولة، ومفتقرة إلى الفاعل، متعلقة الوجود به، ولولا دوامه لم تدم، ولولا وجوده لم توجد، ولولا قدمه لم تكن قديمة، وهو في ذاته غني عنها وبريء منها! وكيف لا يكون كذلك وقد تبرهن أن قدرته غير متناهية، وأن جميع الأجسام وما يتصل بها أو يتعلق بها، ولو بعض التعلق، هو متناه منقطع. فإذن العالم كله بما في السماوات والأرض والكواكب، وما بينها، وما فوقها، وما تحتها، فعله وخلقه؛ ومتأخر عليه بالذات، وان كانت غير ماخرة عليها بالزمان. كما انك إذا أخذت في قبضتك جسماً من الأجسام، ثم حركت يدك، فان ذلك الجسم لا محالة يتحرك تابعاً لحركة يدك، حركة متأخرة عن حركة يدك، تأخراً بالذات؛ وان كانت لم تتأخر بالزمان عنها، بل كان ابتداؤهما معاً، فكذلك العالم كله، معلول ومخلوق لهذا الفاعل بغير زمان "بسم الله الرحمن الرحيم" إنما أمره إذا أراد شيئاً أن يقول له كن فيكون صدق الله العظيم. فلما راى إن جميع الموجودات فعله، تصفحها من بعد ذا تصفحاً على طريق الاعتبار في قدرة فاعلها؛ والتعجب من غريب صنعته، ولطيف حكمته، ودقيق علمه فتبين له في اقل الأشياء الموجودة، فضلاً عن أكثرها من أثار الحكمة، وبدائع الصنعة، ما قضى منه كل العجب، وتحقق عنده إن ذلك لا يصدر إلا عن فاعل مختار في غاية الكمال وفوق الكمال "بسم الله الرحمن الرحيم" لا يغرب عنه مثقال ذرة في السموات ولا في الأرض ولا اصغر من ذلك ولا أكبر صدق الله العظيم. ثمتأمل في جميع أصناف الحيوان، كيف "بسم الله الرحمن الرحيم" أعطى كل شيء خلقه، ثم هداه صدق الله العظيم لاستعماله، فلولا أنه هداه لاستعمال تلك الأعضاء التي خلقت له في وجوه المنافع المقصود بها، لما انتفع بها الحيوان، وكانت كلاً عليه، فعلم بذلك أنه أكرم الكرماء، وارحم الرحماء. من فيض ذلك الفاعل المختار - جل جلاله - ومن وجوده، ومن فعله، فعلم أن الذي هو في ذاته أعظم منها، وأكمل، واتمم وأحسن، وأبهى وأجمل وأدوم، وأنه لا نسبة لهذه إلى تلك. فما زال يتتبع صفات الكمال كلها، فيراها له وصادرة عنه، ويرى أنه أحق بها من كل ما يوصف بها دونه. وتتبع صفات النقص كلها فرآه بريئاً منها، ومنزهاً عنها؛ وكيف لا يكون بريئاً منها وليس معنى النقص إلا العدم المحض، أو ما يتعلق بالعدم؟ وكيف يكون العدم تعلق أو تلبس، بمن هو الموجود المحض، الواجب الوجود بذاته، المعطي لكل ذي وجود وجوده، فلا وجود إلا هو: فهو الوجود، وهو الكمال، وهو التمام، وهو الحسن، وهو البهاء، وهو القدرة، وهو العلم، وهو هو، و "بسم الله الرحمن الرحيم" كل شيء هالك إلا وجهه صدق الله العظيم. فانتهت به المعرفة إلى هذا الحد، على رأس خمسة أسابيع من منشئه، وذلك خمسة وثلاثون عاماً، وقد رسخ في قلبه من هذا الفاعل، ما شغله عن الفكرة في كل شيء إلا فيه، وذهل عما كان فيه تصفح الموجودات والبحث عنها، حتى صار بحيث لا يقع بصره على شيء من الأشياء، إلا ويرى فيه أثر الصنعة، ومن حينه، فينتقل بفكره على الفور إلى الصانع ويترك المصنوع، حتى اشتد شوقه إليه، وانزعج قلبه بالكلية عن العالم الأدنى المحسوس، وتعلق بالعالم الأرفع المعقول. فلما حصل له العلم بهذا الموجود الرفيع الثابت الوجود الذي لا سبب لوجود جميع الأشياء، أراد أن يعلم بأي شيء حصل له هذا العالم، وبأي قوة أدرك هذا الموجود: فتصفح حواسه كلها وهي: السمع، والبصر، والشم، والذوق، واللمس، فرأى أنها لا تدرك شيئاً إلا جسماً، أو ما هو في الجسم، وذلك أن السمع لا يدرك المسموعات، وهي ما يحدث من تموج الهواء عند تصادم الأجسام، والبصر إنما يدرك الألوان، والشم يدرك الروائح، والذوق يدرك الطعوم، واللمس يدرك الأمزجة والصلابة واللين، والخشونة والملاسة، وكذلك القوة الخيالية لا تدرك شيئاً إلا أن يكون له طول وعرض وعمق؛ وهذه المدركات كلها من صفات الأجسام، وليس لهذه الحواس أدراك شيء سواها، وذلك لأنها قوى شائعة في الأجسام، ومنقسمة بانقسامها، فهي لذلك لا تدرك إلا جسماً منقسماً، لان هذه القوة إذا كانت شائعة في شيء منقسم، فلا محالة أنها إذا أدركت شيئاً من الأشياء، فانه ينقسم بانقسامها؛ فإذن كل قوة في جسم، فانها لا محالة لا تدرك إلا جسماً أو ما هو جسم. وقد تبين إن هذا الموجود الواجب الوجود، بريء من صفات الأجسام من جميع الاتجاهات، فإذن لا سبيل إلى إدراكه إلا بشيء ليس بجسم، ولا هو قوة في جسم، ولا تعلق له وجه من الوجوه بالأجسام، ولا هو داخل فيها ولا خارج عنها، ولا متصل بها ولا منفصل عنها. وقد كان تبين له أن أدركه بذاته، ورسخت المعرفة به عنده، فتبين له بذلك أن ذاته التي أدركه بها أمر غير جسماني، ولا يجوز عليه شيء من صفات الأجسام، وان كل ما يدركه من ظاهر ذاته من الجسمانية فانها ليست حقيقة ذاته، وانما حقيقة ذاته ذلك الشيء الذي أدرك به الموجود المطلق الواجب الوجود. فلما علم أن ذاته ليست هذه المتجسمة التي يدركها بحواسه، ويحيط بها أديمه، هان عنده بالجملة جسمه، وجعل يتفكر في تلك الذات الشريفة، التي أدرك بها ذلك الموجود الشريف الواجب الوجود، ونظر في ذاته تلك الشريفة، هل يمكن أن تبيد أو تفسد وتضمحل، أو هي دائمة البقاء؟ فرأى إن الفساد والاضمحلال إنما هو من صفات الأجسام بأن تخلع صورة وتلبس صورة أخرى، مثل الماء إذا صار هواء، والهواء إذا صار ماء، والنبات إذا صار تراباً، والتراب إذا صار نباتاً، هذا هو معنى الفساد. وأما الشيء الذي ليس بجسم، ولا يحتاج في قوامه إلى جسم، وهو منزه بالجملة عن الجسمانية، فلا يتصور فساده البتة. فلما ثبت له أن ذاته الحقيقة لا يمكن فسادها، أراد إن يعلم كيف يكون حالها إذا اطرح البدن وتخلت عنه، وقد كان تبين له أنها لا تطرحه إلا إذا لم يصلح آلة لها، فتصفح جميع القوى المدركة، فرأى أن كل واحدة منها تارةً تكون مدركة بالقوة، وتارةً تكون مدرة بالفعل: مثل العين في حال تغميضها أو أعراضها عن البصر، فانها تكون مدركة بالقوة - ومعنى مدركه بالقوة أنها لا تدرك الآن وتدرك في المستقبل - وفي حال فتحها واستقبالها للمبصر، تكون مدركه بالفعل - ومعنى مدركة بالفعل أنها الآن تدرك - وكذلك كل واحدة من هذه القوى تكون مدركة بالقوة وتكون مدركة بالفعل، وكل واحدة من هذه القوى إن كانت لم تدرك قط بالفعل، فهي ما دامت بالقوة لا تتشوق إلى إدراك الشيء المخصوص بها لأنها لم تتعرف به بعد، مثل من خلق مكفوف البصر؛ وان كانت قد أدركت بالفعل تارةً، ثم صارت بالقوة، فانها ما دامت بالقوة تشتاق إلى الإدراك بالفعل لأنها قد تعرفت إلى المدرك، وتعلقت به، وحنت إليه، مثل من كان يصيراً ثم عمي فانه لا يزال يشتاق إلى المبصرات. وبحسب ما يكون الشيء المدرك أتم وأبهى وأحسن، يكون الشوق أكثر؛ والتألم لفقده اعظم، ولذلك كان تألم من يفقد بصره بعد الرؤية أعظم من تألم من يفقد شمه، إذ الأشياء التي يدركها البصر أتم وأحسن من التي يدركها الشم، فان كان في الأشياء شيء لا نهاية لكماله، ولا غاية لحسنه وجماله وبهائه، وهو فوق الكمال والبهاء والجمال، وليس في الوجود كمال، ولا حسن، ولا بهاء، ولا جمال إلا صادر من جهته، وفائض من قبله، فمن فقد إدراك ذلك الشيء بعد إن تعرف به، فلا محالة أنهما ما دام فاقد له، يكون في ألام لا نهاية لها، كما أن من كان مدركاً له على الدوام، فانه يكون في لذة لا انفصام لها، وغبطة لا غاية لها ورائها، وبهجة وسرور لا نهاية لهما. وقد تبين له أن الموجود الواجب الوجود. متصف بأوصاف الكمال كلها، ومنزه عن الصفات النقص وبريء منها. وتبين له أن الشيء الذي به يتوصل إلى أدركه أمر لا يشبه الأجسام، ولا يفسد لفسادها؛ فظهر له بذلك أن من كانت له مثل هذه الذات، المعدة لمثل هذا الإدراك؛ فانه إذا أطرح البدن بالموت؛ فإما أن يكون قبل ذلك - في مدة تصريفه للبدن - لم يتعرف قط بهذا الموجود الواجب الوجود؛ ولا اتصل به؛ ولا سمع عنه؛ فهذا إذا فارق البدن لا يشتاق إلى ذلك الموجود ولا يتألم لفقده. واما جميع القوى الجسمانية، فانها تبطل ببطلان الجسم؛ فلا تشتاق أيضاً إلى مقتضيات تلك القوى، ولا تحن إليها، ولا تتألم لفقدها. وهذه حال البهائم غير الناطقة كلها: سواء كانت من صورة الإنسان أو لم تكن. واما إن يكون قبل ذلك - في مدة تصريفه للبدن - قد تعرف بهذا الموجود، وعلم ما هو عليه من الكمال والعظمة والسلطان والحسن إلا انه أعرض عنه واتبع هواه، حتى وافته منيته وهو على تلك الحال، فيحرم المشاهدة، وعنده الشوق إليها فيبقى في عذاب طويل، وألام لا نهاية لها. فأما من يتخلص من تلك الآلام بعد جهد طويل، ويشاهد ما تشوق إليه قبل ذلك، واما أن يبقى في آلامه بقاءً سرمدياً، بحسب استعداده لكل واحد من الوجهين لحياته الجسمانية. واما من تعرف بهذا الموجود الواجب الوجود، قبل أن يفارق البدن، واقبل بكليته عليه والتزم الفكرة في جلاله وحسن بهائه، ولم يعرض عنه حتى وافته منيته، وهذا على حال من الإقبال والمشاهدة بالفعل. فهذا إذا فارق البدن بقي في لذة لا نهاية لها، وغبطة وسرور وفرح دائم، لاتصال مشاهدته لذلك الموجود الواجب الوجود، وسلامة تلك المشاهدة من الكدر والشوائب؛ ويزول عنه ما تقتضيه هذه القوى الجسمانية من الأمور الحسية التي هي - بالإضافة إلى تلك الحال - ألام وشرور وعوائق. فلما تبين له أن كمال ذاته ولذتها إنما هو بمشاهدة ذلك الموجود الواجب الوجود على الدوام، مشاهدة بالفعل أبداً، حتى لا يعرض عنه طرفة عين لكي توافيه منيته، وهو في حال المشاهدة بالفعل، فتتصل لذته دون أن يتخللها ألم. ثم جعل يتفكر كيف يتأتى له دوام هذه المشاهدة بالفعل، حتى لا يقع منه أعراض فكان يلازم الفكرة في ذلك الموجود كل ساعة، فما هو إلا يسنح لبصره محسوس ما من المحسوسات، أو يخرق سمعه صوت بعض الحيوان، أو يتعرضه خيال من الخيالات، أو يناله ألم في أحد اعضائه، أو يصيبه الجوع أو العطش أو البرد أو الحر، أو يحتاج القيام لدفع فضوله؛ فتختل فكرته، ويزول عما كان فيه، ويتعذر عليه الرجوع إلى ما كان عليه من حال المشاهدة، إلا بعد جهد. وكان يخاف أن تفاجأه منيته وهو في حال الأعراض، فيفضي إلى الشقاء الدائم، وألم الحجاب. فساءه حاله ذلك، وأعياء الدواء. فجعل يتصفح أنواع الحيوانات كلها، وينظر أفعالها وما تسعى فيه، لعله يتفطن في بعضها أنها شعرت بهذا الموجود، وجعلت تسعى نحوه، فيتعلم منها ما يكون في سبب نجاته. فرآها كلها إنما تسعى في تحصيل غذائها، ومقتضى شهواتها من المطعوم والمشروب والمنكوح، والاستظلال والاستدفاء، وتجد في ذلك ليلها ونهارها إلى حين مماتها وانقضاء مدتها. ولم ير شيئاً منها ينحرف عن هذا الرأي، ولا يسعى لغيره في وقت من الأوقات، فبان له بذلك أنها لم تشعر بذلك الموجود ولا اشتاقت إليه، ولا تعرفت إليه بوجه من الوجوه، وأنها كلها صائرة إلى العدم، أو إلى حال شبيه بالعدم. فلما حكم على ذلك بالحيوان، علم أن الحكم على النبات أولى، إذ ليس للنبات من الادراكات إلا بعض ما للحيوان. وإذا كان الأكمل إدراكاً لم يصل إلى هذه المعرفة، فالأنقص إدراكاً أحرى أن لا يصل، مع انه رأى أيضاً أن أفعال النبات كلها لا تتعدى الغذاء والتوليد. ثم انه بعد ذلك نظر إلى الكواكب والأفلاك فرآها كلها منتظمة الحركات، جارية على نسق؛ ورآها شفافة ومضيئة بعيدة عن قبول التغيير والفساد، فحدس حدساً قوياً أن لها ذوات سوى أجسامها، تعرف ذلك الموجود الواجب الوجود، وأن تلك الذوات العارفة ليست بأجسام، ولا منطبعة في أجسام مثل ذاته، هو، العارفة، وكيف لا يكون لها مثل تلك الذوات البريئة عن الجسمانية، ويكون لمثله على ما به من الضعف وشدة الاحتياج إلى الأمور المحسوسة، وأنه من جملة الأجسام الفاسدة؟ ومع ما به من النقص، فلم يعقه ذلك عن أن تكون ذاته بريئة عن الأجسام لا تفسد، فتبين له بذلك أن الأجسام السماوية أولى بذلك، وعلم أنها تعرف ذلك الموجود الواجب الوجود وتشاهد على الدوام بالفعل، لأن العوائق التي قطعت به هو عن الدوام المشاهدة من العوارض المحسوسة، لا يوجد مثلها للأجسام السماوية. ثم انه تفكر: لم اختص هو من بين سائر أنواع الحيوانات بهذه الذات التي أشبه بها الأجسام السماوية. وقد كان تبين له أولاً من آمر العناصر واستحالة بعضها إلى بعض، وأن جميع ما على وجه الأرض لا يبقى على صورته؛ بل الكون والفساد متعاقبان عليه أبداً، وأن أكثر هذه الأجسام مختلطة مركبة من أشياء متضادة، ولذلك تؤول إلى الفساد، وانه لا يوجد منه شيء صرفاً، وما كان منها قريباً من أن يكون صرفاً خالصاً لا شائبة فيه، فهو بعيد عن الفساد جداً مثل الذهب والياقوت، وأن الأجسام البسيطة صرفة، ولذلك هي بعيدة عن الفساد، والصور لا تتعاقب عليها. وتبين له هنالك أن جميع الأجسام التي في عالم الكون والفساد، منها ما تتقوم حقيقتها بصورة واحدة زائدة على معنى الجسمية - وهذه هي الاسطقسات الأربع - ومنها ما تتقوم حقيقتها أكثر من ذلك كالحيوان والنبات. فما كان قوام حقيقته بصور أقل، كانت أفعاله أقل، وبعده عن الحياة أكثر، فان عدم الصورة جملة لم يكن فيه إلى الحياة طريق، وصار في حال شبيه بالعدم، وما كان قوام حقيقته بصور أكثر، كانت أفعاله أكثر، ودخوله في حال الحياة أبلغ؛ وان كانت تلك الصورة بحيث لا سبيل إلى مفارقتها لمادتها التي اختصت بها كانت الحياة حينئذ كامل الظهور والكمال والقوة. فالشيء العديم للصورة جملة هو الهيولى والمادة، ولا شيء من الحياة فيها وهي شبيهة بالعدم، والشيء المتقوم بصورة واحدة هي الاسطقسات الأربع وهي في أول مراتب الوجود في عالم الكون والفساد ومنها تتركب الأشياء ذوات الصور الكثيرة. وهذه الاسطقسات ضعيفة الحياة جداً، إذ ليست تتحرك إلا حركة واحدة، وانما كانت ضعيفة الحياة لان لكل واحد منها ضداً ظاهر العناد يخالفه في مقتضى طبيعته، ويطلب أن يغير صورته. فوجوده لذلك غير متمكن، وحياته ضعيف، والبات أقوى حياة منه والحيوان أظهر حياة منه. وذلك أن ما كان من هذه المركبات تغلب عليه طبيعة أسطقس واحد، فلقوته فيه يغلب طبائع الاسطقسات الباقية، ويبطل قواها، ويصير ذلك المركب في حكم الاسطقس الغالب، فلا يستأهل لاجل ذلك من الحياة آل شيئا يسيراً، كما إن ذلك الاسطقس لا يستأهل من الحياة إلا يسيراً ضعيفاً وما كان من هذه المركبات لا تغلب عليه طبيعة أسطقس واحد منها، فان الاسطقسات تكون فيه متعادلة متكافئة، فإذن لا يبطل لأحدهما الآخر قوة الآخر بأكثر مما يبطل ذلك الآخر قوته، بل يفعل بعضها في بعض فعلاً متساوياً، فلا يكون فعل أحد الاسطقسات أظهر فيه، ولا يستولي عليه أحدها، فيكون بعيد الشبه من كل واحد من الاسطقسات، فكأنه لا مضادة لصورته، فيستأهل الحياة بذلك. ومتى زاد هذا الاعتدال وكان أتم وأبعد من الانحراف، كان بعده عن أن يوجد له ضد أكثر، وكانت حياته أكمل. ولما كان الروح الحيواني الذي مسكنه القلب، شديد الاعتدال، لانه ألطف من الأرض والماء وأغلظ من النار والهواء، صار في حكم الوسط ولم يضاده شيء من الاسطقسات مضادة بينه. فاستعد بذلك الصورة الحيوانية، فرأى أن الواجب إلى ذلك أن يكون أعدل ما في هذه الأرواح الحيوانية مستعداً لاتمم ما يكون من الحياة في عالم الكون والفساد، وأن يكون ذلك الروح قريباً من أن يقال أنه لا ضد لصورته، فيشبه لذلك هذه الأجسام السماوية التي لا ضد لصورها؛ ويكون روح ذلك الحيوان، وكأنه وسط بالحقيقة بين الاسطقسات التي لا تتحرك إلى جهة العلو على الإطلاق، ولا إلى جهة السفل، بل لو أمكن أن يجعل في وسط المسافة بين المراكز وأعلى ما تنتهي إليه النار في جهة العلو ولم يطرأ عليه الفساد، لثبت هناك ولم يطلب الصعود ولا نزول. ولو تحرك في المكان، لتحرك حول الوسط كما تتحرك الأجسام السماوية، ولو تحرك في الوضع، لتحرك على نفسه، وكان كروي الشكل إذ لا يمكن غير ذلك، فإذن هو شديد الشبه بالأجسام السماوية. ولما كان قد اعتقد أن أحوال الحيوان، ولم ير فيها ما يظن به انه شعر بالموجود الواجب الوجود، وقد كان علم من ذاتها قد شعرت به، قطع ذلك على أنه هو الحيوان المعتدل الروح، الشيبة بالأجسام السماوية وتبين لو انه نوع مباين لسائر الحيوان، وانه إنما خلق لغاية أخرى، وأعد لامر عظيم، لم يعد له شيء من أنواع الحيوان، وكفى به شرفاً أن يكون أحس جزأيه - وهو الجسماني - أشبه الأشياء بالجواهر السماوية الخارجة عن عالم الكون والفساد، المنزهة عن الحوادث النقص والاستحالة والتغيير. وأما أشرف جزأيه، فهو الشيء الذي به عرف الموجود الواجب الوجود، وهذا الشيء العارف، أمر رباني الهي يستحيل ولا يلحقه الفساد، ولا يوصف بشيء مما توصف به الأجسام، ولا يدرك بشيء من الحواس، ولا يتخيل، ولا يتوصل إلى معرفته بآلة سواه، بل يتوصل إليه به؛ فهو العارف والمعروف، والمعرفة؛ وهو العالم، والمعلوم، والعلم؛ لا يتباين في شيء من ذلك، إذ التباين والانفصال من صفات الأجسام ولواحقها، ولا جسم هنالك ولا صفة جسم ولا لاحق بجسم! فلما تبين له الوجه الذي اختص به من بين سائر أصناف الحيوان بمشابهة الأجسام السماوية، رأى إن الواجب عليه أن يتقبلها ويحاكي أفعالها ويتشبه بها جهده. وكذلك رأى أنه بجزئه الاشرف الذي به عرف الموجود الواجب الوجود، فيه شبه ما منه من حيث هو منزه عن صفات الأجسام، وكما أن الواجب الوجود منزه عنها، فرأى ايضاً انه يجب عليه أن يسعى في تحصيل صفاته لنفسه من أي وجه أمكن، وان يتخلق بأخلاقه ويقتدي بأفعاله، ويجد في تنفيذ إرادته، ويسلم الآمر له، ويرضى بجميع حكمه، رضى من قلبه ظاهراً وباطناً، بحيث يسر به وان كان مؤلماً لجسمه وضاراً به ومتلفتاً لبدنه بالجملة. وكذلك رأى فيه شبهاً من سائر أنواع الحيوان بجزئه الخسيس الذي هو من عالم الكون والفساد، وهو البدن المظلم والكثيف، الذي يطالبه بأنواع المحسوسات من المطعوم والمشروب والمنكوح، ورأى أيضاً أن ذلك البدن لم يخلق له عبثاً ولا قرن به لامر باطل، ويجب عليه أن يتفقده ويصلح من شأنه. هذا التفقد لا يكون منه إلا بفعل يشبه أفعال سائر الحيوان. فاتجهت عنده الأعمال التي يجب عليه أن يفعلها نحو ثلاثة أغراض: أما عمل يتشبه بالحيوان الغير الناطق. واما عمل يتشبه به بالأجسام السماوية. واما عمل يتشبه به بالموجود الواجب الوجود. فالتشبه الأول: يجب عليه من حيث البدن المظلم ذو الأعضاء المنقسمة، والقوى المختلفة، والمنازع المتفننة. والتشبه الثاني: يجب عليه من حيث له الروح الحيواني الذي مسكنه القلب، وهو مبدأ لسائر البدن، ولما فيه من القوى. والتشبه الثالث: يجب عليه من حيث هو، أي: من حيث هو الذات التي بها عرف ذلك الموجود الواجب الوجود. وكان أولاً قد وقف على أن سعادته وفوزه من الشقاء، إنما هي في دوام المشاهدة لهذا الموجود الواجب الوجود، حتى يكون بحيث لا يعرض بطرفة عين. ثم أنه نظر بالوجه الذي يتأتى له به هذا الدوام، فأخر له النظر أنه يجب عليه الاعتمال في هذه الأقسام الثلاثة من التشبيهات: آما التشبه الأول، فلا يحصل له به شيء من هذه المشاهدة، بل هو صارف عنها وعائق دونها، إذ هو تصرف في الأمور المحسوسة، والأمور المحسوسة كلها حجب معترضة دون تلك المشاهدة؛ وانما احتيج إلى هذا التشبه لاستدامة هذا الروح الحيواني الذي يحصل به التشبه الثاني بالأجسام السماوية. فالضرورة تدعو إليه من هذا الطريق،ولو كان لا يخلو من تلك المضرة. واما التشبه الثاني، فيحصل له به حظ عظيم من المشاهدة على الدوام، لكنها مشاهدة يخالطها شوب؛ اذ من يشاهد ذلك النحو من المشاهدة على الدوام فهو مع تلك المشاهدة يعقل ذاته ويلتفت إليه حسبما يتبين بعد هذا. واما التشبه الثالث، فتحصل به المشاهدة الصرفة، والاستغراق المحض الذي لا التفات فيه بوجه من الوجوه الا إلى الموجود الواجب الوجود، والذي يشاهد هذه المشاهدة قد غابت عنه ذات نفسه وفنيت وتلاشت. وكذلك سائر الذوات، كثيرة كانت أو قليلة، إلا ذات الواحد الحق الواجب الوجود - جل وتعالى وعز. فلما تبين له أن مطلوبه الأقصى هو هذا التشبه الثالث، وأنه لا يحصل له إلا بعد التمرن والاعتمال مدة طويلة في التشبه الثاني، وان هذه المدة لا تدوم له بالتشبه الأول، وعلم أن التشبه الأول - وان كان ضرورياً، فانه عائق بذاته وان كان معيناً بالعرض لا بالذات لكنه ضروري- فألزم نفسه أن لا يجعل لها حظاً من هذا التشبه الأول، إلا بقدر الضرورة، وهي الكفاية التي لا بقاء للروح الحيواني بأقل منها. ووجد ما تدعو إليه الضرورة في بقاء هذا الروح أمرين: أحدهما: ما يمده من الداخل، ويخلف عليه بدل ما يتخلل منه وهو الغذاء. والأخر: ما يقيه من الخارج، ويدفع عنه وجوه الأذى: من البرد والحر والمطر ولفح الشمس والحيوانات المؤذية ونحو ذلك. ورأى أنه إن تناول ضرورية من هذه جزافاً كيفما اتفق، ربما وقع في السرف واخذ فوق الكفاية. فكان سعيه على نفسه من حيث لا يشعر، فرأى أن الحزم له أن يفرض لنفسه فيها حدوداً لا يتعداها، ومقادير لا يتجاوزها، وبأن له الفرض يجب أن يكون في جنس ما يتغذى به. وأي شيء يكون وفي مقداره وفي المدة التي تكون بين العبادات إليه. فنظر أولاً إلى أجناس ما به يتغذى، فرآها ثلاثة أضرب: أولاً: أما نبات لم يكمل بعد نضجه ولم ينته إلى غاية تمامه، وهي أصناف البقول الرطبة التي يمكن الاغتذاء بها. ثانياً: واما ثمرات النبات الذي تم وانتهى وأخرج بذرة ليتكون منه أخر من نوعه حفظاً له، وهي أصناف الفواكه رطبها ويابسها. ثالثاً: واما حيوان من الحيوانات التي يتغذى بها: أما البرية واما البحرية. وكان قد صح عنده أن هذه الأجناس كلها، من فعل ذلك الموجود الواجب الوجود الذي تبين له أن سعادته في القرب منه، وطلب التشبه به، ولا محالة أن الاغتذاء بها مما يقطعها عن كمالها ويحول بينها وبين الغاية القصوى المقصودة بها. فكان ذلك اعتراض على فعل الفاعل. وهذا الاعتراض مضاد لما يطلبه من القرب منه والتشبه به. فرأى أن الصواب كان له لو أمكن أن يمتنع عن الغذاء جملة واحدة، لكنه لما لم يمكنه ذلك، لانه أن امتنع عنه أل ذلك إلى فساد جسمه، فيكون ذلك اعتراضاً على فاعله أشد من الأول، إذ هو أشرف من تلك الأشياء الآخر التي يكون فسادها سبباً لبقائه. فاستهل أيسر الضررين، وتسامح في اخف الاعتراضين، ورأى إن يأخذ من هذه الأجناس إذا عدمت آيها تيسر له، بالقدر الذي يتبين له بعد هذا. فأما إن كانت كلها موجودة فينبغي له حينئذ إن يتثبت ويتخير منها ما لم يكن في أخذه كبير اعتراض على فعل فاعل، وذلك مثل لحوم الفواكه التي قد تناهت في الطيب، وصلح ما فيها لتوليد البزر على الشرط التحفظ على ذلك البزر، بان لا يأكله ولا يفسده ولا يلقيه في موضع لا يصلح للنبات، مثل الصفاة والسبخة ونحوهما. فان تعذر عليه وجود مثل هذه الثمرات ذات الطعم الغاذي، كالتفاح والكمثرى والأجاص ونحوها، كان له عند ذلك إن يأكل آما الثمرات التي لا يغذو منها إلا نفس البزر، كالجوز والقسطل، واما من البقول التي لم تصل بعد حد كمالها. والشرط عليه في هذين لأن يقصد أكثرها وجوداً وأقواها توليداً، وان لا يستأصل أصولها ولا يفني بزرها. فان عدم هذه، فله أن يأخذ من الحيوان آو من بيضه، والشرط عليه من الحيوان إن يأخذ من أكثره وجوداً، ويستأصل منه نوعاً بأسره. هذا ما رأى في جنس ما يتغذى به. واما المقدر فرأى أن يكون بحسب ما يسد خلة الجوع ولا يزيد عليها. واما الزمان الذي بين كل عودتين، فرأى انه إذا اخذ حاجته من الغذاء، أن يقيم عليه ولا يتعرض لسواه، حتى يلحقه ضعف يقطع به بعض الأعمال التي تجب عليه في التشبه الثاني، وهي التي يأتي ذكرها بعد هذا. فأما ما تدعو إليه الضرورة في بقاء الروح الحيواني مما يقيه من خارج، فكان الخطب فيه يسيراً: إذ كان مكتسياً بالجلود، وقد كان له مسكن يقيه مما يرد عليه من خارج، فاكتفى بذلك ولم يرى الاشتغال به، والتزم في غذائه القوانين التي رسمها لنفسه، وهي التي تقدم شرحها. ثم اخذ في العمل الثاني، وهو التشبه بالأجسام السماوية والاقتداء بها، والتقبل أوصافها، فانحصرت عنده في ثلاثة أضرب: الضرب الأول: أوصاف لها بالإضافة إلى ما تحتها من عالم الكون والفساد، وهي ما تعطيه إياه من التسخين بالذات، آو التبريد بالعرض، والإضاءة والتلطيف والتكثيف، إلى سائر ما تفعل فيه من الأمور التي بها يستعد لفيضان الصور الروحانية عليه من عند الفاعل الواجب الوجود. والضرب الثاني: أوصاف لها في ذاتها، مثل كونها شفافة وناصعة وطاهرة منزهة عن الكدر وضروب الرجس، ومتحركة بالاستدارة بعضها على مركز نفسها، وبعضها على مركز غيرها. والضرب الثالث: أوصاف لها بالإضافة إلى الموجود الواجب الوجود، مثل كونها تشاهد مشاهدة دائمة، وتعرض عنه، وتتشوق إليه، وتتصرف بحكمه، وتتسخر في تتميم إرادته، ولا تتحرك إلا بمشيئته وفي قبضته. فجعل يتشبه بها جهده في كل من هذه الاضرب الثلاثة. آما الضرب الأول: فكان تشبه بها فيه: إن ألزم نفسه إن لا يرى ذا حاجة آو عاهة آو مضرة، أو ذا عائق من الحيوان أو النبات، وهو يقدر على أزالتها عنه إلا ويزيلها. فمتى وقع بصره على نبات قد حجبه عن الشمس حاجب آو تعلق به نبات آخر يؤذيه، أو عطش عطشاً يكاد يفسده، أزال عنه ذلك الحاجب إن كان ما يزال، وفصل بينه وبين ذلك المؤذي بفاصل لا يضر المؤذي، وتهده بالسقي ما أمكنه. ومتى وقع بصره على حيوان قد أرهقه سبع آو نشب به ناشب، آو تعلق به شوك، آو سقط على عينيه آو آذنيه شيء يؤذيه، آو مسه ظمأ آو جوع، تكفل بإزالة ذلك كله عنه جهده واطعمه وسقاه. ومتى وقع بصره على ماء يسيل إلى سقي نبات أو حيوان وقد عاقه عن ممره ذلك عائق، من حجر سقط فيه، آو جرف انهار عليه، ازال ذلك كله عنه. وما زال يمعن في هذا النوع من ضروب التشبه حتى بلغ فيه الغاية. واما الضرب الثاني: فكان تشبهه بها فيه إن الزم نفسه دوام الطهارة وإزالة الدنس والرجس عن جسمه والاغتسال بالماء في أكثر الأوقات، وتنظيف ما كان من أظافره واسنانه ومغابن بدنه، وتطيبها بما أمكن من طيبات النبات وصنوف الدهون العطرة، وتعهد لباسه بالتنظيف والتطييب حتى كان يتلألأ حسناً وجمالاً ونظافة وطيباً. والتزم مع ذلك ضروب الحركة على الاستدارة: فتارةً كان يطوف بالجزيرة، ويدور على ساحلها ويسيح باكنافها، وتارةً كان يطوف ببيته، او ببعض الكدى أدوارا معدوده: آما مشياً، آما هرولة؛ وتارة يدور على نفسه حتى يغشه عليه. وأما الضرب الثالث: فكان تشبهه بها فيه، إن كان يلازم الفكرة في تلك الموجود الواجب الوجود، ثم يقطع علائق المحسوسات. ويغمض عينيه، ويسد أذنيه، ويضرب جهده عن تتبع الخيال، ويروم بمبلغ طاقته إن لا يفكر في شيء سواه، ولا يشترك به احداً ويستعين على ذلك بالاستدارة على نفسه والاستحثاث فيها. فكان اذا اشتد في الاستدارة، غابت عنه جميع المحسوسات، وضعف الخيال وسائر القوى التي إلى الألأت الجسمانية، وقوي فعل ذاته - التي هي بريئة من الجسم - فكانت في بعض الأوقات فكرته قد تخلص عن الشوب ويشاهد بها الموجود الواجب الوجود، ثم تكر عليه القوى الجسمانية فتفسد عليه حاله، وترده إلى اسفل السافلين. ويعود من ذي قبل، فان لحقه ضعف يقطع به عن غرضه تناول بعض الأغذية عن الشرائط المذكورة. ثم انتقل إلى شأنه من التشبه بالأجسام السماوية بالأضرب الثلاثة المذكورة. ودأب على ذلك مدة وهو يجاهد قواه الجسمانية وتجاهده، وينازعها وتنازعه في الأوقات التي يكون له عليها الظهور، وتتخلص فكرته عن الشوب، يلوح له شيء من أحوال أهل التشبه الثالث. ثم جعل يطلب التشبه الثالث، ويسعى في تحصيله، فينظر في صفات الموجود الواجب الوجود. وقد كان تبين له أثناء نظره العلمي قبل الشروع في العمل، إنها على ضربين: آما صفة ثبوت: كالعلم والقدرة والحكمة. وأما صففة سلب: كتنزه عن الجسمانية وعن صفات الأجسام ولواحقها، وما يتعلق بها، ولو على بعد. وأن صفات الثبوت يشترط فيها هذا التنزيه حتى لا يكون فيها شيء من صفات الأجسام التي من جملتها الكثرة، فلا تتكثر ذاته بهذه الصفات الثبوتية، ثم ترجع كلها إلى معنى واحد هي حقيقة ذاته. فجعل يطلب كيف يتشبه به في كل واحد من هذين الضربين. آما صفات الاجاب، فلما علم انها كلها راجعة إلى حقيقة ذاته، وانه لا كثرة فيها بوجه من الوجوه، إذ الكثرة من صفات الأجسام؛ وعلم إن علمه بذاته؛ ليس معنى زائداً على ذاته، بل ذاته هي علمه لذاته؛ وعلمه بذاته هو ذاته، تبين له انه إن أمكنه هو إن يعلم ذاته، فليس ذلك العلم الذي علم به ذاته معنى زائداً على ذاته، بل هو هو! فرأى إن التشبه به من صفات الاجاب، هو ان يعلمه فقط دون إن يشرك به شيئاً من صفات الأجسام؛ فاخذ نفسه بذلك. واما صقات السلب، فانها كلها راجعة إلى التنزه عن الجسمية. فجعل يطرح اوصاف الجسمية عن ذاته. وكان قد طرح منها كثيراً في رياضته المتقدمة التي كان ينحو بها بالتشبه بالأجسام السماوية. إلا انه أبقى منها بقايا كثيرة: كحركة الاستدارة - والحركة من أخص صفات الأجسام - وكل الاعتناء بأمر الحيوان والنبات والرحمة لها، والاهتمام بإزالة عوائقها. فان هذه أيضاً من صفات الأجسام، إذ لا يراها أولاً إلا بقوة جسمانية، ثم يكدح بأمرها بقوة جسمانية أيضاً. فاخذ في طرح ذلك كله عن نفسه، إذ هي بجملتها مما لا يليق بهذه الحالة التي يطلبها الآن. وما زال يقتصر على السكون في قصر مغارته مطرقاً، غاضاً بصره، معرضاً عن جميع المحسوسات والقوى الجسمانية، مجتمع الهم والفكرة في الموجود الواجب الوجود وحده دون شركه؛ فمتى سنح بخياله سانح سواه، طرده عن خياله جهده، ودافعه وراض نفسه على ذلك، ودأب فيه مدة طويلة، بحيث تمر عليه عدة أيام لا يتغذى فيها ولا يتحرك. وفي خلال شدة مجاهدته هذه ربما كانت تغيب عن ذكره وفكره جميع الأشياء إلا ذاته، فانها كانت لا تغيب عنه في وقت استغراقه بمشاهدة الموجود الأول الحق الواجب الوجود. فكان يسوءه ذلك، ويعلم انه شوب في المشاهدة المحضة، وشركه في الملاحظة. ومازال يطلب الفناء عن نفسه والإخلاص في مشاهدة الحق حتى تأتى له ذلك، وغابت عن ذكره وفكره السموات والأرض وما بينهما، وجميع الصور الروحانية والقوى الجسمانية، وجميع القوى المفارقة للمواد، والتي هي الذوات العارفه بالموجود الحق؛ وغابت ذاته في جملة تلك الذوات، وتلاشى الكل واضمحل، وصار هباءً منثوراً، ولم يبقى إلا الواحد الحق الموجود الثابت الوجود. وهو يقول بقوله الذي ليس معنى زائداً على ذاته: "بسم الله الرحمن الرحيم" لمن الملك اليوم لله الواحد القهار صدق الله العظيم ففهم كلامه وسمع ندائه ولم يمنعه عن فهمه كونه لا يعرف الكلام، ولا يتكلم. واستغرق في حالته هذه وشاهد ما لا عين رأت ولا إذن سمعت! ولا خطر على قلب بشر. فلا تعلق قلبك بوصف آمر لم يخطر على قلب بشر، فان كثيراً من الأمور التي تخطر على قلوب البشر قد يتعذر وصفه، فكيف بأمر لا سبيل إلى خطورة على القلب، ولا هو من عالمه ولا من طوره!؟ ولست أعني بالقلب جسم القلب، ولا الروح التي في تجويفه بل أعني صورة تلك الروح الفائضة بقواها على بدن الإنسان، فان كل واحد من هذه الثلاثة قد يقال له قلب ولكن لا سبيل لخطور ذلك الآمر على واحد من هذه الثلاثة، ولا يتأتى التعبير إلا عما الخطر علها. ومن رام التعبير عن تلك الحال، فقد رام مستحيلاً وهو بمنزلة من يريد أن يذوق الألوان من حيث هي الألوان، ويطلب أن يكون السواد مثلاً حلواً أو حامضاً. لكنا، مع ذلك، لا نخيلك عن إشارات نومئ بها إلى ما شاهده من عجائب ذلك المقام، على سبيل ضرب المثل، لا على سبيل قرع باب الحقيقية. إذ لا سبيل إلى التحقق بما في ذلك المقام إلا بالوصول إليه. فأصغ الآن بسمع قلبك، وحدق ييصر إلى ما أشير به اليك لعلك أن تجد منه هدياً يلقيك على جادة الطريق! وشرطي عليك أن لا تطلب مني في هذا الوقت مزيد بيان بالمشافهة على ما أودعه هذه الاوراق فان المجال ضيق، والتحكم بالألفاظ على آمر ليس من شأنه أن يلفظ به خطر. فأقول: انه لما فني عن ذاتهوعن جميع وعن جميع الذوات ولم ير في الوجود إلا الواحد القيوم، وشاهد ما شاهد، ثم عاد إلى ملاحظة الاغيار عندما آفاق من حالة تلك التي شبيه بالسكر، خطر بباله انه لا ذات له يغاير بها ذات الحق تعالى، وان حقيقة ذاته هي ذات الحق، وان الشيء الذي كان يظن أولاً انه ذات المغايرة لذات الحق، ليس شيئاً في الحقيقة، بل ليس ثم شيء إلا ذات الحق، وان ذلك بمنزلة نور الشمس الذي يقع على الأجسام الكثيفة فتراه يظهر فيها. فإنه وإن نسب إلى الجسم الذي يظهر فيه، فليس هو في الحقيقية شيئاً سوى نور الشمس. وان زال ذلك الجسم زال نوره، وبقي نور الشمس بحاله لم ينقص عند حضور ذلك الجسم ولم يزد عند مغيبه. ومتى حدث جسم يصلح لقبول ذلك النور، قبله، فإذا عدم الجسم عدم ذلك القبول، ولك يكن له معنى، عنده هذا الظن بما قد بان له من إن ذات الحق، عز وجل، لا تتكثر بوجهه من الوجوه، وأن علمه بذاته، وهو ذاته بعينها. فلزم عنده من هذا أن حصل عنده العلم بذاته، فقد حصلت عنده ذاته، وقد كان حصل عنده العلم فحصلت عنده الذات. وهذه الذات لا تحصل إلا عند ذاتها، ونفس حصولها هو الذات؛ فإذن هو الذات بعينها. وكذلك جميع الذوات المفارقة للمادة العارفة بتلك الذات الحقه التي كان يراها أولاً كثيرة، وصارت عنده بهذا الظن شيئاً واحداً. وكادت هذه الشبه ترسخ في نفسه لولا أن تداركه الله برحمته وتلافاه بهدايته، فعلم إن الشبهة انما ثارت عنده من بقايا ظلمة الأجسام، وكدورة المحسوسات. فان الكثير والقليل والواحد والوحدة، والجمع والاجتماع، والافتراق، هي كلها من صفات الأجسام، وتلك الذوات المفارقة العارفة بذات الحق، عز وجل، لبرائتها عن المادة، لا يجب إن يقال انها كثيرة، ولا واحدة، لان الكثرة انما هي مغايرة الذوات بعضها لبعض، والوحدة أيضاً لا تكون إلا بالاتصال. ولا يفهم شيء من ذلك إلا في المعاني المركبة المتلبسة بالمادة. غير إن العبارة في هذا الموضع قد تضيق جداً لانك إن عبرت عن تلك الذوات المفارقة بصيغة الجمع حسب لفظنا هذا، أوهم ذلك معنى الكثرة فيها، وهي بريئة عن الكثرة. وان أنت عبرت بصيغة الإفراد، اوهم ذلك معنى الاتحاد، وهو مستحيل عليها. وكأني بمن يقف على هذا الموضع من الخفافيش الذين تظلم الشمس في أعينهم يتحرك في سلسلة جنونه، ويقول: لقد افرطت في تدقيقك حتى انك قد انخلعت عن غريزة العقلاء، واطرحت حكم معقول، فان من أحكام العقل إن الشيء آما واحد واما كثير، فليتئد في غلوائه، وليكف من غرب لسانه وليتهم نفسه، وليعتبر بالعالم المحسوس الخسيس الذي هو أطباقه بنحو ما اعتبر به حي بن يقظان حيث كان بنظر فيه بنظر فيراه كثيراً كثرة لا تنحصر ولا تدخل تحت حد، ثم ينظر فيه بنظر آخر، فيراه واحداً. وبقي في ذلك متردداً ولم يكنه إن يقطع بأحد الوصفين دون الآخر. هذا فالعالم المحسوس منشأ الجمع والإفراد، وفيه الانفصال والاتصال، والتحيز والمغايرة، والاتفاق والاختلاف، فما ظنه بالعالم الإلهي الذي لا يقال فيه كل ولا بعض، ولا ينطق في أمره بلفظ من الألفاظ المسموعة، إلا وتوهم فيه شيء على خلاف الحقيقة، فلا يعرفه إلا من شاهده؛ ولا تثبت حقيقته إلا عند من حصل فيه. واما قوله: حتى انخلعت عن غريزة العقلاء، واطرحت حكم المعقول. فنحن نسلم له ذلك، ونتركه مع عقله وعقلائه، فان العقل الذي يعنيه هو أمثاله، انما هو القوة الناطقة التي تتصفح أشخاص الموجودات المحسوسة، وتقتنص منها المعنى الكلي. والعقلاء الذين يعنيهم، هم ينظرون من هذا النظر والنمط الذي كلامنا فيه فوق هذا كله، فليسد عنه سمعه من لا يعرف سوى المحسوسات وكلياتها، وليرجع إلى فريقه الذين "بسم الله الرحمن الرحيم" يعملون ظاهراً من الحياة الدنيا. وهم عن الآخرة هم غافلون. صدق الله العظيم. فان كنت ممن يقتنع بهذا النوع من التلويح والإشارة إلى ما في العالم الإلهي، ولا تحمل ألفاظاً من المعاني على ما جرت العادة بها في تحميلها إياه، فنحن نزيدك شيئاً مما شاهده حي بن يقظان في مقام أولي الصدق الذي تقدم ذكره، فتقول: انه بعض الاستغراق المحض، والفناء التام، وحقيقة الوصول، وشاهد للفلك الأعلى، الذي لا جسم له، ورأى ذاتاً بريئة عن المادة، ليست هي ذات الواحد الحق، ولا هي نفس الفلك، ولا هي غيرها؛ وكأنها صورة الشمس التي تظهر في مرآة من المرائي الصقيلة، فانها ليست هي الشمس ولا المرأة ولا غيرهما. وراى لذات ذلك الفلك المفارقة من الكمال والبهاء والحسن، ما يعظم عن إن يوصف بلسان، ويدق إن يكسى بحرف آو صوت، وراه في غاية من اللذة والسرور، والغبطة والفرح، بمشاهدة ذات الحق جل جلاله. وشاهد ايضاً للفلك الذي يليه، وهو فلك الكواكب الثابتة، ذاتاً بريئة عن المادة أيضاً، ليست هي ذات الواحد الحق، ولا ذات الفلك الأعلى المفارقة، ولا نفسه، ولا هي غيرها. وكأنها صورة الشمس التي تظهر في المرآة قد انعكست إليها من مرآة أخرى مقابلة للشمس، ورأى لهذه الذات ايضاً من البهاء والحسن واللذة مثل ما راى لتلك التي للفلك الأعلى. وشاهد ايضاً للفلك الذي يلي هذا، وهو فلك زحل ذاتاً مفارقة للمادة ليست هي شيئاً من الدواب التي شاهدها قبله ولا هي غيرها؛ وكأنها صورة الشمس التي تظهر في مرآة قد انعكست إليها الصورة من مرآة مقابلة للشمس؛ وراى لهذه الذات ايضاً مثل ما راى آمل قبلها من البهاء واللذة. ومازال يشاهد لكل فلك ذاتاً مفارقة بريئة عن المادة ليست هي شيئاً من الذوات التي قبلها ولا هي غيرها وكأنها صورة الشمس التي تنعكس من مرآة على مرآة، على رتب مرتبة بحسب ترتيب الأفلاك. وشاهد لكل ذات من هذه الذوات من الحسن والبهاء، واللذة والفرح، ما لا عين رأت، ولا أذن سمعت، ولا خطر على قلب بشر. إلى أن انتهى إلى عالم الكون والفساد، وهو جميعه حشو فلك القمر. فرأى له ذاتاً بريئة عن المادة ليست شيئاً من الذوات التي شاهدها قبلها، ولا هي سواها. ولهذه سبعون ألف وجه، في كل وجه سبعون ألف فم، في كل فم سبعون ألف لسان، يسبح بها ذات الواحد الحق، ويقدسها ويمجدها، لا يفتر؛ ورأى لهذه الذات، التي توهم فيها الكثرة وليست كثيرة، من الكمال واللذة، مثل الذي رآه لما قبلها. وكأن هذه الذات صورة الشمس التي تظهر في ماء مترجرج، وقد انعكست إليها الصورة من آخر المرايا التي انتهى إليها الانعكاس على الترتيب المتقدم من المرآة الأولى التي قابلت الشمس بعينها. ثم شاهد لنفسه ذاتاً مفارقة، لو جاز إن تتبعض ذات السبعين ألف وجه، لقلنا انها بعضها. ولولا إن هذه الذات حدثت بعد إن لم تكن، لقلنا إنها هي! ولولا اختصاصها ببدنه عند حدوثه، لقلنا إنها لم تحدث! وشاهد في هذه الرتبة ذواتاً، مثل ذاته، لاجسام كانت ثم اضمحلت، ولاجسام لم تزل معه في الوجود، وهي من الكثرة في حد بحيث لا تتناهى إن جاز أن يقال لها كثيرة، أو هي كلها متحدة إن جاز إن يقال لها واحدة. وراى لذاته ولتلك الذوات التي في رتبته من الحسن والبهاء واللذة غير المتناهية، ما لا عين رأت ولا أذن سمعت، ولا خطر على قلب بشر، ولا يصفه الواصفون، ولا يعقله إلا الواصلون العارفون. وشاهد ذواتاً كثيرة مفارقة للمادة كأنها مرايا صدئة، قد ران عليها الخبث، وهي مع ذلك مستدبرة للمرايا الصقيلة التي ارتسمت فيها صورة الشمس، ومولية عنها بوجوهها، وراى لهذه الذوات من القبح والنقص ما لم يقم بباله قط؛ وراها في ألام لا تنقضي، وحسرات لا تنمحي؛ قد أحاط بها سرادق العذاب، وأحرقتها نار الحجاب، ونشرت بمناشير بين الانزعاج والانجذاب. وشاهد هنا ذواتاً سوى هذه المعذبة تلوح ثم تضمحل، وتنعقد ثم تنحل، فتثبت فيها وأنعم النظر إليها، فرأى هولاً عظيماً وخطباً جسيماً، وخلقاً حثيثاً، وأحكاماً بليغة، وتسوية ونفخاً وإنشاء ونسخاً. فما هو إلا إن تثبت قليلاً، فعادت إليه حواسه، وتنبه من حاله تلك التي كانت شبيهة بالغشي، وزلت قدمه عن ذلك المقام، ولاح له العالم المحسوس، وغاب عنه العالم الإلهي: إذ لم يكن اجتماعهما في حال واحدة، إذ الأخرى والدنيا كضرتين، إن أرضيت احدهما أسخطت الأخرى، فان قلت يظهر مما حكيته من هذه المشاهدة، إن الذوات المفارقة إن كانت لجسم دائم الوجود لا يفسد، كالأفلاك، كانت هي دائمة الوجود؛ وان كانت لجسم يؤول إلى الفساد كالحيوان الناطق، فسدت هي واضمحلت وتلاشت، حسبما مثلث به في المرايا الانعكاس، فان الصورة لا ثبات لها إلا ثبات بثبات المرآة، فإذا فسدت المرآة صح فساد الصورة واضمحلت هي؛ فأقول لك: ما لأسرع ما نسيت العهد، وحلت عن الربط، ألم نقدم إليك إن مجال العبارة هنا ضيق، وان الألفاظ على كل حال توهم غير الحقيقة وذلك الذي توهمته إنما أوقعك فيه، إن جعلت المثال والممثل به على حكم واحد من جميع الوجوه. ولا ينبغي أن يفعل ذلك في أصناف المخاطبات المعتادة، فكيف ها هنا والشمس ونورها، وصورتها وتشكلها والمرايا والصور الحاصلة فيها، كلها أمور غير مفارقة للأجسام، ولا قوام لها إلا بها وفيها؟ فلذلك افتقرت في وجودها إليها وبطلت ببطلانها. واما الذوات الإلهية، والأرواح الربانية، فانها كلها بريئة عن الأجسام ولواحقها ومنزهة غاية التنزيه عنها، فلا ارتباط ولا تعلق لها بها، وسواء بالإضافة إليها بطلان الأجسام أو ثبوتها، ووجودها أو عدمها؛ وانما ارتباطها وتعلقها بذات الواحد الحق الموجود الواجب الوجود، الذي هو أولها ومبدؤها وسببها وموجدها، وهو يعطيها الدوام ويمدها بالبقاء والتسرمد؛ ولا حاجة بها إلى الأجسام بل الأجسام المحتاجة إليها. ولو جاز عدمها لعدمت الأجسام فانها هي مبديها، كما انه لو جاز إن تعدم ذات الواحد الحق - تعالى وتقدس عن ذلك؛ لا اله إلا هو! - لعدمت هذه الذوات كلها، ولعدمت الأجسام، ولعدم العالم الحسي بآسره، ولم يبق موجود، إذ الكل مرتبط بعضه ببعض. والعالم المحسوس وان كان تابعاً للعالم الإلهي، شبيه الظل له؛ والعالم الإلهي مستغن عنه وبريء منه فانه مع ذلك قد يستحيل فرض عدمه، إذ هو لا محالة تابع للعالم الإلهي، وانما فساده إن يبدل، لا إن يعدم بالجملة، وبذلك نطق الكتاب العزيز حيثما وقع هذا المعنى منه في تسيير الجبال وتسييرها كالعهن والناس كالفراش. وتكوير الشمس والقمر، وتفجيرالبحار يوم تبدل الارض غير الأرض والسموات. فهذا القدر هو الذي امكنني الآن أن أشير إليك به فيما شاهده حي بن يقظان في ذلك المقام الكريم فلا تلتمس الزيادة عليه من جهة الألفاظ فان ذلك كالمعتذر. واما تمام خبره - فسأتلوه عليك إن شاء الله تعالى: وهو انه لما عاد إلى العالم المحسوس، وذلك بعد جولا نه حيث جال، سئم تكاليف الحياة الدنيا، واشتد شوقه إلى الحياة الدنيا، واشتد شوقه إلى الحياة القصوى، فجعل يطلب العود إلى ذلك المقام بالنحو الذي طلبه أولاً حتى وصل إليه بأيسر من السعي الذي وصل به أولاً ودام فيه ثانياً مدة أطول من الأولى. ثم عاد إلى عالم الحس. ثم تكلف الوصول إلى مقامه بعد ذلك فكان ايسر عليه من الأولى والثانية وكان دوامه أطول. وما زال الوصول إلى ذلك المقام الكريم يزيد عليه سهولة، والدوام يزيد فيه طولاً مدة بعد مدة، حتى صار يصل إليه متى شاء، ولا ينفصل عنه إلا متى شاء؛ فكان يلازم مقامه ذلك ولا ينثني عنه إلا لضرورة بدنه التي كان قد قللها، حتى كان لا يوجد اقل منها. وهو في كل ذلك كله يريد إن يريحه الله عز وجل من كل بدنه الذي يدعوه إلى مفارقة مقامه ذلك، فيتخلص إلى لذته تخلصاً دائماً، ويبرأ عما يجده من الألم عند الأعراض عن مقامه ذلك إلى ضرورة البدن. وبقي على حالته تلك حتى أناف على سبعة أسابيع من منشئه وذلك خمسون عاماً. وحينئذ اتفقت له صحبة أسال وكان من قصته معه ما يأتي ذكره بعد هذا إن شاء الله تعالى. ذكروا: إن جزيرة قريبة من الجزيرة التي ولد بها حي بن يقظان على أحد القولين المختلفين على صفة مبدئه، انتقلت إليه ملة من الملل الصحيحة الماخوذه على بعض الأنبياء المتقدمين، صلوات الله عليهم. وكانت ملة محاكية لجميع الموجودات الحقيقية بالأمثال المضروبة التي خيالات تلك الأشياء، وتثبت رسومها في النفوس، حسبما جرت به العادة في مخاطبة الجمهور؛ فما زالت تلك الملة تنتشر بتلك الجزيرة وتقوى وتظهر، حتى قام بها ملكها وحمل الناس على التزامها. وكان قد نشأ بها فتيان من أهل الفضل والخير، يسمى أحدهما أسال والآخر سلامان فتلقيا هذه الملة وقبلاها احسن قبول، واخذ على أنفسهما على بالتزام جميع شرائعها والموظبة على جميع أعمالها، واصطحبا على ذلك. وكانا يتفقهان في بعض الأوقات فيما ورد من ألفاظ تلك الشريعة في صفة الله عز وجل وملائكته، وصفات الميعاد والثواب والعقاب. فأما أسال فكان أشد غوصاً على الباطن، وأكثر عثوراً على المعاني الروحانية واطمع في التأويل. واما سلامان صاحبه فكان أكثر احتفاظاً بالظاهر، وأشد بعداً عن التأويل، وأوقف عن التصرف والتأمل؛ وكلاهما مجد في الأعمال الظاهرة، ومحاسبة النفس، ومجاهدة الهوى. وكان في تلك الشريعة أقوال تحمل عن العزلة والانفراد، وتدل على إن الفوز والنجاة فيهما؛ واقوال أخر تحمل على المعاشرة وملازمة الجماعة. فتعلق أسال بطلب العزلة، ورجح القول فيها لما كان في طباعه من دوام الفكرة، وملازمة العبرة، والغوص على المعاني، وأكثر ما كان يتأتى له أمله من ذلك بالانفراد. وتعلق سلامان بملازمة الجماعة، ورجح القول فيها لما كان في طباعه من الجبن عن الفكرة والتصرف. فكانت ملازمته الجماعة عنده مما يدرأ الوسواس، ويزيل الظنون المعترضة ويعيد من همزات الشياطين. وكان اختلافهما في هذا الرأي سبب افتراقهما. وكان أسال قد سمع عن الجزيرة التي ذكر أن حي بن يقظان تكون بها وعرف ما بها من الخصب والمرافق والهواء المعتدل، وان الانفراد بها يتأتى لملتمسه، فأجمع إن يرتحل إليها ويعتزل الناس بها بقية عمره. فجمع ما كان له من المال، واشترى ببعضه مركباً تحمله إلى تلك الجزيرة، وفرق باقيه على المساكين، وودع صاحبه سلامان وركب متن البحر؛ فحمله الملاحون إلى تلك الجزيرة؛ ووضعوه بساحلها؛ وانفصلوا عنها. فبقي أسال بتلك الجزيرة يعبد الله عز وجل؛ ويعظمه ويقدسه؛ ويفكر في اسمائه الحسنى وصفاته العليا؛ فلا ينقطع خاطره؛ ولا تتكدر فكرته. واذا احتاج إلى غذاء تناول من ثمرات تلك الجزيرة وصيدها ما يسد بها جوعته. وأقام على تلك الحال مدة وهو في أتم غبطة وأعظم أنس بمناجاة ربه. وكان كل يوم يشاهد من ألطافه ومزايا تحفة وتيسره عليه في مطلبه وغذائه ما يثبت يقينه ويقر عينه. وكان في تلك المدة حي بن يقظان شديد الاستغراق في مقاماته الكريمة؛ فكان لا يبرح عن مغارته إلا مرة في الاسبوع لتناول ما سنح من الغذاء، فلذلك لم يعثر عليه أسال لأول وهلة، بل كان يتطوف بأكناف تلك الجزيرة ويسبح في أرجائها، فلا يرى أنسياً ولا يشاهد أثراً فيزيد بذلك أنسه وتنبسط نفسه لما كان قد عزم عليه من التناهي في طلب العزلة والانفراد. إلى إن اتفق في بعض تلك الأوقات إن خرج حي بن يقظان لالتماس غذائه وأسال قد ألم بتلك الجهة، فوقع بصر كل منهما على الآخر. فإما أسال فلم يشك أنه من العباد المنقطعين، وصل تلك الجزيرة لطلب العزلة عن الناس كما وصل هو إليها. فخشي إن هو تعرض له وتعرف به إن يكون سبباً في فساد حاله وعائقاً بينه وبين أمله. واما حي بن يقظان فلم يدر ما هو، لانه لم يره على صورة شيء من الحيوانات التي كان قد عاينها قبل ذلك. وكان عليه مدرعة سوداء من الشعر والصوف، فظن إنها لباس طبيعي. فوقف يتعجب منه ملياً. وولى أسال هارباً منه خيفة أن يشغله عن حاله، فاقتفى حي بن يقظان أثره لما كان في طباعه من البحث عن الحقائق. فلما رآه يشتد في الهرب. خنس عنه وتوارى له، حتى ظن أسال انه قد انصرف عنه وتباعد من تلك الجهة. فشرع أسال في الصلاة والقراءة، والدعاء والبكاء، والتضرع والتواجد، حتى شغله ذلك عن كل شيء. فجعل حي بن يقظان يتقرب منه قليلاً قليلاً، وأسال لا يشعر به حتى دنا منه بحيث يسمع قراءته وتسبيحه، ويشاهد خضوعه وبكائه. فسمع صوتاً حسناً وحروف منظمة، لم يعهد مثلها من شيء من أصناف الحيوان. ونظر إلى أشكاله وتخطيطه فرآه على صورته، وتبين له أن المدرعة التي عليه ليست جلداً طبيعياً، وانما هي لباس متخذ مثل لباسه هو، ولما رأى حسن خشوعه وتضرعه وبكائه لم يشك في انه من الذوات العارفة بالحق؛ فتشوق إليه واراد إن يرى ما عنده، وما الذي أوجب بكاءه وتضرعه؛ فزاد في الدنو منه حتى أحس به أسال؛ فاشتد في العدو، واشتد حي بن يقظان في أثره حتى التحق به - لما كان أعطاه الله من القوة والبسطة في العلم والجسم - فالتزمه وقبض عليه؛ ولم يمكنه من البراح. فلما نظر إليه أسال وهو مكتس بجلود الحيوان ذوات الاوبار؛ وشعره قد طال حتى جلل كثيراً منه، ورأى ما عنده من سرعة العدو وقوة البطش، فرق منه فرقاً شديداً، وجعل يستعطفه ويرغب إليه بكلام لا يفهمه حي بن يقظان ولا يدري ما هو، غير أنه يميز فيه شمائل الجزع. فكان يؤنسه بأصوات كان قد تعلمها من الحيوانات، ويجر يده على رأسه، ويمسح أعطافه. ويتملق إليه، ويظهر البشر والفرح به. حتى سكن جأش أسال وعلم أنه لا يريد به سوءاً. كان أسال قديماً لمحبته في علم التأويل. قد تعلم أكثر الألسن، ومهر فيها. فجعل يكلم حي بن يقظان ويسائله عن شأنه بكل لسان يعلمه ويعالج أفهامه فلا يستطيع، وحي بن يقظان في ذلك كله يتعجب مما يسمع ولا يدري ما هو. غير أنه يظهر له البشر والقبول. فاستغرب كل واحد منهما أمر صاحبه. وكان عند أسال من زاد كان قد اصطحبه من الجزيرة المعمورة، فقربه إلى حي بن يقظان فلم يدر ما هو، لانه لم يكن شاهده قبل ذلك. فأكل منه أسال وأشار إليه ليأكل ففكر حي بن يقظان فيما كان ألزم نفسه من الشروط لتناول الغذاء، ولم يدر اصل ذلك الشيء الذي قدم له ما هو، وهل يجوز له تناوله أم لا! فامتنع عن الآكل. ولم يزل أسال يرغب إليه ويستعطفه. وقد كان اولع به حي بن يقظان فخشي إن دام على امتناعه إن يوحشه، فاقدم على ذلك الزاد وأكل منه. فلما ذاقه واستطابه بدا له سوء ما صنع من نقض عهوده في شرط غذاء، وندم على فعله، وأراد الانفصال عن أسال والإقبال على شأنه من طلب الرجوع إلى مقامه الكريم، فلما تتأت له المشاهدة بسرعة. فرأى أن يقيم مع أسال في عالم الحس حتى يقف على حقيقة شأنه، ولا يبقي في نفسه هو نزوع إليه، وينصرف بعد ذلك إلى مقامه دون إن يشغله شاغل. فالتزم صحبة أسال ولما رأى أسال أيضاً انه لا يتكلم، آمن من غلوائه على دينه، ورجا أن يعلمه الكلام والعلم والدين، فيكون له بذلك أعظم أجر وزلفى عند الله. فشرع أسال في تعليمه الكلام أولاً بأن كان يشير له إلى أعيان الموجودات وينطق بأسمائها ويكرر ذلك عليه ويحمله على النطق، فينطق بها مقترناً بالاشارة، حتى علمه الأسماء كلها، ودرجه قليلاً قليلاً حتى تكلم في أقرب مدة. فجعل أسال يسأله عن شأنه ومن أين صار إلى تلك الجزيرة، فأعلمه حي بن يقظان انه لا يدري لنفسه ابتداء ولا أباً ولا أماً أكثر من الظبية التي ربته، ووصف له شأنه كله وكيف ترقى بالمعرفة، حتى انتهى إلى درجة الوصول. فلما سمع أسال منه وصف تلك الحقائق والذوات المفارقة لعالم الحس العارفة بذات الحق عز وجل، ووصفه ذلك الحق تعالى وجل بأوصافه الحسنى، ووصف له ما أمكنه وصفه مما شاهده عند الوصول من لذات الواصلين وألام المحجوبين، لم يشك أسال في أن جميع الأشياء التي وردت في شريعته من أمر الله عز وجل، وملائكته، وكتبه، ورسله، واليوم الآخر، وجنته وناره، هي أمثلة هذه التي شاهدها حي بن يقظان؛ فانفتح بصر قلبه وانقدحت نار خطره وتطابق عنده المعقول والمنقول، وقربت عليه طرق التأويل، ولم يبق عليه مشكل في الشرع إلا تبين له، ولا مغلق إلا انفتح، ولا غامض إلا اتضح، وصار من أولى الألباب. وعند ذلك نظر إلى حي بن يقظان بعين التعظيم والتوقير، وتحقق عنده أنه من أولياء الله الذين لا خوف عليهم ولا هم يحزنون. فالتزم خدمته والاقتداء به بإشارته فيما تعارض عنده من الأعمال الشرعية التي قد تعلمها في ملته. وجعل حي بن يقظان يستفصحه عن أمره وشأنه، فجعل أسال يصف له شأن جزيرته وما فيها من العالم، وكيف كانت سيرهم قبل وصول الملة اليهم. وكيف هي الآن بعد وصولها إليهم، وصف له جميع ما ورد في الشريعة من وصف العالم الإلهي، والجنة والنار، والبعث والنشور، والحشر والحساب، والميزان والصراط. ففهم حي بن يقظان ذلك كله ولم ير فيه شيء على خلاف ما شاهده في مقامه الكريم. فعلم أن الذي وصف ذلك وجاء به محق في وصفه، صادق في قوله، ورسول من عند ربه؛ فأمن به وصدقه وشهد برسالته. ثم جاء يسأله عما جاء به من الفرائض، ووضعه من العبادات؛ فوصف له الصلاة والزكاة، والصيام والحج، وما أشبهها من الأعمال الظاهرة؛ فتلقى ذلك والتزمه، وأخذ نفسه بأدائه امتثالاً للآمر الذي صح عنده صدق قوله. إلا انه بقي في نفسه أمران كان يتعجب منهما ولا يدري وجه الحكمة فيهما: أحدهما - لما ضرب هذا الرسول الأمثال للناس في أكثر ما وصفه من أمر العالم الإلهي، وأضرب عن المكاشفة حتى وقع الناس في أمر عظيم من التجسيم، واعتقاد أشياء في ذات الحق هو منزه عنها وبريء منها؟ وكذلك في أمر الثواب والعقاب! والآمر الآخر - لم اقتصر على هذه الفرائض ووظائف العبادات وأباح الاقتناء للأموال والتوسع في المأكل، حتى بفرغ الناس بالاشتغال بالباطل، والأعراض عن الحق؟ وكان رأيه هو لا يتناول أحد شيئاً إلا ما يقيم به من الرمق؛ واما الأموال فلم تكن لها عنده معنى. وكان يرى ما في الشرع من الأحكام في أمر الأموال: كالزكاة وتشعبها، والبيوع والربا والحدود والعقوبات، فكان يستغرب هذا كله ويراه تطويلاً، ويقول: إن الناس لو فهموا الآمر على حقيقته لاعرضوا عن هذه البواطل، وأقبلو على الحق، واستغنوا عن هذا كله، ولم يكن لاحد اختصاص بمال يسأل عن زكاته، أو تقطع الأيدي على سرقته، أو تذهب النفوس على أخذه مجاهرة. وكان الذي أوقعه في ذلك ظنه، أن الناس كلهم ذوو فطر فائقة، وأذهان ثاقبة، ونفوس عازمة، ولم يكن يدري ما هم عليه من البلادة والنقص، وسوء الرأي وضعف العزم، وأنهم كالأنعام بل هم أضل سبيلاً. فلما اشتد إشفاقه على الناس، وطمع أن تكون نجاتهم على يديه، حدثت له النية في الوصول إليهم، وإيضاح الحق لديهم، وتبييه لهم ففاوض في ذلك صاحبه أسال وسأله: هل تمكنه حيلة في الوصول اليهم؟ فأعلمه بما هم فيه من نقص الفطرة والأعراض عن آمر الله فلم يتأت له فهم ذلك، وبقي في نفسه تعلق بما كان قد أمله. وطمع أسال أيضاً أن يهدي الله على يديه طائفة من معارفه المريدين الذين كانوا أقرب من التخلص من سواهم، فساعده على رأيه، ورأيا أن يلتزما ساحل البحر ولا يفارقاه ليلاً ولا نهاراً، لعل الله إن السني لهما عبور البحر فالتزما ذلك وابتهلا الله تعالى أن يهيء لهما من أمرهما رشدأً. فكان من أمر الله عز وجل أن سفينة ضلت مسلكها، ودفعها الرياح وتلاطم الأمواج إلى ساحلها. فلما قربت من البر رأى أهلها الرجلين على الشاطئ. فدنوا منها فكلمهم أسال وسألهم أن يحملوهما معهم، فأجابوهما إلى ذلك، وأدخلوهما السفينة، فأرسل الله إليهم ريحاً رخاء حملت السفينة في أقرب مدة إلى الجزيرة التي أملاها فنزلا بها، ودخلا مدينتها، واجتمع أصحاب أسال به، فعرفهم شأن حي بن يقظان، فاشتملوا عليه شديداً وأكبروا آمره، واجتمعوا إليه واعظموه وبجلوه، وأعلمه أسال أن تلك الطائفة هم أن تلك الطائفة هم أقرب إلى الفهم والذكاء من جميع الناس، وانه إن عجز عن تعليمهم فهو عن تعليم الجمهور أعجز. وكان رأس تلك الجزيرة سلامان وهو صاحب أسال الذي كان يراه ملازمة الجماعة، ويقول بتحريم العزلة، فشرع حي بن يقظان في تعليمهم وبث أسرار الحكمة إليهم. فما هو إلا أن ترقى عن الظاهر قليلاً وأخذ في وصف ما سبق إلى فهمهم خلافه، فجعلوا ينقبضون منه وتشمئز نفوسهم مما يأتي به، ويتسخطونه بقلوبهم، وان اظهروا له الرضا في وجهه اكراماً لغربته فيهم، ومراعاة لحق صاحبهم أسال! وما زال حي بن يقظان يستلطفهم ليلاً ونهاراً، ويبن لهم الحق سراً وجهاراً، فلا يزيدهم ذلك إلا نبوأً ونفاراً، مع أنهم كانوا محبين للخير، راغبين في الحق، إلا انهم لنقص فطرتهم كانوا لا يطلبون الحق من طريقة ولا يأخذونه لجهة تحقيقه، ولا يلتمسونه من بابه، بل كانوا لا يريدون معرفته من طريق أربابه. فيأس من أصلاحهم، وانقطع رجائه من صلاحهم لقلة قبولهم. وتصفح طبقات الناس بعد ذلك، فرأى كل حزب بما لديهم فرحون، قد اتخذوا ألههم هواهم، ومعبودهم شهواتهم، وتهالكوا في جميع حطام الدنيا، ألهاهم التكاثر حتى زاروا المقابر، لا تنجح فيهم الموعظة ولا تعمل فيهم الكلمة الحسنة، ولا يزدادون بالجدل إلا إصرارا. واما الحكمة فلا سبيل لهم إليها، ولا حظ لهم منه، قد غمرتهم الجهالة وران على قلوبهم ما يكسبون ختم الله على قلوبهم وعلى سمعهم وعلى أبصارهم غشاوةً ولهم عذاب عظيم. فلما رأى سرادق العذاب قد أحاط بهم، الظالمات الحجب قد تغشتهم، والكل منهم - إلا اليسير - لا يتمسكون من ملتهم إلا بالدنيا، وقد نبذوا أعمالهم على خفتها وسهولتها وراء ظهورهم، واشتروا بها ثمناً قليلاً، وألهاهم عن ذكر الله تعالى التجارة والبيع، ولم يخافوا يوماً تنقلب فيه القلوب والابصار، لأن له وتحقق على القطع، أن مخاطبتهم بطريق المكاشفة لا تمكن وأن تكليفهم من العمل فوق هذا القدر لا يتفق، وأن حظ أكثر الجمهور من الانتفاع بالشريعة إنما هو في حياتهم الدنيا لا يستقيم له معاشه، ولا يتعدى عليه سواه فيما اختص هو به، وانه لا يفوز منه بالسعادة الأخروية إلا الشاذ النادر، وهو من أراد حرث الآخرة وسعى لها سعياً وهو مؤمن. وأما من طغى وأثر الحياة الدنيا فان الجحيم هي المأوى، وأي تعب أعظم وشقاوةً أطم ممن إذا تصفحت أعماله من وقت انتباهه من نومه إلى حين رجوعه إلى الكره لا تجد منها شيئاً إلا وهو يلتمس به تحصيل غايةً من هذه الأمور المحسوسة الخسيسة آما مال يجمعه أو لذة ينالها أو شهوة يقضيها أو غيطاً يتشفه به أو جاه يحرزه أو عمل من أعمال الشرع يتزين به أو يدافع عن رقبته، وهي كلها ظلمات بعضها فوق بعض في بحر لجي وان منكم إلا واردها كان على ربك حتماً مقضياً. فلما فهم أحوال الناس وان أكثرهم بمنزلة الحيوان غير الناطق علم أن الحكمة كلها والهداية والتوفيق فيما نطقت به الرسل ووردت به الشريعة لا يمكن غير ذلك ولا يحتمل المزيد عليه ولكل عمل رجال وكل ميسر لما خلق له "بسم الله الرحمن الرحيم" سنة الله التي قد خلت من قبل ولن تجد لسنة الله تبديلاً صدق الله العظيم. فانصرف إلى سلامان وأصحابه، فاعتذر عما تكلم به معه وتبرأ إليهم منه وأعلمهم أنه قد رآه مثل رأيهم واهتدى بمثل هديهم، وأوصاهم بملازمة ما هم عليه من التزام حدود الشرع والأعمال الظاهرة مقلة الخوض فيما لا يعنيهم، والإيمان بالمتشابهات والتسليم لها، والأعراض عن البدع والأهواء والاقتداء بالسلف الصالح والترك لمحدثات الأمور، وأمرهم بمجانبة ما عليه جمهور العوام من إهمال الشريعة والإقبال على الدنيا، وحذرهم عنه غاية التحذير، وعلم هو وصاحبه أسال أن هذه الطائفة المريدة القاصرة لا نجاة لها إلا بهذا الطريق، وأنها إن رفعت عنه إلى يفاع الاستبصار اختل ما هي عليه ولم يمكنها أن تلحق بدرجة السعداء وتذبذبت وانتكست وساءت عاقبتها. وان هي دامت على ما هي عليه حتى يوافيها اليقين فازت بالآمن وكانت من أصحاب اليمين، والسابقون السابقون أولئك المقربون. فو دعاهم وانفصلا عنهم وتلطفا في العود إلى جزيرتهما حتى يسر الله عز وجل عليهما العبور إليها. وطلب حي بن يقظان مقامه الكريم بالنحو الذي طلبه أولاً حتى عاد إليه، واقتدى به أسال حتى قرب من أو كاد وعبدا الله في تلك الجزيرة حتى أتاهما اليقين. هذا - أيدنا الله وأياك بروح منه - ما كان من نبأ حي بن يقظان وأسال وسلامان وقد أشتمل على حظ من الكلام لا يوجد في كتاب ولا يسمع في معتاد خطاب، وهو من العلم المكنون الذي لا يقبله إلا أهل المعرفة بالله، ولا يجهله إلا أهل الغرة بالله. وقد خالفنا فيه طريق السلف الصالح في الضنانا به والشح عليه. إلا أن الذي سهل علينا إفشاء هذا السر وهتك الحجاب، ما ظهر في زماننا من أراء فاسده نبغت بها متفلسفة العصر وصرحت بها، حتى انتشرت في البلدان وعما ضررها وخشينا على الضعفاء الذين اطرحوا تقليد الأنبياء صلوات الله عليهم، وأرادوا تقليد السفهاء والأغبياء أن يظنوا أن تلك الآراء هي الأسرار المضنون بها على غير أهلها، فيزيد بذلك حبهم فيها وولعهم فيها. فرأينا أن نلمح إليهم بطرف من سر الأسرار لنجتذبهم إلى جانب التحقيق، ثم نصدهم عن ذلك الطريق. ولم نخل مع ذلك ما أودعناه هذه الأوراق اليسيره من الأسرار عن حجاب رقيق وستر لطيف ينتهك سريعاً لمن هو أهله، ويتكاثف لمن لا يستحق تجاوزه حتى لا يتعداه. وأنا أسئل إخواني الواقفين على هذا الكلام، أن يقبلو عذري فيما تسائلت في تبينه وتسامحت في تثبيته، فلم أفعل ذلك إلا لأني تسمنت شواهق يزل الطرف عن مرآها. وأردت تقريب الكلام فيها على وجه الترغيب والتشويق في دخول الطريق. وأسأل الله التجاوز والعفو، وأن يوردنا من المعرفة به الصفو، إنه منعم كريم. والسلام عليك أيها الأخ المفترض إسعافه ورحمت الله وبركاته. EOT; } generator->parse($format)); } public function domainName() { return static::randomElement(static::$lastNameAscii) . '.' . $this->tld(); } } generator->parse($format); } public static function country() { return static::randomElement(static::$country); } public static function postcode() { return static::toUpper(static::bothify(static::randomElement(static::$postcode))); } public static function regionSuffix() { return static::randomElement(static::$regionSuffix); } public static function region() { return static::randomElement(static::$region); } public static function cityPrefix() { return static::randomElement(static::$cityPrefix); } public function city() { return static::randomElement(static::$city); } public static function streetPrefix() { return static::randomElement(static::$streetPrefix); } public static function street() { return static::randomElement(static::$street); } } bothify("??######"); } public function passportNumber() { return $this->bothify("??#######"); } public function personalIdentityNumber(\DateTime $birthdate = null) { if (!$birthdate) { $birthdate = \Faker\Provider\DateTime::dateTimeThisCentury(); } $datePart = $birthdate->format('dmy'); $randomDigits = (string) static::numerify('####'); $checksum = Luhn::computeCheckDigit($datePart . $randomDigits); return $datePart . '-' . $randomDigits . $checksum; } } lastName().'dóttir'; } public function lastNameFemale() { return $this->lastName().'son'; } public static function ssn() { $birthdate = new \DateTime('@' . mt_rand(0, time())); $lastFour = null; $ref = '32765432'; $valid = false; while (! $valid) { $rand = static::randomDigit().static::randomDigit(); $tmp = $birthdate->format('dmy').$rand; for ($i = 7, $sum = 0; $i >= 0; $i--) { $sum += ($tmp[$i] * $ref[$i]); } $chk = ($sum % 11 === 0) ? 0 : (11 - ($sum % 11)); if ($chk < 10) { $lastFour = $rand.$chk.substr($birthdate->format('Y'), 1, 1); $valid = true; } } return sprintf('%s-%s', $birthdate->format('dmy'), $lastFour); } } generator->parse($format); } public static function country() { return static::randomElement(static::$country); } public static function postcode() { return static::toUpper(static::bothify(static::randomElement(static::$postcode))); } public static function regionSuffix() { return static::randomElement(static::$regionSuffix); } public static function region() { return static::randomElement(static::$region); } public static function cityPrefix() { return static::randomElement(static::$cityPrefix); } public function city() { return static::randomElement(static::$city); } public function streetPrefix() { return static::randomElement(static::$streetPrefix); } public static function street() { return static::randomElement(static::$street); } } generator->parse($format); } public function streetName() { $format = static::randomElement(static::$streetNameFormats); return $this->generator->parse($format); } public function streetAddress() { $format = static::randomElement(static::$streetAddressFormats); return $this->generator->parse($format); } public static function postcode() { return static::toUpper(static::bothify(static::randomElement(static::$postcode))); } public function address() { $format = static::randomElement(static::$addressFormats); return $this->generator->parse($format); } public static function country() { return static::randomElement(static::$country); } public static function latitude() { return static::randomFloat(6, 0, 180) - 90; } public static function longitude() { return static::randomFloat(6, 0, 360) - 180; } } format('ymd'); if ($gender && $gender == static::GENDER_MALE) { $randomDigits = (string)static::numerify('##') . static::randomElement(array(1,3,5,7,9)); } elseif ($gender && $gender == static::GENDER_FEMALE) { $randomDigits = (string)static::numerify('##') . static::randomElement(array(0,2,4,6,8)); } else { $randomDigits = (string)static::numerify('###'); } $checksum = Luhn::computeCheckDigit($datePart . $randomDigits); return $datePart . '-' . $randomDigits . $checksum; } } 0; $i--) { $numbers[$i] = substr($number, $i - 1, 1); $partial[$i] = $numbers[$i] * $factor; $sum += $partial[$i]; if ($factor == $base) { $factor = 1; } $factor++; } $res = $sum % 11; if ($res == 0 || $res == 1) { $digit = 0; } else { $digit = 11 - $res; } return $digit; } protected static $firstNameMale = array( 'Rodrigo', 'João', 'Martim', 'Afonso', 'Tomás', 'Gonçalo', 'Francisco', 'Tiago', 'Diogo', 'Guilherme', 'Pedro', 'Miguel', 'Rafael', 'Gabriel', 'Santiago', 'Dinis', 'David', 'Duarte', 'José', 'Simão', 'Daniel', 'Lucas', 'Gustavo', 'André', 'Denis', 'Salvador', 'António', 'Vasco', 'Henrique', 'Lourenço', 'Manuel', 'Eduardo', 'Bernardo', 'Leandro', 'Luís', 'Diego', 'Leonardo', 'Alexandre', 'Rúben', 'Mateus', 'Ricardo', 'Vicente', 'Filipe', 'Bruno', 'Nuno', 'Carlos', 'Rui', 'Hugo', 'Samuel', 'Álvaro', 'Matias', 'Fábio', 'Ivo', 'Paulo', 'Jorge', 'Xavier', 'Marco', 'Isaac', 'Raúl','Benjamim', 'Renato', 'Artur', 'Mário', 'Frederico', 'Cristiano', 'Ivan', 'Sérgio', 'Micael', 'Vítor', 'Edgar', 'Kevin', 'Joaquim', 'Igor', 'Ângelo', 'Enzo', 'Valentim', 'Flávio', 'Joel', 'Fernando', 'Sebastião', 'Tomé', 'César', 'Cláudio', 'Nelson', 'Lisandro', 'Jaime', 'Gil', 'Mauro', 'Sandro', 'Hélder', 'Matheus', 'William', 'Gaspar', 'Márcio', 'Martinho', 'Emanuel', 'Marcos', 'Telmo', 'Davi', 'Wilson' ); protected static $firstNameFemale = array( 'Maria', 'Leonor', 'Matilde', 'Mariana', 'Ana', 'Beatriz', 'Inês', 'Lara', 'Carolina', 'Margarida', 'Joana', 'Sofia', 'Diana', 'Francisca', 'Laura', 'Sara', 'Madalena', 'Rita', 'Mafalda', 'Catarina', 'Luana', 'Marta', 'Íris', 'Alice', 'Bianca', 'Constança', 'Gabriela', 'Eva', 'Clara', 'Bruna', 'Daniela', 'Iara', 'Filipa', 'Vitória', 'Ariana', 'Letícia', 'Bárbara', 'Camila', 'Rafaela', 'Carlota', 'Yara', 'Núria', 'Raquel', 'Ema', 'Helena', 'Benedita', 'Érica', 'Isabel', 'Nicole', 'Lia', 'Alícia', 'Mara', 'Jéssica', 'Soraia', 'Júlia', 'Luna', 'Victória', 'Luísa', 'Teresa', 'Miriam', 'Adriana', 'Melissa', 'Andreia', 'Juliana', 'Alexandra', 'Yasmin', 'Tatiana', 'Leticia', 'Luciana', 'Eduarda', 'Cláudia', 'Débora', 'Fabiana', 'Renata', 'Kyara', 'Kelly', 'Irina', 'Mélanie', 'Nádia', 'Cristiana', 'Liliana', 'Patrícia', 'Vera', 'Doriana', 'Ângela', 'Mia', 'Erica', 'Mónica', 'Isabela', 'Salomé', 'Cátia', 'Verónica', 'Violeta', 'Lorena', 'Érika', 'Vanessa', 'Iris', 'Anna', 'Viviane', 'Rebeca', 'Neuza', ); protected static $lastName = array( 'Abreu', 'Almeida', 'Alves', 'Amaral', 'Amorim', 'Andrade', 'Anjos', 'Antunes', 'Araújo', 'Assunção', 'Azevedo', 'Baptista', 'Barbosa', 'Barros', 'Batista', 'Borges', 'Branco', 'Brito', 'Campos', 'Cardoso', 'Carneiro', 'Carvalho', 'Castro', 'Coelho', 'Correia', 'Costa', 'Cruz', 'Cunha', 'Domingues', 'Esteves', 'Faria', 'Fernandes', 'Ferreira', 'Figueiredo', 'Fonseca', 'Freitas', 'Garcia', 'Gaspar', 'Gomes', 'Gonçalves', 'Guerreiro', 'Henriques', 'Jesus', 'Leal', 'Leite', 'Lima', 'Lopes', 'Loureiro', 'Lourenço', 'Macedo', 'Machado', 'Magalhães', 'Maia', 'Marques', 'Martins', 'Matias', 'Matos', 'Melo', 'Mendes', 'Miranda', 'Monteiro', 'Morais', 'Moreira', 'Mota', 'Moura', 'Nascimento', 'Neto', 'Neves', 'Nogueira', 'Nunes', 'Oliveira', 'Pacheco', 'Paiva', 'Pereira', 'Pinheiro', 'Pinho', 'Pinto', 'Pires', 'Ramos', 'Reis', 'Ribeiro', 'Rocha', 'Rodrigues', 'Santos', 'Silva', 'Simões', 'Soares', 'Sousa', 'Sá', 'Tavares', 'Teixeira', 'Torres', 'Valente', 'Vaz', 'Vicente', 'Vieira', ); } 5) { throw new \InvalidArgumentException('indexSize must be at most 5'); } $words = $this->getConsecutiveWords($indexSize); $result = array(); $resultLength = 0; $next = static::randomKey($words); while ($resultLength < $maxNbChars && isset($words[$next])) { $word = static::randomElement($words[$next]); $currentWords = static::explode($next); $currentWords[] = $word; array_shift($currentWords); $next = static::implode($currentWords); if ($resultLength == 0 && !static::validStart($word)) { continue; } $result[] = $word; $resultLength += static::strlen($word) + static::$separatorLen; } array_pop($result); $result = static::implode($result); return static::appendEnd($result); } protected function getConsecutiveWords($indexSize) { if (!isset($this->consecutiveWords[$indexSize])) { $parts = $this->getExplodedText(); $words = array(); $index = array(); for ($i = 0; $i < $indexSize; $i++) { $index[] = array_shift($parts); } for ($i = 0, $count = count($parts); $i < $count; $i++) { $stringIndex = static::implode($index); if (!isset($words[$stringIndex])) { $words[$stringIndex] = array(); } $word = $parts[$i]; $words[$stringIndex][] = $word; array_shift($index); $index[] = $word; } $this->consecutiveWords[$indexSize] = $words; } return $this->consecutiveWords[$indexSize]; } protected function getExplodedText() { if ($this->explodedText === null) { $this->explodedText = static::explode(preg_replace('/\s+/u', ' ', static::$baseText)); } return $this->explodedText; } protected static function explode($text) { return explode(static::$separator, $text); } protected static function implode($words) { return implode(static::$separator, $words); } protected static function strlen($text) { return function_exists('mb_strlen') ? mb_strlen($text, 'UTF-8') : strlen($text); } protected static function validStart($word) { return preg_match('/^\p{Lu}/u', $word); } protected static function appendEnd($text) { return $text.'.'; } } generator->parse($format); } public static function country() { return static::randomElement(static::$country); } public static function postcode() { return static::toUpper(static::bothify(static::randomElement(static::$postcode))); } public static function regionSuffix() { return static::randomElement(static::$regionSuffix); } public static function region() { return static::randomElement(static::$region); } public static function cityPrefix() { return static::randomElement(static::$cityPrefix); } public function city() { return static::randomElement(static::$city); } public static function streetPrefix() { return static::randomElement(static::$streetPrefix); } public static function street() { return static::randomElement(static::$street); } } generator->parse($format); } public static function companyPrefix() { return static::randomElement(static::$companyPrefixes); } public static function companyNameElement() { return static::randomElement(static::$companyElements); } public static function companyNameSuffix() { return static::randomElement(static::$companyNameSuffixes); } } format('dmy'); $randomDigits = (string)static::numerify('##'); switch($gender) { case static::GENDER_MALE: $genderDigit = static::randomElement(array(1,3,5,7,9)); break; case static::GENDER_FEMALE: $genderDigit = static::randomElement(array(0,2,4,6,8)); break; default: $genderDigit = (string)static::numerify('#'); } $digits = $datePart.$randomDigits.$genderDigit; $checksum = (string)static::numerify('##'); return $digits.$checksum; } } 'I','Ö' => 'O','Œ' => 'O','Ü' => 'U','ä' => 'a','æ' => 'a', 'ij' => 'i','ö' => 'o','œ' => 'o','ü' => 'u','ß' => 's','ſ' => 's', 'À' => 'A','Á' => 'A','Â' => 'A','Ã' => 'A','Ä' => 'A','Å' => 'A', 'Æ' => 'A','Ā' => 'A','Ą' => 'A','Ă' => 'A','Ç' => 'C','Ć' => 'C', 'Č' => 'C','Ĉ' => 'C','Ċ' => 'C','Ď' => 'D','Đ' => 'D','È' => 'E', 'É' => 'E','Ê' => 'E','Ë' => 'E','Ē' => 'E','Ę' => 'E','Ě' => 'E', 'Ĕ' => 'E','Ė' => 'E','Ĝ' => 'G','Ğ' => 'G','Ġ' => 'G','Ģ' => 'G', 'Ĥ' => 'H','Ħ' => 'H','Ì' => 'I','Í' => 'I','Î' => 'I','Ï' => 'I', 'Ī' => 'I','Ĩ' => 'I','Ĭ' => 'I','Į' => 'I','İ' => 'I','Ĵ' => 'J', 'Ķ' => 'K','Ľ' => 'K','Ĺ' => 'K','Ļ' => 'K','Ŀ' => 'K','Ł' => 'L', 'Ñ' => 'N','Ń' => 'N','Ň' => 'N','Ņ' => 'N','Ŋ' => 'N','Ò' => 'O', 'Ó' => 'O','Ô' => 'O','Õ' => 'O','Ø' => 'O','Ō' => 'O','Ő' => 'O', 'Ŏ' => 'O','Ŕ' => 'R','Ř' => 'R','Ŗ' => 'R','Ś' => 'S','Ş' => 'S', 'Ŝ' => 'S','Ș' => 'S','Š' => 'S','Ť' => 'T','Ţ' => 'T','Ŧ' => 'T', 'Ț' => 'T','Ù' => 'U','Ú' => 'U','Û' => 'U','Ū' => 'U','Ů' => 'U', 'Ű' => 'U','Ŭ' => 'U','Ũ' => 'U','Ų' => 'U','Ŵ' => 'W','Ŷ' => 'Y', 'Ÿ' => 'Y','Ý' => 'Y','Ź' => 'Z','Ż' => 'Z','Ž' => 'Z','à' => 'a', 'á' => 'a','â' => 'a','ã' => 'a','ā' => 'a','ą' => 'a','ă' => 'a', 'å' => 'a','ç' => 'c','ć' => 'c','č' => 'c','ĉ' => 'c','ċ' => 'c', 'ď' => 'd','đ' => 'd','è' => 'e','é' => 'e','ê' => 'e','ë' => 'e', 'ē' => 'e','ę' => 'e','ě' => 'e','ĕ' => 'e','ė' => 'e','ƒ' => 'f', 'ĝ' => 'g','ğ' => 'g','ġ' => 'g','ģ' => 'g','ĥ' => 'h','ħ' => 'h', 'ì' => 'i','í' => 'i','î' => 'i','ï' => 'i','ī' => 'i','ĩ' => 'i', 'ĭ' => 'i','į' => 'i','ı' => 'i','ĵ' => 'j','ķ' => 'k','ĸ' => 'k', 'ł' => 'l','ľ' => 'l','ĺ' => 'l','ļ' => 'l','ŀ' => 'l','ñ' => 'n', 'ń' => 'n','ň' => 'n','ņ' => 'n','ʼn' => 'n','ŋ' => 'n','ò' => 'o', 'ó' => 'o','ô' => 'o','õ' => 'o','ø' => 'o','ō' => 'o','ő' => 'o', 'ŏ' => 'o','ŕ' => 'r','ř' => 'r','ŗ' => 'r','ś' => 's','š' => 's', 'ť' => 't','ù' => 'u','ú' => 'u','û' => 'u','ū' => 'u','ů' => 'u', 'ű' => 'u','ŭ' => 'u','ũ' => 'u','ų' => 'u','ŵ' => 'w','ÿ' => 'y', 'ý' => 'y','ŷ' => 'y','ż' => 'z','ź' => 'z','ž' => 'z','Α' => 'A', 'Ά' => 'A','Ἀ' => 'A','Ἁ' => 'A','Ἂ' => 'A','Ἃ' => 'A','Ἄ' => 'A', 'Ἅ' => 'A','Ἆ' => 'A','Ἇ' => 'A','ᾈ' => 'A','ᾉ' => 'A','ᾊ' => 'A', 'ᾋ' => 'A','ᾌ' => 'A','ᾍ' => 'A','ᾎ' => 'A','ᾏ' => 'A','Ᾰ' => 'A', 'Ᾱ' => 'A','Ὰ' => 'A','ᾼ' => 'A','Β' => 'B','Γ' => 'G','Δ' => 'D', 'Ε' => 'E','Έ' => 'E','Ἐ' => 'E','Ἑ' => 'E','Ἒ' => 'E','Ἓ' => 'E', 'Ἔ' => 'E','Ἕ' => 'E','Ὲ' => 'E','Ζ' => 'Z','Η' => 'I','Ή' => 'I', 'Ἠ' => 'I','Ἡ' => 'I','Ἢ' => 'I','Ἣ' => 'I','Ἤ' => 'I','Ἥ' => 'I', 'Ἦ' => 'I','Ἧ' => 'I','ᾘ' => 'I','ᾙ' => 'I','ᾚ' => 'I','ᾛ' => 'I', 'ᾜ' => 'I','ᾝ' => 'I','ᾞ' => 'I','ᾟ' => 'I','Ὴ' => 'I','ῌ' => 'I', 'Θ' => 'T','Ι' => 'I','Ί' => 'I','Ϊ' => 'I','Ἰ' => 'I','Ἱ' => 'I', 'Ἲ' => 'I','Ἳ' => 'I','Ἴ' => 'I','Ἵ' => 'I','Ἶ' => 'I','Ἷ' => 'I', 'Ῐ' => 'I','Ῑ' => 'I','Ὶ' => 'I','Κ' => 'K','Λ' => 'L','Μ' => 'M', 'Ν' => 'N','Ξ' => 'K','Ο' => 'O','Ό' => 'O','Ὀ' => 'O','Ὁ' => 'O', 'Ὂ' => 'O','Ὃ' => 'O','Ὄ' => 'O','Ὅ' => 'O','Ὸ' => 'O','Π' => 'P', 'Ρ' => 'R','Ῥ' => 'R','Σ' => 'S','Τ' => 'T','Υ' => 'Y','Ύ' => 'Y', 'Ϋ' => 'Y','Ὑ' => 'Y','Ὓ' => 'Y','Ὕ' => 'Y','Ὗ' => 'Y','Ῠ' => 'Y', 'Ῡ' => 'Y','Ὺ' => 'Y','Φ' => 'F','Χ' => 'X','Ψ' => 'P','Ω' => 'O', 'Ώ' => 'O','Ὠ' => 'O','Ὡ' => 'O','Ὢ' => 'O','Ὣ' => 'O','Ὤ' => 'O', 'Ὥ' => 'O','Ὦ' => 'O','Ὧ' => 'O','ᾨ' => 'O','ᾩ' => 'O','ᾪ' => 'O', 'ᾫ' => 'O','ᾬ' => 'O','ᾭ' => 'O','ᾮ' => 'O','ᾯ' => 'O','Ὼ' => 'O', 'ῼ' => 'O','α' => 'a','ά' => 'a','ἀ' => 'a','ἁ' => 'a','ἂ' => 'a', 'ἃ' => 'a','ἄ' => 'a','ἅ' => 'a','ἆ' => 'a','ἇ' => 'a','ᾀ' => 'a', 'ᾁ' => 'a','ᾂ' => 'a','ᾃ' => 'a','ᾄ' => 'a','ᾅ' => 'a','ᾆ' => 'a', 'ᾇ' => 'a','ὰ' => 'a','ᾰ' => 'a','ᾱ' => 'a','ᾲ' => 'a','ᾳ' => 'a', 'ᾴ' => 'a','ᾶ' => 'a','ᾷ' => 'a','β' => 'b','γ' => 'g','δ' => 'd', 'ε' => 'e','έ' => 'e','ἐ' => 'e','ἑ' => 'e','ἒ' => 'e','ἓ' => 'e', 'ἔ' => 'e','ἕ' => 'e','ὲ' => 'e','ζ' => 'z','η' => 'i','ή' => 'i', 'ἠ' => 'i','ἡ' => 'i','ἢ' => 'i','ἣ' => 'i','ἤ' => 'i','ἥ' => 'i', 'ἦ' => 'i','ἧ' => 'i','ᾐ' => 'i','ᾑ' => 'i','ᾒ' => 'i','ᾓ' => 'i', 'ᾔ' => 'i','ᾕ' => 'i','ᾖ' => 'i','ᾗ' => 'i','ὴ' => 'i','ῂ' => 'i', 'ῃ' => 'i','ῄ' => 'i','ῆ' => 'i','ῇ' => 'i','θ' => 't','ι' => 'i', 'ί' => 'i','ϊ' => 'i','ΐ' => 'i','ἰ' => 'i','ἱ' => 'i','ἲ' => 'i', 'ἳ' => 'i','ἴ' => 'i','ἵ' => 'i','ἶ' => 'i','ἷ' => 'i','ὶ' => 'i', 'ῐ' => 'i','ῑ' => 'i','ῒ' => 'i','ῖ' => 'i','ῗ' => 'i','κ' => 'k', 'λ' => 'l','μ' => 'm','ν' => 'n','ξ' => 'k','ο' => 'o','ό' => 'o', 'ὀ' => 'o','ὁ' => 'o','ὂ' => 'o','ὃ' => 'o','ὄ' => 'o','ὅ' => 'o', 'ὸ' => 'o','π' => 'p','ρ' => 'r','ῤ' => 'r','ῥ' => 'r','σ' => 's', 'ς' => 's','τ' => 't','υ' => 'y','ύ' => 'y','ϋ' => 'y','ΰ' => 'y', 'ὐ' => 'y','ὑ' => 'y','ὒ' => 'y','ὓ' => 'y','ὔ' => 'y','ὕ' => 'y', 'ὖ' => 'y','ὗ' => 'y','ὺ' => 'y','ῠ' => 'y','ῡ' => 'y','ῢ' => 'y', 'ῦ' => 'y','ῧ' => 'y','φ' => 'f','χ' => 'x','ψ' => 'p','ω' => 'o', 'ώ' => 'o','ὠ' => 'o','ὡ' => 'o','ὢ' => 'o','ὣ' => 'o','ὤ' => 'o', 'ὥ' => 'o','ὦ' => 'o','ὧ' => 'o','ᾠ' => 'o','ᾡ' => 'o','ᾢ' => 'o', 'ᾣ' => 'o','ᾤ' => 'o','ᾥ' => 'o','ᾦ' => 'o','ᾧ' => 'o','ὼ' => 'o', 'ῲ' => 'o','ῳ' => 'o','ῴ' => 'o','ῶ' => 'o','ῷ' => 'o','А' => 'A', 'Б' => 'B','В' => 'V','Г' => 'G','Д' => 'D','Е' => 'E','Ё' => 'E', 'Ж' => 'Z','З' => 'Z','И' => 'I','Й' => 'I','К' => 'K','Л' => 'L', 'М' => 'M','Н' => 'N','О' => 'O','П' => 'P','Р' => 'R','С' => 'S', 'Т' => 'T','У' => 'U','Ф' => 'F','Х' => 'K','Ц' => 'T','Ч' => 'C', 'Ш' => 'S','Щ' => 'S','Ы' => 'Y','Э' => 'E','Ю' => 'Y','Я' => 'Y', 'а' => 'A','б' => 'B','в' => 'V','г' => 'G','д' => 'D','е' => 'E', 'ё' => 'E','ж' => 'Z','з' => 'Z','и' => 'I','й' => 'I','к' => 'K', 'л' => 'L','м' => 'M','н' => 'N','о' => 'O','п' => 'P','р' => 'R', 'с' => 'S','т' => 'T','у' => 'U','ф' => 'F','х' => 'K','ц' => 'T', 'ч' => 'C','ш' => 'S','щ' => 'S','ы' => 'Y','э' => 'E','ю' => 'Y', 'я' => 'Y','ð' => 'd','Ð' => 'D','þ' => 't','Þ' => 'T','ა' => 'a', 'ბ' => 'b','გ' => 'g','დ' => 'd','ე' => 'e','ვ' => 'v','ზ' => 'z', 'თ' => 't','ი' => 'i','კ' => 'k','ლ' => 'l','მ' => 'm','ნ' => 'n', 'ო' => 'o','პ' => 'p','ჟ' => 'z','რ' => 'r','ს' => 's','ტ' => 't', 'უ' => 'u','ფ' => 'p','ქ' => 'k','ღ' => 'g','ყ' => 'q','შ' => 's', 'ჩ' => 'c','ც' => 't','ძ' => 'd','წ' => 't','ჭ' => 'c','ხ' => 'k', 'ჯ' => 'j','ჰ' => 'h','ā' => 'a','ţ' => 't','ʼ' => "'", '̧' => '', 'ḩ' => 'h','ʼ' => "'",'‘' => "'",'’' => "'",'ừ' => 'u','/' => '', 'ế' => 'e','ả' => 'a','ị' => 'i','ậ' => 'a','ệ' => 'e','ỉ' => 'i', 'ồ' => 'o','ề' => 'e','ơ' => 'o','ạ' => 'a','ẵ' => 'a','ư' => 'u', 'ằ' => 'a','ầ' => 'a','ḑ' => 'd','Ḩ' => 'H','Ḑ' => 'D','ḑ' => 'd', 'Ģ' => 'G','Š' => 'S','ļ' => 'l','ž' => 'z','Ē' => 'E','ņ' => 'n', 'Č' => 'C','ș' => 's','ț' => 't', 'ộ' => 'o','ắ' => 'a','ş' => 's', "'" => '', 'ու' => 'u','ա' => 'a','բ' => 'b','գ' => 'g','դ' => 'd', 'ե' => 'e','զ' => 'z','է' => 'e','ը' => 'y','թ' => 't','ժ' => 'zh', 'ի' => 'i','լ' => 'l','խ' => 'kh','ծ' => 'ts','կ' => 'k','հ' => 'h', 'ձ' => 'dz','ղ' => 'gh','ճ' => 'ch','մ' => 'm','յ' => 'y','ն' => 'n', 'շ' => 'sh','ո' => 'o','չ' => 'ch','պ' => 'p','ջ' => 'j','ռ' => 'r', 'ս' => 's','վ' => 'v','տ' => 't','ր' => 'r','ց' => 'ts','փ' => 'p', 'ք' => 'q','և' => 'ev','օ' => 'o','ֆ' => 'f', ); return str_replace(array_keys($transliterationTable), array_values($transliterationTable), $string); } private static function transliterate($string) { $transId = 'Any-Latin; Latin-ASCII; NFD; [:Nonspacing Mark:] Remove; NFC; Lower();'; if (function_exists('transliterator_transliterate') && $transliterator = \Transliterator::create($transId)) { $transString = $transliterator->transliterate($string); } else { $transString = static::toAscii($string); } return preg_replace('/[^A-Za-z0-9_.]/u', '', $transString); } public function email() { $format = static::randomElement(static::$emailFormats); return $this->generator->parse($format); } final public function safeEmail() { return preg_replace('/\s/u', '', $this->userName() . '@' . static::safeEmailDomain()); } public function freeEmail() { return preg_replace('/\s/u', '', $this->userName() . '@' . static::freeEmailDomain()); } public function companyEmail() { return preg_replace('/\s/u', '', $this->userName() . '@' . $this->domainName()); } public static function freeEmailDomain() { return static::randomElement(static::$freeEmailDomain); } final public static function safeEmailDomain() { $domains = array( 'example.com', 'example.org', 'example.net' ); return static::randomElement($domains); } public function userName() { $format = static::randomElement(static::$userNameFormats); $username = static::bothify($this->generator->parse($format)); return static::transliterate($username); } public function password($minLength = 6, $maxLength = 20) { $pattern = str_repeat('*', $this->numberBetween($minLength, $maxLength)); return $this->asciify($pattern); } public function domainName() { return $this->domainWord() . '.' . $this->tld(); } public function domainWord() { $lastName = $this->generator->format('lastName'); return static::transliterate($lastName); } public function tld() { return static::randomElement(static::$tld); } public function url() { $format = static::randomElement(static::$urlFormats); return $this->generator->parse($format); } public function slug($nbWords = 6, $variableNbWords = true) { if ($nbWords <= 0) { return ''; } if ($variableNbWords) { $nbWords = (int) ($nbWords * mt_rand(60, 140) / 100) + 1; } $words = $this->generator->words($nbWords); return join($words, '-'); } public function ipv4() { return long2ip(mt_rand(0, 1) == 0 ? mt_rand(-2147483648, 0) : mt_rand(1, 2147483647)); } public function ipv6() { $res = array(); for ($i=0; $i < 8; $i++) { $res []= dechex(mt_rand(0, "65535")); } return join(':', $res); } public static function localIpv4() { if (static::numberBetween(0, 1) === 0) { $ip = long2ip(static::numberBetween(167772160, 184549375)); } else { $ip = long2ip(static::numberBetween(3232235520, 3232301055)); } return $ip; } public static function macAddress() { for ($i=0; $i<6; $i++) { $mac[] = sprintf('%02X', static::numberBetween(0, 0xff)); } $mac = implode(':', $mac); return $mac; } } generator->parse(static::randomElement(static::$lastNameFormat)); } public static function lastNameMale() { return static::randomElement(static::$lastNameMale); } public static function lastNameFemale() { return static::randomElement(static::$lastNameFemale); } public function title($gender = null) { return static::randomElement(static::$title); } public static function titleMale() { return static::title(); } public static function titleFemale() { return static::title(); } public static function pesel($birthdate = null, $sex = null) { if ($birthdate === null) { $birthdate = \Faker\Provider\DateTime::dateTimeThisCentury(); } $weights = array(1, 3, 7, 9, 1, 3, 7, 9, 1, 3); $length = count($weights); $fullYear = (int) $birthdate->format('Y'); $year = (int) $birthdate->format('y'); $month = $birthdate->format('m') + (((int) ($fullYear/100) - 14) % 5) * 20; $day = $birthdate->format('d'); $result = array((int) ($year / 10), $year % 10, (int) ($month / 10), $month % 10, (int) ($day / 10), $day % 10); for ($i = 6; $i < $length; $i++) { $result[$i] = static::randomDigit(); } if ($sex == "M") { $result[$length - 1] |= 1; } elseif ($sex == "F") { $result[$length - 1] ^= 1; } $checksum = 0; for ($i = 0; $i < $length; $i++) { $checksum += $weights[$i] * $result[$i]; } $checksum = (10 - ($checksum % 10)) % 10; $result[] = $checksum; return implode('', $result); } public static function personalIdentityNumber() { $range = str_split("ABCDEFGHIJKLMNPRSTUVWXYZ"); $low = array("A", static::randomElement($range), static::randomElement($range)); $high = array(static::randomDigit(), static::randomDigit(), static::randomDigit(), static::randomDigit(), static::randomDigit()); $weights = array(7, 3, 1, 7, 3, 1, 7, 3); $checksum = 0; for ($i = 0, $size = count($low); $i < $size; $i++) { $checksum += $weights[$i] * (ord($low[$i]) - 55); } for ($i = 0, $size = count($high); $i < $size; $i++) { $checksum += $weights[$i+3] * $high[$i]; } $checksum %= 10; return implode('', $low).$checksum.implode('', $high); } public static function taxpayerIdentificationNumber() { $weights = array(6, 5, 7, 2, 3, 4, 5, 6, 7); $result = array(); do { $result = array( static::randomDigitNotNull(), static::randomDigitNotNull(), static::randomDigitNotNull(), static::randomDigit(), static::randomDigit(), static::randomDigit(), static::randomDigit(), static::randomDigit(), static::randomDigit(), ); $checksum = 0; for ($i = 0, $size = count($result); $i < $size; $i++) { $checksum += $weights[$i] * $result[$i]; } $checksum %= 11; } while ($checksum == 10); $result[] = $checksum; return implode('', $result); } } 'Aareal Bank Aktiengesellschaft (Spółka Akcyjna) - Oddział w Polsce', '249' => 'Alior Bank SA', '247' => 'Banco Espirito Santo de Investimento, S.A. Spółka Akcyjna Oddział w Polsce', '238' => 'Banco Mais S.A. (SA) Oddział w Polsce', '106' => 'Bank BPH SA', '219' => 'Bank DnB NORD Polska SA', '203' => 'Bank Gospodarki Żywnościowej SA', '113' => 'Bank Gospodarstwa Krajowego', '122' => 'Bank Handlowo - Kredytowy SA (w likwidacji 31.03.92)', '103' => 'Bank Handlowy w Warszawie SA', '116' => 'Bank Millennium SA', '154' => 'Bank Ochrony Środowiska SA', '260' => 'Bank of China (Luxembourg)S.A. Spółka Akcyjna Oddział w Polsce', '221' => 'Bank of Tokyo-Mitsubishi UFJ (Polska) SA', '132' => 'Bank Pocztowy SA', '124' => 'Bank Polska Kasa Opieki SA', '193' => 'BANK POLSKIEJ SPÓŁDZIELCZOŚCI SA', '109' => 'Bank Zachodni WBK SA', '224' => 'Banque PSA Finance SA Oddział w Polsce', '160' => 'BNP PARIBAS BANK POLSKA SA', '235' => 'BNP PARIBAS SA Oddział w Polsce', '243' => 'BNP Paribas Securities Services SKAOddział w Polsce', '229' => 'BPI Bank Polskich Inwestycji SA', '215' => 'BRE Bank Hipoteczny SA', '114' => 'BRE Bank SA', '239' => 'CAIXABANK, S.A. (SPÓŁKA AKCYJNA)ODDZIAŁ W POLSCE', '254' => 'Citibank Europe plc (Publiczna Spółka Akcyjna) Oddział w Polsce', '194' => 'Credit Agricole Bank Polska SA', '252' => 'CREDIT SUISSE (LUXEMBOURG) S.A. Spółka Akcyjna, Oddział w Polsce', '236' => 'Danske Bank A/S SA Oddział w Polsce', '191' => 'Deutsche Bank PBC SA', '188' => 'Deutsche Bank Polska SA', '174' => 'DZ BANK Polska SA', '241' => 'Elavon Financial Services Limited (Spółka z ograniczoną odpowiedzialnością) Oddział w Polsce', '147' => 'Euro Bank SA', '265' => 'EUROCLEAR Bank SA/NV (Spółka Akcyjna) - Oddział w Polsce', '207' => 'FCE Bank Polska SA', '214' => 'Fiat Bank Polska SA', '253' => 'FM Bank SA', '248' => 'Getin Noble Bank SA', '128' => 'HSBC Bank Polska SA', '195' => 'Idea Bank SA', '255' => 'Ikano Bank GmbH (Sp. z o.o.) Oddział w Polsce', '262' => 'Industrial and Commercial Bank of China (Europe) S.A. (Spółka Akcyjna) Oddział w Polsce', '105' => 'ING Bank Śląski SA', '266' => 'Intesa Sanpaolo S.p.A. Spółka Akcyjna Oddział w Polsce', '168' => 'INVEST - BANK SA', '258' => 'J.P. Morgan Europe Limited Sp. z o.o. Oddział w Polsce', '158' => 'Mercedes-Benz Bank Polska SA', '130' => 'Meritum Bank ICB SA', '101' => 'Narodowy Bank Polski', '256' => 'Nordea Bank AB SA Oddział w Polsce', '144' => 'NORDEA BANK POLSKA SA', '232' => 'Nykredit Realkredit A/S SA - Oddział w Polsce', '189' => 'Pekao Bank Hipoteczny SA', '187' => 'Polski Bank Przedsiębiorczości SA', '102' => 'Powszechna Kasa Oszczędności Bank Polski SA', '200' => 'Rabobank Polska SA', '175' => 'Raiffeisen Bank Polska SA', '167' => 'RBS Bank (Polska) SA', '264' => 'RCI Banque Spółka Akcyjna Oddział w Polsce', '212' => 'Santander Consumer Bank SA', '263' => 'Saxo Bank A/S Spółka Akcyjna Oddział w Polsce', '161' => 'SGB-Bank SA', '237' => 'Skandinaviska Enskilda Banken AB (SA) - Oddział w Polsce', '184' => 'Societe Generale SA Oddział w Polsce', '225' => 'Svenska Handelsbanken AB SA Oddział w Polsce', '227' => 'Sygma Banque Societe Anonyme (SA) Oddział w Polsce', '216' => 'Toyota Bank Polska SA', '257' => 'UBS Limited (spółka z ograniczoną odpowiedzialnością) Oddział w Polsce', '261' => 'Vanquis Bank Limited (spółka z ograniczoną odpowiedzialnością) Oddział w Polsce', '213' => 'VOLKSWAGEN BANK POLSKA SA', ); public static function bank() { return static::randomElement(static::$banks); } public static function bankAccountNumber($prefix = '', $countryCode = 'PL', $length = null) { return static::iban($countryCode, $prefix, $length); } protected static function addBankCodeChecksum($iban, $countryCode = 'PL') { if ($countryCode != "PL" || strlen($iban) <= 8) { return $iban; } $checksum = 0; $weights = array(7, 1, 3, 9, 7, 1, 3); for ($i = 0; $i < 7; $i++) { $checksum += $weights[$i] * (int) $iban[$i]; } $checksum = $checksum % 10; return substr($iban, 0, 7) . $checksum . substr($iban, 8); } } generator->parse($format); } public function address() { $format = static::randomElement(static::$addressFormats); return $this->generator->parse($format); } public function streetAddress() { $format = static::randomElement(static::$streetAddressFormats); return $this->generator->parse($format); } } format('dmy'), static::numerify('%###')); } } generator->parse($format); } public function firstName($gender = null) { if ($gender === static::GENDER_MALE) { return static::firstNameMale(); } elseif ($gender === static::GENDER_FEMALE) { return static::firstNameFemale(); } return $this->generator->parse(static::randomElement(static::$firstNameFormat)); } public static function firstNameMale() { return static::randomElement(static::$firstNameMale); } public static function firstNameFemale() { return static::randomElement(static::$firstNameFemale); } public function lastName() { return static::randomElement(static::$lastName); } public function title($gender = null) { if ($gender === static::GENDER_MALE) { return static::titleMale(); } elseif ($gender === static::GENDER_FEMALE) { return static::titleFemale(); } return $this->generator->parse(static::randomElement(static::$titleFormat)); } public static function titleMale() { return static::randomElement(static::$titleMale); } public static function titleFemale() { return static::randomElement(static::$titleFemale); } } numerify(str_repeat('#', $length - 1)); return $code . static::eanChecksum($code); } protected static function eanChecksum($input) { $sequence = (strlen($input) - 1) == 8 ? array(3, 1) : array(1, 3); $sums = 0; foreach (str_split($input) as $n => $digit) { $sums += $digit * $sequence[$n % 2]; } return (10 - $sums % 10) % 10; } protected static function isbnChecksum($input) { $length = 9; if (strlen($input) != $length) { throw new \LengthException(sprintf('Input length should be equal to %d', $length)); } $digits = str_split($input); array_walk( $digits, function (&$digit, $position) { $digit = (10 - $position) * $digit; } ); $result = (11 - array_sum($digits) % 11) % 11; return ($result < 10)?$result:'X'; } public function ean13() { return $this->ean(13); } public function ean8() { return $this->ean(8); } public function isbn10() { $code = $this->numerify(str_repeat('#', 9)); return $code . static::isbnChecksum($code); } public function isbn13() { $code = '97' . static::numberBetween(8, 9) . $this->numerify(str_repeat('#', 9)); return $code . static::eanChecksum($code); } } array( "4539########", "4539###########", "4556########", "4556###########", "4916########", "4916###########", "4532########", "4532###########", "4929########", "4929###########", "40240071####", "40240071#######", "4485########", "4485###########", "4716########", "4716###########", "4###########", "4##############" ), 'MasterCard' => array( "51#############", "52#############", "53#############", "54#############", "55#############" ), 'American Express' => array( "34############", "37############" ), 'Discover Card' => array( "6011###########" ), ); protected static $ibanFormats = array( 'AD' => array(array('n', 4), array('n', 4), array('c', 12)), 'AE' => array(array('n', 3), array('n', 16)), 'AL' => array(array('n', 8), array('c', 16)), 'AT' => array(array('n', 5), array('n', 11)), 'AZ' => array(array('a', 4), array('c', 20)), 'BA' => array(array('n', 3), array('n', 3), array('n', 8), array('n', 2)), 'BE' => array(array('n', 3), array('n', 7), array('n', 2)), 'BG' => array(array('a', 4), array('n', 4), array('n', 2), array('c', 8)), 'BH' => array(array('a', 4), array('c', 14)), 'BR' => array(array('n', 8), array('n', 5), array('n', 10), array('a', 1), array('c', 1)), 'CH' => array(array('n', 5), array('c', 12)), 'CR' => array(array('n', 3), array('n', 14)), 'CY' => array(array('n', 3), array('n', 5), array('c', 16)), 'CZ' => array(array('n', 4), array('n', 6), array('n', 10)), 'DE' => array(array('n', 8), array('n', 10)), 'DK' => array(array('n', 4), array('n', 9), array('n', 1)), 'DO' => array(array('c', 4), array('n', 20)), 'EE' => array(array('n', 2), array('n', 2), array('n', 11), array('n', 1)), 'ES' => array(array('n', 4), array('n', 4), array('n', 1), array('n', 1), array('n', 10)), 'FR' => array(array('n', 5), array('n', 5), array('c', 11), array('n', 2)), 'GB' => array(array('a', 4), array('n', 6), array('n', 8)), 'GE' => array(array('a', 2), array('n', 16)), 'GI' => array(array('a', 4), array('c', 15)), 'GR' => array(array('n', 3), array('n', 4), array('c', 16)), 'GT' => array(array('c', 4), array('c', 20)), 'HR' => array(array('n', 7), array('n', 10)), 'HU' => array(array('n', 3), array('n', 4), array('n', 1), array('n', 15), array('n', 1)), 'IE' => array(array('a', 4), array('n', 6), array('n', 8)), 'IL' => array(array('n', 3), array('n', 3), array('n', 13)), 'IS' => array(array('n', 4), array('n', 2), array('n', 6), array('n', 10)), 'IT' => array(array('a', 1), array('n', 5), array('n', 5), array('c', 12)), 'KW' => array(array('a', 4), array('c', 22)), 'KZ' => array(array('n', 3), array('c', 13)), 'LB' => array(array('n', 4), array('c', 20)), 'LI' => array(array('n', 5), array('c', 12)), 'LT' => array(array('n', 5), array('n', 11)), 'LU' => array(array('n', 3), array('c', 13)), 'LV' => array(array('a', 4), array('c', 13)), 'MC' => array(array('n', 5), array('n', 5), array('c', 11), array('n', 2)), 'MD' => array(array('c', 2), array('c', 18)), 'ME' => array(array('n', 3), array('n', 13), array('n', 2)), 'MK' => array(array('n', 3), array('c', 10), array('n', 2)), 'MR' => array(array('n', 5), array('n', 5), array('n', 11), array('n', 2)), 'MT' => array(array('a', 4), array('n', 5), array('c', 18)), 'MU' => array(array('a', 4), array('n', 2), array('n', 2), array('n', 12), array('n', 3), array('a', 3)), 'NL' => array(array('a', 4), array('n', 10)), 'NO' => array(array('n', 4), array('n', 6), array('n', 1)), 'PK' => array(array('a', 4), array('c', 16)), 'PL' => array(array('n', 8), array('n', 16)), 'PS' => array(array('a', 4), array('c', 21)), 'PT' => array(array('n', 4), array('n', 4), array('n', 11), array('n', 2)), 'RO' => array(array('a', 4), array('c', 16)), 'RS' => array(array('n', 3), array('n', 13), array('n', 2)), 'SA' => array(array('n', 2), array('c', 18)), 'SE' => array(array('n', 3), array('n', 16), array('n', 1)), 'SI' => array(array('n', 5), array('n', 8), array('n', 2)), 'SK' => array(array('n', 4), array('n', 6), array('n', 10)), 'SM' => array(array('a', 1), array('n', 5), array('n', 5), array('c', 12)), 'TN' => array(array('n', 2), array('n', 3), array('n', 13), array('n', 2)), 'TR' => array(array('n', 5), array('c', 1), array('c', 16)), 'VG' => array(array('a', 4), array('n', 16)), ); public static function creditCardType() { return static::randomElement(static::$cardVendors); } public static function creditCardNumber($type = null, $formatted = false, $separator = '-') { if (is_null($type)) { $type = static::creditCardType(); } $mask = static::randomElement(static::$cardParams[$type]); $number = static::numerify($mask); $number .= Luhn::computeCheckDigit($number); if ($formatted) { $p1 = substr($number, 0, 4); $p2 = substr($number, 4, 4); $p3 = substr($number, 8, 4); $p4 = substr($number, 12); $number = $p1 . $separator . $p2 . $separator . $p3 . $separator . $p4; } return $number; } public function creditCardExpirationDate($valid = true) { if ($valid) { return $this->generator->dateTimeBetween('now', '36 months'); } return $this->generator->dateTimeBetween('-36 months', '36 months'); } public function creditCardExpirationDateString($valid = true, $expirationDateFormat = null) { return $this->creditCardExpirationDate($valid)->format(is_null($expirationDateFormat) ? static::$expirationDateFormat : $expirationDateFormat); } public function creditCardDetails($valid = true) { $type = static::creditCardType(); return array( 'type' => $type, 'number' => static::creditCardNumber($type), 'name' => $this->generator->name(), 'expirationDate' => $this->creditCardExpirationDateString($valid) ); } protected static function iban($countryCode, $prefix = '', $length = null) { $countryCode = strtoupper($countryCode); $format = !isset(static::$ibanFormats[$countryCode]) ? array() : static::$ibanFormats[$countryCode]; if ($length === null) { if ($format === null) { $length = 24; } else { $length = 0; foreach ($format as $part) { list($class, $groupCount) = $part; $length += $groupCount; } } } $result = $prefix; $length -= strlen($prefix); $nextPart = array_shift($format); if ($nextPart !== false) { list($class, $groupCount) = $nextPart; } else { $class = 'n'; $groupCount = 0; } $groupCount = $nextPart === false ? 0 : $nextPart[1]; for ($i = 0; $i < $length; $i++) { if ($nextPart !== false && $groupCount-- < 1) { $nextPart = array_shift($format); list($class, $groupCount) = $nextPart; } switch ($class) { default: case 'c': $result .= mt_rand(0, 100) <= 50 ? static::randomDigit() : strtoupper(static::randomLetter()); break; case 'a': $result .= strtoupper(static::randomLetter()); break; case 'n': $result .= static::randomDigit(); break; } } $result = static::addBankCodeChecksum($result, $countryCode); $countryNumber = 100 * (ord($countryCode[0])-55) + (ord($countryCode[1])-55); $tempResult = $result . $countryNumber . '00'; $checksum = (int) $tempResult[0]; for ($i = 1, $size = strlen($tempResult); $i < $size; $i++) { $checksum = (10 * $checksum + (int) $tempResult[$i]) % 97; } $checksum = 98 - $checksum; if ($checksum < 10) { $checksum = '0'.$checksum; } return $countryCode . $checksum . $result; } protected static function addBankCodeChecksum($iban, $countryCode = '') { return $iban; } public static function swiftBicNumber() { return self::regexify("^([A-Z]){4}([A-Z]){2}([0-9A-Z]){2}([0-9A-Z]{3})?$"); } } generator->parse($format); } } generator->parse($format)); } public function domainName() { return static::randomElement(static::$lastNameAscii) . '.' . $this->tld(); } } generator->parse($format); } public static function firstKanaName() { return static::randomElement(static::$firstKanaName); } public static function lastKanaName() { return static::randomElement(static::$lastKanaName); } } generator = $generator; } public static function randomDigit() { return mt_rand(0, 9); } public static function randomDigitNotNull() { return mt_rand(1, 9); } public static function randomNumber($nbDigits = null, $strict = false) { if (!is_bool($strict)) { throw new \InvalidArgumentException('randomNumber() generates numbers of fixed width. To generate numbers between two boundaries, use numberBetween() instead.'); } if (null === $nbDigits) { $nbDigits = static::randomDigitNotNull(); } $max = pow(10, $nbDigits) - 1; if ($max > mt_getrandmax()) { throw new \InvalidArgumentException('randomNumber() can only generate numbers up to mt_getrandmax()'); } if ($strict) { return mt_rand(pow(10, $nbDigits - 1), $max); } return mt_rand(0, $max); } public static function randomFloat($nbMaxDecimals = null, $min = 0, $max = null) { if (null === $nbMaxDecimals) { $nbMaxDecimals = static::randomDigit(); } if (null === $max) { $max = static::randomNumber(); } if ($min > $max) { $tmp = $min; $min = $max; $max = $tmp; } return round($min + mt_rand() / mt_getrandmax() * ($max - $min), $nbMaxDecimals); } public static function numberBetween($min = 0, $max = 2147483647) { return mt_rand($min, $max); } public static function randomLetter() { return chr(mt_rand(97, 122)); } public static function randomAscii() { return chr(mt_rand(33, 126)); } public static function randomElements(array $array = array('a', 'b', 'c'), $count = 1) { $allKeys = array_keys($array); $numKeys = count($allKeys); if ($numKeys < $count) { throw new \LengthException(sprintf('Cannot get %d elements, only %d in array', $count, $numKeys)); } $highKey = $numKeys - 1; $keys = $elements = array(); $numElements = 0; while ($numElements < $count) { $num = mt_rand(0, $highKey); if (isset($keys[$num])) { continue; } $keys[$num] = true; $elements[] = $array[$allKeys[$num]]; $numElements++; } return $elements; } public static function randomElement($array = array('a', 'b', 'c')) { if (!$array) { return null; } $elements = static::randomElements($array, 1); return $elements[0]; } public static function randomKey($array = array()) { if (!$array) { return null; } $keys = array_keys($array); $key = $keys[mt_rand(0, count($keys) - 1)]; return $key; } public static function shuffle($arg = '') { if (is_array($arg)) { return static::shuffleArray($arg); } if (is_string($arg)) { return static::shuffleString($arg); } throw new \InvalidArgumentException('shuffle() only supports strings or arrays'); } public static function shuffleArray($array = array()) { $shuffledArray = array(); $i = 0; reset($array); while (list($key, $value) = each($array)) { if ($i == 0) { $j = 0; } else { $j = mt_rand(0, $i); } if ($j == $i) { $shuffledArray[]= $value; } else { $shuffledArray[]= $shuffledArray[$j]; $shuffledArray[$j] = $value; } $i++; } return $shuffledArray; } public static function shuffleString($string = '', $encoding = 'UTF-8') { if (function_exists('mb_strlen')) { $array = array(); $strlen = mb_strlen($string, $encoding); for ($i = 0; $i < $strlen; $i++) { $array []= mb_substr($string, $i, 1, $encoding); } } else { $array = str_split($string, 1); } return join('', static::shuffleArray($array)); } public static function numerify($string = '###') { $toReplace = array(); for ($i = 0, $count = strlen($string); $i < $count; $i++) { if ($string[$i] === '#') { $toReplace []= $i; } } if ($nbReplacements = count($toReplace)) { $maxAtOnce = strlen((string) mt_getrandmax()) - 1; $numbers = ''; $i = 0; while ($i < $nbReplacements) { $size = min($nbReplacements - $i, $maxAtOnce); $numbers .= str_pad(static::randomNumber($size), $size, '0', STR_PAD_LEFT); $i += $size; } for ($i = 0; $i < $nbReplacements; $i++) { $string[$toReplace[$i]] = $numbers[$i]; } } $string = preg_replace_callback('/\%/u', 'static::randomDigitNotNull', $string); return $string; } public static function lexify($string = '????') { return preg_replace_callback('/\?/u', 'static::randomLetter', $string); } public static function bothify($string = '## ??') { return static::lexify(static::numerify($string)); } public static function asciify($string = '****') { return preg_replace_callback('/\*/u', 'static::randomAscii', $string); } public static function regexify($regex = '') { $regex = preg_replace('/^\/?\^?/', '', $regex); $regex = preg_replace('/\$?\/?$/', '', $regex); $regex = preg_replace('/\{(\d+)\}/', '{\1,\1}', $regex); $regex = preg_replace('/(?generator; } return new DefaultGenerator($default); } public function unique($reset = false, $maxRetries = 10000) { if ($reset || !$this->unique) { $this->unique = new UniqueGenerator($this->generator, $maxRetries); } return $this->unique; } } generator->parse($format)); } public function hamletPrefix() { return static::randomElement(static::$hamletPrefix); } public function wardName() { $format = static::randomElement(static::$wardNameFormats); return static::bothify($this->generator->parse($format)); } public function wardPrefix() { return static::randomElement(static::$wardPrefix); } public function districtName() { $format = static::randomElement(static::$districtNameFormats); return static::bothify($this->generator->parse($format)); } public function districtPrefix() { return static::randomElement(static::$districtPrefix); } public function city() { return static::randomElement(static::$city); } public static function province() { return static::randomElement(static::$province); } } generator->parse(static::randomElement(static::$middleNameFormat)); } public static function middleNameMale() { return static::randomElement(static::$middleNameMale); } public static function middleNameFemale() { return static::randomElement(static::$middleNameFemale); } } array( '0[a] ### ####', '(0[a]) ### ####', '0[a]-###-####', '(0[a])###-####', '84-[a]-###-####', '(84)([a])###-####', '+84-[a]-###-####', ), '8' => array( '0[a] #### ####', '(0[a]) #### ####', '0[a]-####-####', '(0[a])####-####', '84-[a]-####-####', '(84)([a])####-####', '+84-[a]-####-####', ), ); public static function phoneNumber() { $areaCode = static::randomElement(static::$areaCodes); $areaCodeLength = strlen($areaCode); $digits = 7; if ($areaCodeLength < 2) { $digits = 8; } return static::numerify(str_replace('[a]', $areaCode, static::randomElement(static::$formats[$digits]))); } } generator->parse(static::randomElement(static::$lastNameFormat)); } public static function lastNameMale() { return static::randomElement(static::$lastNameMale); } public static function lastNameFemale() { return static::randomElement(static::$lastNameFemale); } } generator->parse(static::randomElement(static::$lastNameFormat)); } public static function lastNameMale() { return static::randomElement(static::$lastNameMale); } public static function lastNameFemale() { return static::randomElement(static::$lastNameFemale); } public static function suffix() { return static::randomElement(static::$suffix); } } format('a') === 'am' ? 'öö' : 'ös'; } public static function dayOfWeek($max = 'now') { $map = array( 'Sunday' => 'Pazar', 'Monday' => 'Pazartesi', 'Tuesday' => 'Salı', 'Wednesday' => 'Çarşamba', 'Thursday' => 'Perşembe', 'Friday' => 'Cuma', 'Saturday' => 'Cumartesi', ); $week = static::dateTime($max)->format('l'); return isset($map[$week]) ? $map[$week] : $week; } public static function monthName($max = 'now') { $map = array( 'January' => 'Ocak', 'February' => 'Şubat', 'March' => 'Mart', 'April' => 'Nisan', 'May' => 'Mayıs', 'June' => 'Haziran', 'July' => 'Temmuz', 'August' => 'Ağustos', 'September' => 'Eylül', 'October' => 'Ekim', 'November' => 'Kasım', 'December' => 'Aralık', ); $month = static::dateTime($max)->format('F'); return isset($map[$month]) ? $map[$month] : $month; } } array( '板橋區', '三重區', '中和區', '永和區', '新莊區', '新店區', '樹林區', '鶯歌區', '三峽區', '淡水區', '汐止區', '瑞芳區', '土城區', '蘆洲區', '五股區', '泰山區', '林口區', '深坑區', '石碇區', '坪林區', '三芝區', '石門區', '八里區', '平溪區', '雙溪區', '貢寮區', '金山區', '萬里區', '烏來區', ), '宜蘭縣' => array( '宜蘭市', '羅東鎮', '蘇澳鎮', '頭城鎮', '礁溪鄉', '壯圍鄉', '員山鄉', '冬山鄉', '五結鄉', '三星鄉', '大同鄉', '南澳鄉', ), '桃園縣' => array( '桃園市', '中壢市', '大溪鎮', '楊梅鎮', '蘆竹鄉', '大園鄉', '龜山鄉', '八德市', '龍潭鄉', '平鎮市', '新屋鄉', '觀音鄉', '復興鄉', ), '新竹縣' => array( '竹北市', '竹東鎮', '新埔鎮', '關西鎮', '湖口鄉', '新豐鄉', '芎林鄉', '橫山鄉', '北埔鄉', '寶山鄉', '峨眉鄉', '尖石鄉', '五峰鄉', ), '苗栗縣' => array( '苗栗市', '苑裡鎮', '通霄鎮', '竹南鎮', '頭份鎮', '後龍鎮', '卓蘭鎮', '大湖鄉', '公館鄉', '銅鑼鄉', '南庄鄉', '頭屋鄉', '三義鄉', '西湖鄉', '造橋鄉', '三灣鄉', '獅潭鄉', '泰安鄉', ), '臺中市' => array( '豐原區', '東勢區', '大甲區', '清水區', '沙鹿區', '梧棲區', '后里區', '神岡區', '潭子區', '大雅區', '新社區', '石岡區', '外埔區', '大安區', '烏日區', '大肚區', '龍井區', '霧峰區', '太平區', '大里區', '和平區', '中區', '東區', '南區', '西區', '北區', '西屯區', '南屯區', '北屯區', ), '彰化縣' => array( '彰化市', '鹿港鎮', '和美鎮', '線西鄉', '伸港鄉', '福興鄉', '秀水鄉', '花壇鄉', '芬園鄉', '員林鎮', '溪湖鎮', '田中鎮', '大村鄉', '埔鹽鄉', '埔心鄉', '永靖鄉', '社頭鄉', '二水鄉', '北斗鎮', '二林鎮', '田尾鄉', '埤頭鄉', '芳苑鄉', '大城鄉', '竹塘鄉', '溪州鄉', ), '南投縣' => array( '南投市', '埔里鎮', '草屯鎮', '竹山鎮', '集集鎮', '名間鄉', '鹿谷鄉', '中寮鄉', '魚池鄉', '國姓鄉', '水里鄉', '信義鄉', '仁愛鄉', ), '雲林縣' => array( '斗六市', '斗南鎮', '虎尾鎮', '西螺鎮', '土庫鎮', '北港鎮', '古坑鄉', '大埤鄉', '莿桐鄉', '林內鄉', '二崙鄉', '崙背鄉', '麥寮鄉', '東勢鄉', '褒忠鄉', '臺西鄉', '元長鄉', '四湖鄉', '口湖鄉', '水林鄉', ), '嘉義縣' => array( '太保市', '朴子市', '布袋鎮', '大林鎮', '民雄鄉', '溪口鄉', '新港鄉', '六腳鄉', '東石鄉', '義竹鄉', '鹿草鄉', '水上鄉', '中埔鄉', '竹崎鄉', '梅山鄉', '番路鄉', '大埔鄉', '阿里山鄉', ), '臺南市' => array( '新營區', '鹽水區', '白河區', '柳營區', '後壁區', '東山區', '麻豆區', '下營區', '六甲區', '官田區', '大內區', '佳里區', '學甲區', '西港區', '七股區', '將軍區', '北門區', '新化區', '善化區', '新市區', '安定區', '山上區', '玉井區', '楠西區', '南化區', '左鎮區', '仁德區', '歸仁區', '關廟區', '龍崎區', '永康區', '東區', '南區', '西區', '北區', '中區', '安南區', '安平區', ), '高雄市' => array( '鳳山區', '林園區', '大寮區', '大樹區', '大社區', '仁武區', '鳥松區', '岡山區', '橋頭區', '燕巢區', '田寮區', '阿蓮區', '路竹區', '湖內區', '茄萣區', '永安區', '彌陀區', '梓官區', '旗山區', '美濃區', '六龜區', '甲仙區', '杉林區', '內門區', '茂林區', '桃源區', '三民區', '鹽埕區', '鼓山區', '左營區', '楠梓區', '三民區', '新興區', '前金區', '苓雅區', '前鎮區', '旗津區', '小港區', ), '屏東縣' => array( '屏東市', '潮州鎮', '東港鎮', '恆春鎮', '萬丹鄉', '長治鄉', '麟洛鄉', '九如鄉', '里港鄉', '鹽埔鄉', '高樹鄉', '萬巒鄉', '內埔鄉', '竹田鄉', '新埤鄉', '枋寮鄉', '新園鄉', '崁頂鄉', '林邊鄉', '南州鄉', '佳冬鄉', '琉球鄉', '車城鄉', '滿州鄉', '枋山鄉', '三地門鄉', '霧臺鄉', '瑪家鄉', '泰武鄉', '來義鄉', '春日鄉', '獅子鄉', '牡丹鄉', ), '臺東縣' => array( '臺東市', '成功鎮', '關山鎮', '卑南鄉', '鹿野鄉', '池上鄉', '東河鄉', '長濱鄉', '太麻里鄉', '大武鄉', '綠島鄉', '海端鄉', '延平鄉', '金峰鄉', '達仁鄉', '蘭嶼鄉', ), '花蓮縣' => array( '花蓮市', '鳳林鎮', '玉里鎮', '新城鄉', '吉安鄉', '壽豐鄉', '光復鄉', '豐濱鄉', '瑞穗鄉', '富里鄉', '秀林鄉', '萬榮鄉', '卓溪鄉', ), '澎湖縣' => array( '馬公市', '湖西鄉', '白沙鄉', '西嶼鄉', '望安鄉', '七美鄉', ), '基隆市' => array( '中正區', '七堵區', '暖暖區', '仁愛區', '中山區', '安樂區', '信義區', ), '新竹市' => array( '東區', '北區', '香山區', ), '嘉義市' => array( '東區', '西區', ), '臺北市' => array( '松山區', '信義區', '大安區', '中山區', '中正區', '大同區', '萬華區', '文山區', '南港區', '內湖區', '士林區', '北投區', ), '連江縣' => array( '南竿鄉', '北竿鄉', '莒光鄉', '東引鄉', ), '金門縣' => array( '金城鎮', '金沙鎮', '金湖鎮', '金寧鄉', '烈嶼鄉', '烏坵鄉', ), ); protected static $country = array( '不丹', '中非', '丹麥', '伊朗', '冰島', '剛果', '加彭', '北韓', '南非', '卡達', '印尼', '印度', '古巴', '哥德', '埃及', '多哥', '寮國', '尼日', '巴曼', '巴林', '巴紐', '巴西', '希臘', '帛琉', '德國', '挪威', '捷克', '教廷', '斐濟', '日本', '智利', '東加', '查德', '汶萊', '法國', '波蘭', '波赫', '泰國', '海地', '瑞典', '瑞士', '祕魯', '秘魯', '約旦', '紐埃', '緬甸', '美國', '聖尼', '聖普', '肯亞', '芬蘭', '英國', '荷蘭', '葉門', '蘇丹', '諾魯', '貝南', '越南', '迦彭', '迦納', '阿曼', '阿聯', '韓國', '馬利', '以色列', '以色利', '伊拉克', '俄羅斯', '利比亞', '加拿大', '匈牙利', '南極洲', '南蘇丹', '厄瓜多', '吉布地', '吐瓦魯', '哈撒克', '哈薩克', '喀麥隆', '喬治亞', '土庫曼', '土耳其', '塔吉克', '塞席爾', '墨西哥', '大西洋', '奧地利', '孟加拉', '安哥拉', '安地卡', '安道爾', '尚比亞', '尼伯爾', '尼泊爾', '巴哈馬', '巴拉圭', '巴拿馬', '巴貝多', '幾內亞', '愛爾蘭', '所在國', '摩洛哥', '摩納哥', '敍利亞', '敘利亞', '新加坡', '東帝汶', '柬埔寨', '比利時', '波扎那', '波札那', '烏克蘭', '烏干達', '烏拉圭', '牙買加', '獅子山', '甘比亞', '盧安達', '盧森堡', '科威特', '科索夫', '科索沃', '立陶宛', '紐西蘭', '維德角', '義大利', '聖文森', '艾塞亞', '菲律賓', '萬那杜', '葡萄牙', '蒲隆地', '蓋亞納', '薩摩亞', '蘇利南', '西班牙', '貝里斯', '賴索托', '辛巴威', '阿富汗', '阿根廷', '馬其頓', '馬拉威', '馬爾他', '黎巴嫩', '亞塞拜然', '亞美尼亞', '保加利亞', '南斯拉夫', '厄利垂亞', '史瓦濟蘭', '吉爾吉斯', '吉里巴斯', '哥倫比亞', '坦尚尼亞', '塞內加爾', '塞内加爾', '塞爾維亞', '多明尼加', '多米尼克', '奈及利亞', '委內瑞拉', '宏都拉斯', '尼加拉瓜', '巴基斯坦', '庫克群島', '愛沙尼亞', '拉脫維亞', '摩爾多瓦', '摩里西斯', '斯洛伐克', '斯里蘭卡', '格瑞那達', '模里西斯', '波多黎各', '澳大利亞', '烏茲別克', '玻利維亞', '瓜地馬拉', '白俄羅斯', '突尼西亞', '納米比亞', '索馬利亞', '索馬尼亞', '羅馬尼亞', '聖露西亞', '聖馬利諾', '莫三比克', '莫三鼻克', '葛摩聯盟', '薩爾瓦多', '衣索比亞', '西薩摩亞', '象牙海岸', '賴比瑞亞', '賽普勒斯', '馬來西亞', '馬爾地夫', '克羅埃西亞', '列支敦斯登', '哥斯大黎加', '布吉納法索', '布吉那法索', '幾內亞比索', '幾內亞比紹', '斯洛維尼亞', '索羅門群島', '茅利塔尼亞', '蒙特內哥羅', '赤道幾內亞', '阿爾及利亞', '阿爾及尼亞', '阿爾巴尼亞', '馬紹爾群島', '馬達加斯加', '密克羅尼西亞', '沙烏地阿拉伯', '千里達及托巴哥', ); protected static $postcode = array('###-##', '###'); public function street() { return static::randomElement(static::$street); } public static function randomChineseNumber() { $digits = array( '', '一', '二', '三', '四', '五', '六', '七', '八', '九', ); return $digits[static::randomDigitNotNull()]; } public static function randomNumber2() { return static::randomNumber(2) + 1; } public static function randomNumber3() { return static::randomNumber(3) + 1; } public static function localLatitude() { return number_format(mt_rand(22000000, 25000000)/1000000, 6); } public static function localLongitude() { return number_format(mt_rand(120000000, 122000000)/1000000, 6); } public function city() { $county = static::randomElement(array_keys(static::$city)); $city = static::randomElement(static::$city[$county]); return $county.$city; } public function state() { return '臺灣省'; } public static function stateAbbr() { return '臺'; } public static function cityPrefix() { return ''; } public static function citySuffix() { return ''; } public static function secondaryAddress() { return (static::randomNumber(2)+1).static::randomElement(static::$secondaryAddressSuffix); } } userName(); } public function domainWord() { return \Faker\Factory::create('en_US')->domainWord(); } } creditCardDetails($valid); } } format('a') === 'am' ? '上午' : '下午'; } public static function dayOfWeek($max = 'now') { $map = array( 'Sunday' => '星期日', 'Monday' => '星期一', 'Tuesday' => '星期二', 'Wednesday' => '星期三', 'Thursday' => '星期四', 'Friday' => '星期五', 'Saturday' => '星期六', ); $week = static::dateTime($max)->format('l'); return isset($map[$week]) ? $map[$week] : $week; } public static function monthName($max = 'now') { $map = array( 'January' => '一月', 'February' => '二月', 'March' => '三月', 'April' => '四月', 'May' => '五月', 'June' => '六月', 'July' => '七月', 'August' => '八月', 'September' => '九月', 'October' => '十月', 'November' => '十一月', 'December' => '十二月', ); $month = static::dateTime($max)->format('F'); return isset($map[$month]) ? $map[$month] : $month; } } generator->parse($format); } public static function companyModifier() { return static::randomElement(static::$companyModifier); } public static function companyPrefix() { return static::randomElement(static::$companyPrefix); } public function catchPhrase() { return static::randomElement(static::$catchPhrase); } public function bs() { $result = ''; foreach (static::$bsWords as &$word) { $result .= static::randomElement($word); } return $result; } } 5) { throw new \InvalidArgumentException('indexSize must be at most 5'); } $words = $this->getConsecutiveWords($indexSize); $result = array(); $resultLength = 0; $next = static::randomKey($words); while ($resultLength < $maxNbChars && isset($words[$next])) { $word = static::randomElement($words[$next]); $currentWords = explode(' ', $next); $currentWords[] = $word; array_shift($currentWords); $next = implode(' ', $currentWords); if ($resultLength == 0 && !preg_match('/^[\x{0600}-\x{06FF}]/u', $word)) { continue; } $result[] = $word; $resultLength += strlen($word) + 1; } array_pop($result); $result = implode(' ', $result); return $result.'.'; } protected static $baseText = <<<'EOT' از در که وارد شدم سیگارم دستم بود. زورم آمد سلام کنم. همین طوری دنگم گرفته بود قد باشم. رئیس فرهنگ که اجازه‌ی نشستن داد، نگاهش لحظه‌ای روی دستم مکث کرد و بعد چیزی را که می‌نوشت، تمام کرد و می‌خواست متوجه من بشود که رونویس حکم را روی میزش گذاشته بودم. حرفی نزدیم. رونویس را با کاغذهای ضمیمه‌اش زیر و رو کرد و بعد غبغب انداخت و آرام و مثلاً خالی از عصبانیت گفت: - جا نداریم آقا. این که نمی‌شه! هر روز یه حکم می‌دند دست یکی می‌فرستنش سراغ من... دیروز به آقای مدیر کل... حوصله‌ی این اباطیل را نداشتم. حرفش را بریدم که: - ممکنه خواهش کنم زیر همین ورقه مرقوم بفرمایید؟ و سیگارم را توی زیرسیگاری براق روی میزش تکاندم. روی میز، پاک و مرتب بود. درست مثل اتاق همان مهمان‌خانه‌ی تازه‌عروس‌ها. هر چیز به جای خود و نه یک ذره گرد. فقط خاکستر سیگار من زیادی بود. مثل تفی در صورت تازه تراشیده‌ای.... قلم را برداشت و زیر حکم چیزی نوشت و امضا کرد و من از در آمده بودم بیرون. خلاص. تحمل این یکی را نداشتم. با اداهایش. پیدا بود که تازه رئیس شده. زورکی غبغب می‌انداخت و حرفش را آهسته توی چشم آدم می‌زد. انگار برای شنیدنش گوش لازم نیست. صد و پنجاه تومان در کارگزینی کل مایه گذاشته بودم تا این حکم را به امضا رسانده بودم. توصیه هم برده بودم و تازه دو ماه هم دویده بودم. مو، لای درزش نمی‌رفت. می‌دانستم که چه او بپذیرد، چه نپذیرد، کار تمام است. خودش هم می‌دانست. حتماً هم دستگیرش شد که با این نک و نالی که می‌کرد، خودش را کنف کرده. ولی کاری بود و شده بود. در کارگزینی کل، سفارش کرده بودند که برای خالی نبودن عریضه رونویس را به رؤیت رئیس فرهنگ هم برسانم تازه این طور شد. و گر نه بالی حکم کارگزینی کل چه کسی می‌توانست حرفی بزند؟ یک وزارت خانه بود و یک کارگزینی! شوخی که نبود. ته دلم قرص‌تر از این‌ها بود که محتاج به این استدلال‌ها باشم. اما به نظرم همه‌ی این تقصیرها از این سیگار لعنتی بود که به خیال خودم خواسته بودم خرجش را از محل اضافه حقوق شغل جدیدم در بیاورم. البته از معلمی، هم اُقم نشسته بود. ده سال «الف.ب.» درس دادن و قیافه‌های بهت‌زده‌ی بچه‌های مردم برای مزخرف‌ترین چرندی که می‌گویی... و استغناء با غین و استقراء با قاف و خراسانی و هندی و قدیمی‌ترین شعر دری و صنعت ارسال مثل و ردالعجز... و از این مزخرفات! دیدم دارم خر می‌شوم. گفتم مدیر بشوم. مدیر دبستان! دیگر نه درس خواهم داد و نه مجبور خواهم بود برای فرار از اتلاف وقت، در امتحان تجدیدی به هر احمق بی‌شعوری هفت بدهم تا ایام آخر تابستانم را که لذیذترین تکه‌ی تعطیلات است، نجات داده باشم. این بود که راه افتادم. رفتم و از اهلش پرسیدم. از یک کار چاق کن. دستم را توی دست کارگزینی گذاشت و قول و قرار و طرفین خوش و خرم و یک روز هم نشانی مدرسه را دستم دادند که بروم وارسی، که باب میلم هست یا نه. و رفتم. مدرسه دو طبقه بود و نوساز بود و در دامنه‌ی کوه تنها افتاده بود و آفتاب‌رو بود. یک فرهنگ‌دوست خرپول، عمارتش را وسط زمین خودش ساخته بود و بیست و پنج سال هم در اختیار فرهنگ گذاشته بود که مدرسه‌اش کنند و رفت و آمد بشود و جاده‌ها کوبیده بشود و این قدر ازین بشودها بشود، تا دل ننه باباها بسوزد و برای این‌که راه بچه‌هاشان را کوتاه بکنند، بیایند همان اطراف مدرسه را بخرند و خانه بسازند و زمین یارو از متری یک عباسی بشود صد تومان. یارو اسمش را هم روی دیوار مدرسه کاشی‌کاری کرده بود. هنوز در و همسایه پیدا نکرده بودند که حرف‌شان بشود و لنگ و پاچه‌ی سعدی و باباطاهر را بکشند میان و یک ورق دیگر از تاریخ‌الشعرا را بکوبند روی نبش دیوار کوچه‌شان. تابلوی مدرسه هم حسابی و بزرگ و خوانا. از صد متری داد می‌زد که توانا بود هر.... هر چه دلتان بخواهد! با شیر و خورشیدش که آن بالا سر، سه پا ایستاده بود و زورکی تعادل خودش را حفظ می‌کرد و خورشید خانم روی کولش با ابروهای پیوسته و قمچیلی که به دست داشت و تا سه تیر پرتاب، اطراف مدرسه بیابان بود. درندشت و بی آب و آبادانی و آن ته رو به شمال، ردیف کاج‌های درهم فرو رفته‌ای که از سر دیوار گلی یک باغ پیدا بود روی آسمان لکه‌ی دراز و تیره‌ای زده بود. حتماً تا بیست و پنج سال دیگر همه‌ی این اطراف پر می‌شد و بوق ماشین و ونگ ونگ بچه‌ها و فریاد لبویی و زنگ روزنامه‌فروشی و عربده‌ی گل به سر دارم خیار! نان یارو توی روغن بود. - راستی شاید متری ده دوازده شاهی بیشتر نخریده باشد؟ شاید هم زمین‌ها را همین جوری به ثبت داده باشد؟ هان؟ - احمق به توچه؟!... بله این فکرها را همان روزی کردم که ناشناس به مدرسه سر زدم و آخر سر هم به این نتیجه رسیدم که مردم حق دارند جایی بخوابند که آب زیرشان نرود. - تو اگر مردی، عرضه داشته باش مدیر همین مدرسه هم بشو. و رفته بودم و دنبال کار را گرفته بودم تا رسیده بودم به این‌جا. همان روز وارسی فهمیده بودم که مدیر قبلی مدرسه زندانی است. لابد کله‌اش بوی قرمه‌سبزی می‌داده و باز لابد حالا دارد کفاره‌ی گناهانی را می‌دهد که یا خودش نکرده یا آهنگری در بلخ کرده. جزو پر قیچی‌های رئیس فرهنگ هم کسی نبود که با مدیرشان، اضافه حقوقی نصیبش بشود و ناچار سر و دستی برای این کار بشکند. خارج از مرکز هم نداشت. این معلومات را توی کارگزینی به دست آورده بودم. هنوز «گه خوردم نامه‌نویسی» هم مد نشده بود که بگویم یارو به این زودی‌ها از سولدونی در خواهد آمد. فکر نمی‌کردم که دیگری هم برای این وسط بیابان دلش لک زده باشد با زمستان سختش و با رفت و آمد دشوارش. این بود که خیالم راحت بود. از همه‌ی این‌ها گذشته کارگزینی کل موافقت کرده بود! دست است که پیش از بلند شدن بوی اسکناس، آن جا هم دو سه تا عیب شرعی و عرفی گرفته بودند و مثلاً گفته بودن لابد کاسه‌ای زیر نیم کاسه است که فلانی یعنی من، با ده سال سابقه‌ی تدریس، می‌خواهد مدیر دبستان بشود! غرض‌شان این بود که لابد خل شدم که از شغل مهم و محترم دبیری دست می‌شویم. ماهی صد و پنجاه تومان حق مقام در آن روزها پولی نبود که بتوانم نادیده بگیرم. و تازه اگر ندیده می‌گرفتم چه؟ باز باید بر می‌گشتم به این کلاس‌ها و این جور حماقت‌ها. این بود که پیش رئیس فرهنگ، صاف برگشتم به کارگزینی کل، سراغ آن که بفهمی نفهمی، دلال کارم بود. و رونویس حکم را گذاشتم و گفتم که چه طور شد و آمدم بیرون. دو روز بعد رفتم سراغش. معلوم شد که حدسم درست بوده است و رئیس فرهنگ گفته بوده: «من از این لیسانسه‌های پر افاده نمی‌خواهم که سیگار به دست توی هر اتاقی سر می‌کنند.» و یارو برایش گفته بود که اصلاً وابدا..! فلانی همچین و همچون است و مثقالی هفت صنار با دیگران فرق دارد و این هندوانه‌ها و خیال من راحت باشد و پنج‌شنبه یک هفته‌ی دیگر خودم بروم پهلوی او... و این کار را کردم. این بار رئیس فرهنگ جلوی پایم بلند شد که: «ای آقا... چرا اول نفرمودید؟!...» و از کارمندهایش گله کرد و به قول خودش، مرا «در جریان موقعیت محل» گذاشت و بعد با ماشین خودش مرا به مدرسه رساند و گفت زنگ را زودتر از موعد زدند و در حضور معلم‌ها و ناظم، نطق غرایی در خصائل مدیر جدید – که من باشم – کرد و بعد هم مرا گذاشت و رفت با یک مدرسه‌ی شش کلاسه‌ی «نوبنیاد» و یک ناظم و هفت تا معلم و دویست و سی و پنج تا شاگرد. دیگر حسابی مدیر مدرسه شده بودم! ناظم، جوان رشیدی بود که بلند حرف می‌زد و به راحتی امر و نهی می‌کرد و بیا و برویی داشت و با شاگردهای درشت، روی هم ریخته بود که خودشان ترتیب کارها را می‌دادند و پیدا بود که به سر خر احتیاجی ندارد و بی‌مدیر هم می‌تواند گلیم مدرسه را از آب بکشد. معلم کلاس چهار خیلی گنده بود. دو تای یک آدم حسابی. توی دفتر، اولین چیزی که به چشم می‌آمد. از آن‌هایی که اگر توی کوچه ببینی، خیال می‌کنی مدیر کل است. لفظ قلم حرف می‌زد و شاید به همین دلیل بود که وقتی رئیس فرهنگ رفت و تشریفات را با خودش برد، از طرف همکارانش تبریک ورود گفت و اشاره کرد به اینکه «ان‌شاءالله زیر سایه‌ی سرکار، سال دیگر کلاس‌های دبیرستان را هم خواهیم داشت.» پیدا بود که این هیکل کم‌کم دارد از سر دبستان زیادی می‌کند! وقتی حرف می‌زد همه‌اش درین فکر بودم که با نان آقا معلمی چه طور می‌شد چنین هیکلی به هم زد و چنین سر و تیپی داشت؟ و راستش تصمیم گرفتم که از فردا صبح به صبح ریشم را بتراشم و یخه‌ام تمیز باشد و اتوی شلوارم تیز. معلم کلاس اول باریکه‌ای بود، سیاه سوخته. با ته ریشی و سر ماشین کرده‌ای و یخه‌ی بسته. بی‌کراوات. شبیه میرزابنویس‌های دم پست‌خانه. حتی نوکر باب می‌نمود. و من آن روز نتوانستم بفهمم وقتی حرف می‌زند کجا را نگاه می‌کند. با هر جیغ کوتاهی که می‌زد هرهر می‌خندید. با این قضیه نمی‌شد کاری کرد. معلم کلاس سه، یک جوان ترکه‌ای بود؛ بلند و با صورت استخوانی و ریش از ته تراشیده و یخه‌ی بلند آهاردار. مثل فرفره می‌جنبید. چشم‌هایش برق عجیبی می‌زد که فقط از هوش نبود، چیزی از ناسلامتی در برق چشم‌هایش بود که مرا واداشت از ناظم بپرسم مبادا مسلول باشد. البته مسلول نبود، تنها بود و در دانشگاه درس می‌خواند. کلاس‌های پنجم و ششم را دو نفر با هم اداره می‌کردند. یکی فارسی و شرعیات و تاریخ، جغرافی و کاردستی و این جور سرگرمی‌ها را می‌گفت، که جوانکی بود بریانتین زده، با شلوار پاچه تنگ و پوشت و کراوات زرد و پهنی که نعش یک لنگر بزرگ آن را روی سینه‌اش نگه داشته بود و دائماً دستش حمایل موهای سرش بود و دم به دم توی شیشه‌ها نگاه می‌کرد. و آن دیگری که حساب و مرابحه و چیزهای دیگر می‌گفت، جوانی بود موقر و سنگین مازندرانی به نظر می‌آمد و به خودش اطمینان داشت. غیر از این‌ها، یک معلم ورزش هم داشتیم که دو هفته بعد دیدمش و اصفهانی بود و از آن قاچاق‌ها. رئیس فرهنگ که رفت، گرم و نرم از همه‌شان حال و احوال پرسیدم. بعد به همه سیگار تعارف کردم. سراپا همکاری و همدردی بود. از کار و بار هر کدامشان پرسیدم. فقط همان معلم کلاس سه، دانشگاه می‌رفت. آن که لنگر به سینه انداخته بود، شب‌ها انگلیسی می‌خواند که برود آمریکا. چای و بساطی در کار نبود و ربع ساعت‌های تفریح، فقط توی دفتر جمع می‌شدند و دوباره از نو. و این نمی‌شد. باید همه‌ی سنن را رعایت کرد. دست کردم و یک پنج تومانی روی میز گذاشتم و قرار شد قبل و منقلی تهیه کنند و خودشان چای را راه بیندازند. بعد از زنگ قرار شد من سر صف نطقی بکنم. ناظم قضیه را در دو سه کلمه برای بچه‌ها گفت که من رسیدم و همه دست زدند. چیزی نداشتم برایشان بگویم. فقط یادم است اشاره‌ای به این کردم که مدیر خیلی دلش می‌خواست یکی از شما را به جای فرزند داشته باشد و حالا نمی‌داند با این همه فرزند چه بکند؟! که بی‌صدا خندیدند و در میان صف‌های عقب یکی پکی زد به خنده. واهمه برم داشت که «نه بابا. کار ساده‌ای هم نیست!» قبلاً فکر کرده بودم که می‌روم و فارغ از دردسر اداره‌ی کلاس، در اتاق را روی خودم می‌بندم و کار خودم را می‌کنم. اما حالا می‌دیدم به این سادگی‌ها هم نیست. اگر فردا یکی‌شان زد سر اون یکی را شکست، اگر یکی زیر ماشین رفت؛ اگر یکی از ایوان افتاد؛ چه خاکی به سرم خواهم ریخت؟ حالا من مانده بودم و ناظم که چیزی از لای در آهسته خزید تو. کسی بود؛ فراش مدرسه با قیافه‌ای دهاتی و ریش نتراشیده و قدی کوتاه و گشاد گشاد راه می‌رفت و دست‌هایش را دور از بدن نگه می‌داشت. آمد و همان کنار در ایستاد. صاف توی چشمم نگاه می‌کرد. حال او را هم پرسیدم. هر چه بود او هم می‌توانست یک گوشه‌ی این بار را بگیرد. در یک دقیقه همه‌ی درد دل‌هایش را کرد و التماس دعاهایش که تمام شد، فرستادمش برایم چای درست کند و بیاورد. بعد از آن من به ناظم پرداختم. سال پیش، از دانشسرای مقدماتی در آمده بود. یک سال گرمسار و کرج کار کرده بود و امسال آمده بود این‌جا. پدرش دو تا زن داشته. از اولی دو تا پسر که هر دو تا چاقوکش از آب در آمده‌اند و از دومی فقط او مانده بود که درس‌خوان شده و سرشناس و نان مادرش را می‌دهد که مریض است و از پدر سال‌هاست که خبری نیست و... یک اتاق گرفته‌اند به پنجاه تومان و صد و پنجاه تومان حقوق به جایی نمی‌رسد و تازه زور که بزند سه سال دیگر می‌تواند از حق فنی نظامت مدرسه استفاده کند ... بعد بلند شدیم که به کلاس‌ها سرکشی کنیم. بعد با ناظم به تک تک کلاس‌ها سر زدیم در این میان من به یاد دوران دبستان خودم افتادم. در کلاس ششم را باز کردیم «... ت بی پدرو مادر» جوانک بریانتین زده خورد توی صورت‌مان. یکی از بچه‌ها صورتش مثل چغندر قرمز بود. لابد بزک فحش هنوز باقی بود. قرائت فارسی داشتند. معلم دستهایش توی جیبش بود و سینه‌اش را پیش داده بود و زبان به شکایت باز کرد: - آقای مدیر! اصلاً دوستی سرشون نمی‌شه. تو سَری می‌خوان. ملاحظه کنید بنده با چه صمیمیتی... حرفش را در تشدید «ایت» بریدم که: - صحیح می‌فرمایید. این بار به من ببخشید. و از در آمدیم بیرون. بعد از آن به اطاقی که در آینده مال من بود سر زدیم. بهتر از این نمی‌شد. بی سر و صدا، آفتاب‌رو، دور افتاده. وسط حیاط، یک حوض بزرگ بود و کم‌عمق. تنها قسمت ساختمان بود که رعایت حال بچه‌های قد و نیم قد در آن شده بود. دور حیاط دیوار بلندی بود درست مثل دیوار چین. سد مرتفعی در مقابل فرار احتمالی فرهنگ و ته حیاط مستراح و اتاق فراش بغلش و انبار زغال و بعد هم یک کلاس. به مستراح هم سر کشیدیم. همه بی در و سقف و تیغه‌ای میان آن‌ها. نگاهی به ناظم کردم که پا به پایم می‌آمد. گفت: - دردسر عجیبی شده آقا. تا حالا صد تا کاغذ به ادارفردا صبح رفتم مدرسه. بچه‌ها با صف‌هاشان به طرف کلاس‌ها می‌رفتند و ناظم چوب به دست توی ایوان ایستاده بود و توی دفتر دو تا از معلم‌ها بودند. معلوم شد کار هر روزه‌شان است. ناظم را هم فرستادم سر یک کلاس دیگر و خودم آمدم دم در مدرسه به قدم زدن؛ فکر کردم از هر طرف که بیایند مرا این ته، دم در مدرسه خواهند دید و تمام طول راه در این خجالت خواهند ماند و دیگر دیر نخواهند آمد. یک سیاهی از ته جاده‌ی جنوبی پیداشد. جوانک بریانتین زده بود. مسلماً او هم مرا می‌دید، ولی آهسته‌تر از آن می‌آمد که یک معلم تأخیر کرده جلوی مدیرش می‌آمد. جلوتر که آمد حتی شنیدم که سوت می‌زد. اما بی‌انصاف چنان سلانه سلانه می‌آمد که دیدم هیچ جای گذشت نیست. اصلاً محل سگ به من نمی‌گذاشت. داشتم از کوره در می‌رفتم که یک مرتبه احساس کردم تغییری در رفتار خود داد و تند کرد. به خیر گذشت و گرنه خدا عالم است چه اتفاقی می‌افتاد. سلام که کرد مثل این که می‌خواست چیزی بگوید که پیش دستی کردم: - بفرمایید آقا. بفرمایید، بچه‌ها منتظرند. واقعاً به خیر گذشت. شاید اتوبوسش دیر کرده. شاید راه‌بندان بوده؛ جاده قرق بوده و باز یک گردن‌کلفتی از اقصای عالم می‌آمده که ازین سفره‌ی مرتضی علی بی‌نصیب نماند. به هر صورت در دل بخشیدمش. چه خوب شد که بد و بی‌راهی نگفتی! که از دور علم افراشته‌ی هیکل معلم کلاس چهارم نمایان شد. از همان ته مرا دیده بود. تقریباً می‌دوید. تحمل این یکی را نداشتم. «بدکاری می‌کنی. اول بسم‌الله و مته به خشخاش!» رفتم و توی دفتر نشستم و خودم را به کاری مشغول کردم که هن هن کنان رسید. چنان عرق از پیشانی‌اش می‌ریخت که راستی خجالت کشیدم. یک لیوان آب از کوه به دستش دادم و مسخ‌شده‌ی خنده‌اش را با آب به خوردش دادم و بلند که شد برود، گفتم: - عوضش دو کیلو لاغر شدید. برگشت نگاهی کرد و خنده‌ای و رفت. ناگهان ناظم از در وارد شد و از را ه نرسیده گفت: - دیدید آقا! این جوری می‌آند مدرسه. اون قرتی که عین خیالش هم نبود آقا! اما این یکی... از او پرسیدم: - انگار هنوز دو تا از کلاس‌ها ولند؟ - بله آقا. کلاس سه ورزش دارند. گفتم بنشینند دیکته بنویسند آقا. معلم حساب پنج و شش هم که نیومده آقا. در همین حین یکی از عکس‌های بزرگ دخمه‌های هخامنشی را که به دیوار کوبیده بود پس زد و: - نگاه کنید آقا... روی گچ دیوار با مداد قرمز و نه چندان درشت، به عجله و ناشیانه علامت داس کشیده بودند. همچنین دنبال کرد: - از آثار دوره‌ی اوناست آقا. کارشون همین چیزها بود. روزنومه بفروشند. تبلیغات کنند و داس چکش بکشند آقا. رئیس‌شون رو که گرفتند چه جونی کندم آقا تا حالی‌شون کنم که دست ور دارند آقا. و از روی میز پرید پایین. - گفتم مگه باز هم هستند؟ - آره آقا، پس چی! یکی همین آقازاده که هنوز نیومده آقا. هر روز نیم ساعت تأخیر داره آقا. یکی هم مثل کلاس سه. - خوب چرا تا حالا پاکش نکردی؟ - به! آخه آدم درد دلشو واسه‌ی کی بگه؟ آخه آقا در میان تو روی آدم می‌گند جاسوس، مأمور! باهاش حرفم شده آقا. کتک و کتک‌کاری! و بعد یک سخنرانی که چه طور مدرسه را خراب کرده‌اند و اعتماد اهل محله را چه طور از بین برده‌اند که نه انجمنی، نه کمکی به بی‌بضاعت‌ها؛ و از این حرف ها. بعد از سخنرانی آقای ناظم دستمالم را دادم که آن عکس‌ها را پاک کند و بعد هم راه افتادم که بروم سراغ اتاق خودم. در اتاقم را که باز کردم، داشتم دماغم با بوی خاک نم کشیده‌اش اخت می‌کرد که آخرین معلم هم آمد. آمدم توی ایوان و با صدای بلند، جوری که در تمام مدرسه بشنوند، ناظم را صدا زدم و گفتم با قلم قرمز برای آقا یک ساعت تأخیر بگذارند.ه‌ی ساختمان نوشتیم آقا. می‌گند نمی‌شه پول دولت رو تو ملک دیگرون خرج کرد. - گفتم راست می‌گند. دیگه کافی بود. آمدیم بیرون. همان توی حیاط تا نفسی تازه کنیم وضع مالی و بودجه و ازین حرف‌های مدرسه را پرسیدم. هر اتاق ماهی پانزده ریال حق نظافت داشت. لوازم‌التحریر و دفترها را هم اداره‌ی فرهنگ می‌داد. ماهی بیست و پنج تومان هم برای آب خوردن داشتند که هنوز وصول نشده بود. برای نصب هر بخاری سالی سه تومان. ماهی سی تومان هم تنخواه‌گردان مدرسه بود که مثل پول آب سوخت شده بود و حالا هم ماه دوم سال بود. اواخر آبان. حالیش کردم که حوصله‌ی این کارها را ندارم و غرضم را از مدیر شدن برایش خلاصه کردم و گفتم حاضرم همه‌ی اختیارات را به او بدهم. «اصلاً انگار که هنوز مدیر نیامده.» مهر مدرسه هم پهلوی خودش باشد. البته او را هنوز نمی‌شناختم. شنیده بودم که مدیرها قبلاً ناظم خودشان را انتخاب می‌کنند، اما من نه کسی را سراغ داشتم و نه حوصله‌اش را. حکم خودم را هم به زور گرفته بودم. سنگ‌هامان را وا کندیم و به دفتر رفتیم و چایی را که فراش از بساط خانه‌اش درست کرده بود، خوردیم تا زنگ را زدند و باز هم زدند و من نگاهی به پرونده‌های شاگردها کردم که هر کدام عبارت بود از دو برگ کاغذ. از همین دو سه برگ کاغذ دانستم که اولیای بچه‌ها اغلب زارع و باغبان و اویارند و قبل از این‌که زنگ آخر را بزنند و مدرسه تعطیل بشود بیرون آمدم. برای روز اول خیلی زیاد بود. فردا صبح رفتم مدرسه. بچه‌ها با صف‌هاشان به طرف کلاس‌ها می‌رفتند و ناظم چوب به دست توی ایوان ایستاده بود و توی دفتر دو تا از معلم‌ها بودند. معلوم شد کار هر روزه‌شان است. ناظم را هم فرستادم سر یک کلاس دیگر و خودم آمدم دم در مدرسه به قدم زدن؛ فکر کردم از هر طرف که بیایند مرا این ته، دم در مدرسه خواهند دید و تمام طول راه در این خجالت خواهند ماند و دیگر دیر نخواهند آمد. یک سیاهی از ته جاده‌ی جنوبی پیداشد. جوانک بریانتین زده بود. مسلماً او هم مرا می‌دید، ولی آهسته‌تر از آن می‌آمد که یک معلم تأخیر کرده جلوی مدیرش می‌آمد. جلوتر که آمد حتی شنیدم که سوت می‌زد. اما بی‌انصاف چنان سلانه سلانه می‌آمد که دیدم هیچ جای گذشت نیست. اصلاً محل سگ به من نمی‌گذاشت. داشتم از کوره در می‌رفتم که یک مرتبه احساس کردم تغییری در رفتار خود داد و تند کرد. به خیر گذشت و گرنه خدا عالم است چه اتفاقی می‌افتاد. سلام که کرد مثل این که می‌خواست چیزی بگوید که پیش دستی کردم: - بفرمایید آقا. بفرمایید، بچه‌ها منتظرند. واقعاً به خیر گذشت. شاید اتوبوسش دیر کرده. شاید راه‌بندان بوده؛ جاده قرق بوده و باز یک گردن‌کلفتی از اقصای عالم می‌آمده که ازین سفره‌ی مرتضی علی بی‌نصیب نماند. به هر صورت در دل بخشیدمش. چه خوب شد که بد و بی‌راهی نگفتی! که از دور علم افراشته‌ی هیکل معلم کلاس چهارم نمایان شد. از همان ته مرا دیده بود. تقریباً می‌دوید. تحمل این یکی را نداشتم. «بدکاری می‌کنی. اول بسم‌الله و مته به خشخاش!» رفتم و توی دفتر نشستم و خودم را به کاری مشغول کردم که هن هن کنان رسید. چنان عرق از پیشانی‌اش می‌ریخت که راستی خجالت کشیدم. یک لیوان آب از کوه به دستش دادم و مسخ‌شده‌ی خنده‌اش را با آب به خوردش دادم و بلند که شد برود، گفتم: - عوضش دو کیلو لاغر شدید. برگشت نگاهی کرد و خنده‌ای و رفت. ناگهان ناظم از در وارد شد و از را ه نرسیده گفت: - دیدید آقا! این جوری می‌آند مدرسه. اون قرتی که عین خیالش هم نبود آقا! اما این یکی... از او پرسیدم: - انگار هنوز دو تا از کلاس‌ها ولند؟ - بله آقا. کلاس سه ورزش دارند. گفتم بنشینند دیکته بنویسند آقا. معلم حساب پنج و شش هم که نیومده آقا. در همین حین یکی از عکس‌های بزرگ دخمه‌های هخامنشی را که به دیوار کوبیده بود پس زد و: - نگاه کنید آقا... روی گچ دیوار با مداد قرمز و نه چندان درشت، به عجله و ناشیانه علامت داس کشیده بودند. همچنین دنبال کرد: - از آثار دوره‌ی اوناست آقا. کارشون همین چیزها بود. روزنومه بفروشند. تبلیغات کنند و داس چکش بکشند آقا. رئیس‌شون رو که گرفتند چه جونی کندم آقا تا حالی‌شون کنم که دست ور دارند آقا. و از روی میز پرید پایین. - گفتم مگه باز هم هستند؟ - آره آقا، پس چی! یکی همین آقازاده که هنوز نیومده آقا. هر روز نیم ساعت تأخیر داره آقا. یکی هم مثل کلاس سه. - خوب چرا تا حالا پاکش نکردی؟ - به! آخه آدم درد دلشو واسه‌ی کی بگه؟ آخه آقا در میان تو روی آدم می‌گند جاسوس، مأمور! باهاش حرفم شده آقا. کتک و کتک‌کاری! و بعد یک سخنرانی که چه طور مدرسه را خراب کرده‌اند و اعتماد اهل محله را چه طور از بین برده‌اند که نه انجمنی، نه کمکی به بی‌بضاعت‌ها؛ و از این حرف ها. بعد از سخنرانی آقای ناظم دستمالم را دادم که آن عکس‌ها را پاک کند و بعد هم راه افتادم که بروم سراغ اتاق خودم. در اتاقم را که باز کردم، داشتم دماغم با بوی خاک نم کشیده‌اش اخت می‌کرد که آخرین معلم هم آمد. آمدم توی ایوان و با صدای بلند، جوری که در تمام مدرسه بشنوند، ناظم را صدا زدم و گفتم با قلم قرمز برای آقا یک ساعت تأخیر بگذارند. روز سوم باز اول وقت مدرسه بودم. هنوز از پشت دیوار نپیچیده بودم که صدای سوز و بریز بچه‌ها به پیشبازم آمد. تند کردم. پنج تا از بچه‌ها توی ایوان به خودشان می‌پیچیدند و ناظم ترکه‌ای به دست داشت و به نوبت به کف دست‌شان می‌زد. بچه‌ها التماس می‌کردند؛ گریه می‌کردند؛ اما دستشان را هم دراز می‌کردند. نزدیک بود داد بزنم یا با لگد بزنم و ناظم را پرت کنم آن طرف. پشتش به من بود و من را نمی‌دید. ناگهان زمزمه‌ای توی صف‌ها افتاد که یک مرتبه مرا به صرافت انداخت که در مقام مدیریت مدرسه، به سختی می‌شود ناظم را کتک زد. این بود که خشمم را فرو خوردم و آرام از پله‌ها رفتم بالا. ناظم، تازه متوجه من شده بود در همین حین دخالتم را کردم و خواهش کردم این بار همه‌شان را به من ببخشند. نمی‌دانم چه کار خطایی از آنها سر زده بود که ناظم را تا این حد عصبانی کرده بود. بچه‌ها سکسکه‌کنان رفتند توی صف‌ها و بعد زنگ را زدند و صف‌ها رفتند به کلاس‌ها و دنبالشان هم معلم‌ها که همه سر وقت حاضر بودند. نگاهی به ناظم انداختم که تازه حالش سر جا آمده بود و گفتم در آن حالی که داشت، ممکن بود گردن یک کدامشان را بشکند. که مرتبه براق شد: - اگه یک روز جلوشونو نگیرید سوارتون می‌شند آقا. نمی‌دونید چه قاطرهای چموشی شده‌اند آقا. مثل بچه مدرسه‌ای‌ها آقا آقا می‌کرد. موضوع را برگرداندم و احوال مادرش را پرسیدم. خنده، صورتش را از هم باز کرد و صدا زد فراش برایش آب بیاورد. یادم هست آن روز نیم ساعتی برای آقای ناظم صحبت کردم. پیرانه. و او جوان بود و زود می‌شد رامش کرد. بعد ازش خواستم که ترکه‌ها را بشکند و آن وقت من رفتم سراغ اتاق خودم. در همان هفته‌ی اول به کارها وارد شدم. فردای زمستان و نه تا بخاری زغال سنگی و روزی چهار بار آب آوردن و آب و جاروی اتاق‌ها با یک فراش جور در نمی‌آید. یک فراش دیگر از اداره ی فرهنگ خواستم که هر روز منتظر ورودش بودیم. بعد از ظهرها را نمی‌رفتم. روزهای اول با دست و دل لرزان، ولی سه چهار روزه جرأت پیدا کردم. احساس می‌کردم که مدرسه زیاد هم محض خاطر من نمی‌گردد. کلاس اول هم یکسره بود و به خاطر بچه‌های جغله دلهره‌ای نداشتم. در بیابان‌های اطراف مدرسه هم ماشینی آمد و رفت نداشت و گرچه پست و بلند بود اما به هر صورت از حیاط مدرسه که بزرگ‌تر بود. معلم ها هم، هر بعد از ظهری دو تاشان به نوبت می‌رفتند یک جوری باهم کنار آمده بودند. و ترسی هم از این نبود که بچه‌ها از علم و فرهنگ ثقل سرد بکنند. یک روز هم بازرس آمد و نیم ساعتی پیزر لای پالان هم گذاشتیم و چای و احترامات متقابل! و در دفتر بازرسی تصدیق کرد که مدرسه «با وجود عدم وسایل» بسیار خوب اداره می‌شود. بچه‌ها مدام در مدرسه زمین می‌خوردند، بازی می‌کردند، زمین می‌خوردند. مثل اینکه تاتوله خورده بودند. ساده‌ترین شکل بازی‌هایشان در ربع ساعت‌های تفریح، دعوا بود. فکر می‌کردم علت این همه زمین خوردن شاید این باشد که بیش‌ترشان کفش حسابی ندارند. آن‌ها هم که داشتند، بچه‌ننه بودند و بلد نبودند بدوند و حتی راه بروند. این بود که روزی دو سه بار، دست و پایی خراش بر می‌داشت. پرونده‌ی برق و تلفن مدرسه را از بایگانی بسیار محقر مدرسه بیرون کشیده بودم و خوانده بودم. اگر یک خرده می‌دویدی تا دو سه سال دیگر هم برق مدرسه درست می‌شد و هم تلفنش. دوباره سری به اداره ساختمان زدم و موضوع را تازه کردم و به رفقایی که دورادور در اداره‌ی برق و تلفن داشتم، یکی دو بار رو انداختم که اول خیال می‌کردند کار خودم را می‌خواهم به اسم مدرسه راه بیندازم و ناچار رها کردم. این قدر بود که ادای وظیفه‌ای می‌کرد. مدرسه آب نداشت. نه آب خوراکی و نه آب جاری. با هرزاب بهاره، آب انبار زیر حوض را می‌انباشتند که تلمبه‌ای سرش بود و حوض را با همان پر می‌کردند و خود بچه‌ها. اما برای آب خوردن دو تا منبع صد لیتری داشتیم از آهن سفید که مثل امامزاده‌ای یا سقاخانه‌ای دو قلو، روی چهار پایه کنار حیاط بود و روزی دو بار پر و خالی می‌شد. این آب را از همان باغی می‌آوردیم که ردیف کاج‌هایش روی آسمان، لکه‌ی دراز سیاه انداخته بود. البته فراش می‌آورد. با یک سطل بزرگ و یک آب‌پاش که سوراخ بود و تا به مدرسه می‌رسید، نصف شده بود. هر دو را از جیب خودم دادم تعمیر کردند. یک روز هم مالک مدرسه آمد. پیرمردی موقر و سنگین که خیال می‌کرد برای سرکشی به خانه‌ی مستأجرنشینش آمده. از در وارد نشده فریادش بلند شد و فحش را کشید به فراش و به فرهنگ که چرا بچه‌ها دیوار مدرسه را با زغال سیاه کرده‌اند واز همین توپ و تشرش شناختمش. کلی با او صحبت کردیم البته او دو برابر سن من را داشت. برایش چای هم آوردیم و با معلم‌ها آشنا شد و قول‌ها داد و رفت. کنه‌ای بود. درست یک پیرمرد. یک ساعت و نیم درست نشست. ماهی یک بار هم این برنامه را داشتند که بایست پیه‌اش را به تن می‌مالیدم. اما معلم‌ها. هر کدام یک ابلاغ بیست و چهار ساعته در دست داشتند، ولی در برنامه به هر کدام‌شان بیست ساعت درس بیشتر نرسیده بود. کم کم قرار شد که یک معلم از فرهنگ بخواهیم و به هر کدام‌شان هجده ساعت درس بدهیم، به شرط آن‌که هیچ بعد از ظهری مدرسه تعطیل نباشد. حتی آن که دانشگاه می‌رفت می‌توانست با هفته‌ای هجده ساعت درس بسازد. و دشوارترین کار همین بود که با کدخدامنشی حل شد و من یک معلم دیگر از فرهنگ خواستم. اواخر هفته‌ی دوم، فراش جدید آمد. مرد پنجاه ساله‌ای باریک و زبر و زرنگ که شب‌کلاه می‌گذاشت و لباس آبی می‌پوشید و تسبیح می‌گرداند و از هر کاری سر رشته داشت. آب خوردن را نوبتی می‌آوردند. مدرسه تر و تمیز شد و رونقی گرفت. فراش جدید سرش توی حساب بود. هر دو مستخدم با هم تمام بخاری‌ها را راه انداختند و یک کارگر هم برای کمک به آن‌ها آمد. فراش قدیمی را چهار روز پشت سر هم، سر ظهر می‌فرستادیم اداره‌ی فرهنگ و هر آن منتظر زغال بودیم. هنوز یک هفته از آمدن فراش جدید نگذشته بود که صدای همه‌ی معلم‌ها در آمده بود. نه به هیچ کدامشان سلام می‌کرد و نه به دنبال خرده فرمایش‌هایشان می‌رفت. درست است که به من سلام می‌کرد، اما معلم‌ها هم، لابد هر کدام در حدود من صاحب فضایل و عنوان و معلومات بودند که از یک فراش مدرسه توقع سلام داشته باشند. اما انگار نه انگار. بدتر از همه این که سر خر معلم‌ها بود. من که از همان اول، خرجم را سوا کرده بودم و آن‌ها را آزاد گذاشته بودم که در مواقع بیکاری در دفتر را روی خودشان ببندند و هر چه می‌خواهند بگویند و هر کاری می‌خواهند بکنند. اما او در فاصله‌ی ساعات درس، همچه که معلم‌ها می‌آمدند، می‌آمد توی دفتر و همین طوری گوشه‌ی اتاق می‌ایستاد و معلم‌ها کلافه می‌شدند. نه می‌توانستند شلکلک‌های معلمی‌شان را در حضور او کنار بگذارند و نه جرأت می‌کردند به او چیزی بگویند. بدزبان بود و از عهده‌ی همه‌شان بر می‌آمد. یکی دوبار دنبال نخود سیاه فرستاده بودندش. اما زرنگ بود و فوری کار را انجام می‌داد و بر می‌گشت. حسابی موی دماغ شده بود. ده سال تجربه این حداقل را به من آموخته بود که اگر معلم‌ها در ربع ساعت‌های تفریح نتوانند بخندند، سر کلاس، بچه‌های مردم را کتک خواهند زد. این بود که دخالت کردم. یک روز فراش جدید را صدا زدم. اول حال و احوالپرسی و بعد چند سال سابقه دارد و چند تا بچه و چه قدر می‌گیرد... که قضیه حل شد. سی صد و خرده‌ای حقوق می‌گرفت. با بیست و پنج سال سابقه. کار از همین جا خراب بود. پیدا بود که معلم‌ها حق دارند او را غریبه بدانند. نه دیپلمی، نه کاغذپاره‌ای، هر چه باشد یک فراش که بیشتر نبود! و تازه قلدر هم بود و حق هم داشت. اول به اشاره و کنایه و بعد با صراحت بهش فهماندم که گر چه معلم جماعت اجر دنیایی ندارد، اما از او که آدم متدین و فهمیده‌ای است بعید است و از این حرف‌ها... که یک مرتبه پرید توی حرفم که: - ای آقا! چه می‌فرمایید؟ شما نه خودتون این کاره‌اید و نه اینارو می‌شناسید. امروز می‌خواند سیگار براشون بخرم، فردا می‌فرستنم سراغ عرق. من این‌ها رو می‌شناسم. راست می‌گفت. زودتر از همه، او دندان‌های مرا شمرده بود. فهمیده بود که در مدرسه هیچ‌کاره‌ام. می‌خواستم کوتاه بیایم، ولی مدیر مدرسه بودن و در مقابل یک فراش پررو ساکت ماندن!... که خر خر کامیون زغال به دادم رسید. ترمز که کرد و صدا خوابید گفتم: - این حرف‌ها قباحت داره. معلم جماعت کجا پولش به عرق می‌رسه؟ حالا بدو زغال آورده‌اند. و همین طور که داشت بیرون می‌رفت، افزودم: - دو روز دیگه که محتاجت شدند و ازت قرض خواستند با هم رفیق می‌شید. و آمدم توی ایوان. در بزرگ آهنی مدرسه را باز کرده بودند و کامیون آمده بود تو و داشتند بارش را جلوی انبار ته حیاط خالی می‌کردند و راننده، کاغذی به دست ناظم داد که نگاهی به آن انداخت و مرا نشان داد که در ایوان بالا ایستاده بودم و فرستادش بالا. کاغذش را با سلام به دستم داد. بیجک زغال بود. رسید رسمی اداره‌ی فرهنگ بود در سه نسخه و روی آن ورقه‌ی ماشین شده‌ی «باسکول» که می‌گفت کامیون و محتویاتش جمعاً دوازده خروار است. اما رسیدهای رسمی اداری فرهنگ ساکت بودند. جای مقدار زغالی که تحویل مدرسه داده شده بود، در هر سه نسخه خالی بود. پیدا بود که تحویل گیرنده باید پرشان کند. همین کار را کردم. اوراق را بردم توی اتاق و با خودنویسم عدد را روی هر سه ورق نوشتم و امضا کردم و به دست راننده دادم که راه افتاد و از همان بالا به ناظم گفتم: - اگر مهر هم بایست زد، خودت بزن بابا. و رفتم سراغ کارم که ناگهان در باز شد و ناظم آمد تو؛ بیجک زغال دستش بود و: - مگه نفهمیدین آقا؟ مخصوصاً جاش رو خالی گذاشته بودند آقا... نفهمیده بودم. اما اگر هم فهمیده بودم، فرقی نمی‌کرد و به هر صورت از چنین کودنی نا به هنگام از جا در رفتم و به شدت گفتم: - خوب؟ - هیچ چی آقا.... رسم‌شون همینه آقا. اگه باهاشون کنار نیایید کارمونو لنگ می‌گذارند آقا... که از جا در رفتم. به چنین صراحتی مرا که مدیر مدرسه بودم در معامله شرکت می‌داد. و فریاد زدم: - عجب! حالا سرکار برای من تکلیف هم معین می‌کنید؟... خاک بر سر این فرهنگ با مدیرش که من باشم! برو ورقه رو بده دست‌شون، گورشون رو گم کنند. پدر سوخته‌ها... چنان فریاد زده بودم که هیچ کس در مدرسه انتظار نداشت. مدیر سر به زیر و پا به راهی بودم که از همه خواهش می‌کردم و حالا ناظم مدرسه، داشت به من یاد می‌داد که به جای نه خروار زغال مثلا هجده خروار تحویل بگیرم و بعد با اداره‌ی فرهنگ کنار بیایم. هی هی!.... تا ظهر هیچ کاری نتوانستم بکنم، جز این‌که چند بار متن استعفانامه‌ام را بنویسم و پاره کنم... قدم اول را این جور جلوی پای آدم می‌گذارند. بارندگی که شروع شد دستور دادم بخاری‌ها را از هفت صبح بسوزانند. بچه‌ها همیشه زود می‌آمدند. حتی روزهای بارانی. مثل این‌که اول آفتاب از خانه بیرون‌شان می‌کنند. یا ناهارنخورده. خیلی سعی کردم یک روز زودتر از بچه‌ها مدرسه باشم. اما عاقبت نشد که مدرسه را خالی از نفسِ به علم‌آلوده‌ی بچه‌ها استنشاق کنم. از راه که می‌رسیدند دور بخاری جمع می‌شدند و گیوه‌هاشان را خشک می‌کردند. و خیلی زود فهمیدم که ظهر در مدرسه ماندن هم مسأله کفش بود. هر که داشت نمی‌ماند.این قاعده در مورد معلم‌ها هم صدق می‌کرد اقلاً یک پول واکس جلو بودند. وقتی که باران می‌بارید تمام کوهپایه و بدتر از آن تمام حیاط مدرسه گل می‌شد. بازی و دویدن متوقف شده بود. مدرسه سوت و کور بود. این جا هم مسأله کفش بود. چشم اغلبشان هم قرمز بود. پیدا بود باز آن روز صبح یک فصل گریه کرده‌اند و در خانه‌شان علم صراطی بوده است. مدرسه داشت تخته می‌شد. عده‌ی غایب‌های صبح ده برابر شده بود و ساعت اول هیچ معلمی نمی‌توانست درس بدهد. دست‌های ورم‌کرده و سرمازده کار نمی‌کرد. حتی معلم کلاس اولمان هم می‌دانست که فرهنگ و معلومات مدارس ما صرفاً تابع تمرین است. مشق و تمرین. ده بار بیست بار. دست یخ‌کرده بیل و رنده را هم نمی‌تواند به کار بگیرد که خیلی هم زمخت‌اند و دست پر کن. این بود که به فکر افتادیم. فراش جدید واردتر از همه‌ی ما بود. یک روز در اتاق دفتر، شورامانندی داشتیم که البته او هم بود. خودش را کم‌کم تحمیل کرده بود. گفت حاضر است یکی از دُم‌کلفت‌های همسایه‌ی مدرسه را وادارد که شن برایمان بفرستد به شرط آن که ما هم برویم و از انجمن محلی برای بچه‌ها کفش و لباس بخواهیم. قرار شد خودش قضیه را دنبال کند که هفته‌ی آینده جلسه‌شان کجاست و حتی بخواهد که دعوت‌مانندی از ما بکنند. دو روز بعد سه تا کامیون شن آمد. دوتایش را توی حیاط مدرسه، خالی کردیم و سومی را دم در مدرسه، و خود بچه‌ها نیم ساعته پهنش کردند. با پا و بیل و هر چه که به دست می‌رسید. عصر همان روز ما را به انجمن دعوت کردند. خود من و ناظم باید می‌رفتیم. معلم کلاس چهارم را هم با خودمان بردیم. خانه‌ای که محل جلسه‌ی آن شب انجمن بود، درست مثل مدرسه، دور افتاده و تنها بود. قالی‌ها و کناره‌ها را به فرهنگ می‌آلودیم و می‌رفتیم. مثل این‌که سه تا سه تا روی هم انداخته بودند. اولی که کثیف شد دومی. به بالا که رسیدیم یک حاجی آقا در حال نماز خواندن بود. و صاحب‌خانه با لهجه‌ی غلیظ یزدی به استقبال‌مان آمد. همراهانم را معرفی کردم و لابد خودش فهمید مدیر کیست. برای ما چای آوردند. سیگارم را چاق کردم و با صاحب‌خانه از قالی‌هایش حرف زدیم. ناظم به بچه‌هایی می‌ماند که در مجلس بزرگترها خوابشان می‌گیرد و دل‌شان هم نمی‌خواست دست به سر شوند. سر اعضای انجمن باز شده بود. حاجی آقا صندوقدار بود. من و ناظم عین دو طفلان مسلم بودیم و معلم کلاس چهارم عین خولی وسطمان نشسته. اغلب اعضای انجمن به زبان محلی صحبت می‌کردند و رفتار ناشی داشتند. حتی یک کدامشان نمی‌دانستند که دست و پاهای خود را چه جور ضبط و ربط کنند. بلند بلند حرف می‌زدند. درست مثل این‌که وزارتخانه‌ی دواب سه تا حیوان تازه برای باغ وحش محله‌شان وارد کرده. جلسه که رسمی شد، صاحبخانه معرفی‌مان کرد و شروع کردند. مدام از خودشان صحبت می‌کردند از این‌که دزد دیشب فلان جا را گرفته و باید درخواست پاسبان شبانه کنیم و... همین طور یک ساعت حرف زدند و به مهام امور رسیدگی کردند و من و معلم کلاس چهارم سیگار کشیدیم. انگار نه انگار که ما هم بودیم. نوکرشان که آمد استکان‌ها را جمع کند، چیزی روی جلد اشنو نوشتم و برای صاحبخانه فرستادم که یک مرتبه به صرافت ما افتاد و اجازه خواست و: - آقایان عرضی دارند. بهتر است کارهای خودمان را بگذاریم برای بعد. مثلاً می‌خواست بفهماند که نباید همه‌ی حرف‌ها را در حضور ما زده باشند. و اجازه دادند معلم کلاس چهار شروع کرد به نطق و او هم شروع کرد که هر چه باشد ما زیر سایه‌ی آقایانیم و خوش‌آیند نیست که بچه‌هایی باشند که نه لباس داشته باشند و نه کفش درست و حسابی و از این حرف‌ها و مدام حرف می‌زد. ناظم هم از چُرت در آمد چیزهایی را که از حفظ کرده بود گفت و التماس دعا و کار را خراب کرد.تشری به ناظم زدم که گدابازی را بگذارد کنار و حالی‌شان کردم که صحبت از تقاضا نیست و گدایی. بلکه مدرسه دور افتاده است و مستراح بی در و پیکر و از این اباطیل... چه خوب شد که عصبانی نشدم. و قرار شد که پنج نفرشان فردا عصر بیایند که مدرسه را وارسی کنند و تشکر و اظهار خوشحالی و در آمدیم. در تاریکی بیابان هفت تا سواری پشت در خانه ردیف بودند و راننده‌ها توی یکی از آن‌ها جمع شده بودند و اسرار ارباب‌هاشان را به هم می‌گفتند. در این حین من مدام به خودم می‌گفتم من چرا رفتم؟ به من چه؟ مگر من در بی کفش و کلاهی‌شان مقصر بودم؟ می‌بینی احمق؟ مدیر مدرسه هم که باشی باید شخصیت و غرورت را لای زرورق بپیچی و طاق کلاهت بگذاری که اقلاً نپوسد. حتی اگر بخواهی یک معلم کوفتی باشی، نه چرا دور می‌زنی؟ حتی اگر یک فراش ماهی نود تومانی باشی، باید تا خرخره توی لجن فرو بروی.در همین حین که من در فکر بودم ناظم گفت: - دیدید آقا چه طور باهامون رفتار کردند؟ با یکی از قالی‌هاشون آقا تمام مدرسه رو می‌خرید. گفتم: - تا سر و کارت با الف.ب است به‌پا قیاس نکنی. خودخوری می‌آره. و معلم کلاس چهار گفت: - اگه فحشمون هم می‌دادند من باز هم راضی بودم، باید واقع‌بین بود. خدا کنه پشیمون نشند. بعد هم مدتی درد دل کردیم و تا اتوبوس برسد و سوار بشیم، معلوم شد که معلم کلاس چهار با زنش متارکه کرده و مادر ناظم را سرطانی تشخیص دادند. و بعد هم شب بخیر... دو روز تمام مدرسه نرفتم. خجالت می‌کشیدم توی صورت یک کدام‌شان نگاه کنم. و در همین دو روز حاجی آقا با دو نفر آمده بودند، مدرسه را وارسی و صورت‌برداری و ناظم می‌گفت که حتی بچه‌هایی هم که کفش و کلاهی داشتند پاره و پوره آمده بودند. و برای بچه‌ها کفش و لباس خریدند. روزهای بعد احساس کردم زن‌هایی که سر راهم لب جوی آب ظرف می‌شستند، سلام می‌کنند و یک بار هم دعای خیر یکی‌شان را از عقب سر شنیدم.اما چنان از خودم بدم آمده بود که رغبتم نمی‌شد به کفش و لباس‌هاشان نگاه کنم. قربان همان گیوه‌های پاره! بله، نان گدایی فرهنگ را نو نوار کرده بود. تازه از دردسرهای اول کار مدرسه فارغ شده بودم که شنیدم که یک روز صبح، یکی از اولیای اطفال آمد. بعد از سلام و احوالپرسی دست کرد توی جیبش و شش تا عکس در آورد، گذاشت روی میزم. شش تا عکس زن لخت. لخت لخت و هر کدام به یک حالت. یعنی چه؟ نگاه تندی به او کردم. آدم مرتبی بود. اداری مانند. کسر شأن خودم می‌دانستم که این گوشه‌ی از زندگی را طبق دستور عکاس‌باشی فلان جنده‌خانه‌ی بندری ببینم. اما حالا یک مرد اتو کشیده‌ی مرتب آمده بود و شش تا از همین عکس‌ها را روی میزم پهن کرده بود و به انتظار آن که وقاحت عکس‌ها چشم‌هایم را پر کند داشت سیگار چاق می‌کرد. حسابی غافلگیر شده بودم... حتماً تا هر شش تای عکس‌ها را ببینم، بیش از یک دقیقه طول کشید. همه از یک نفر بود. به این فکر گریختم که الان هزار ها یا میلیون ها نسخه‌ی آن، توی جیب چه جور آدم‌هایی است و در کجاها و چه قدر خوب بود که همه‌ی این آدم‌ها را می‌شناختم یا می‌دیدم. بیش ازین نمی‌شد گریخت. یارو به تمام وزنه وقاحتش، جلوی رویم نشسته بود. سیگاری آتش زدم و چشم به او دوختم. کلافه بود و پیدا بود برای کتک‌کاری هم آماده باشد. سرخ شده بود و داشت در دود سیگارش تکیه‌گاهی برای جسارتی که می‌خواست به خرج بدهد می‌جست. عکس‌ها را با یک ورقه از اباطیلی که همان روز سیاه کرده بودم، پوشاندم و بعد با لحنی که دعوا را با آن شروع می‌کنند؛ پرسیدم: - خوب، غرض؟ و صدایم توی اتاق پیچید. حرکتی از روی بیچارگی به خودش داد و همه‌ی جسارت‌ها را با دستش توی جیبش کرد و آرام‌تر از آن چیزی که با خودش تو آورده بود، گفت: - چه عرض کنم؟... از معلم کلاس پنج تون بپرسید. که راحت شدم و او شروع کرد به این که «این چه فرهنگی است؟ خراب بشود. پس بچه‌های مردم با چه اطمینانی به مدرسه بیایند؟ و از این حرف‌ها... خلاصه این آقا معلم کاردستی کلاس پنجم، این عکس‌ها را داده به پسر آقا تا آن‌ها را روی تخته سه لایی بچسباند و دورش را سمباده بکشد و بیاورد. به هر صورت معلم کلاس پنج بی‌گدار به آب زده. و حالا من چه بکنم؟ به او چه جوابی بدهم؟ بگویم معلم را اخراج می‌کنم؟ که نه می‌توانم و نه لزومی دارد. او چه بکند؟ حتماً در این شهر کسی را ندارد که به این عکس‌ها دلخوش کرده. ولی آخر چرا این جور؟ یعنی این قدر احمق است که حتی شاگردهایش را نمی‌شناسد؟... پاشدم ناظم را صدا بزنم که خودش آمده بود بالا، توی ایوان منتظر ایستاده بود. من آخرین کسی بودم که از هر اتفاقی در مدرسه خبردار می‌شدم. حضور این ولی طفل گیجم کرده بود که چنین عکس‌هایی را از توی جیب پسرش، و لابد به همین وقاحتی که آن‌ها را روی میز من ریخت، در آورده بوده. وقتی فهمید هر دو در مانده‌ایم سوار بر اسب شد که اله می‌کنم و بله می‌کنم، در مدرسه را می‌بندم، و از این جفنگیات.... حتماً نمی‌دانست که اگر در هر مدرسه بسته بشود، در یک اداره بسته شده است. اما من تا او بود نمی‌توانستم فکرم را جمع کنم. می‌خواست پسرش را بخواهیم تا شهادت بدهد و چه جانی کندیم تا حالیش کنیم که پسرش هر چه خفت کشیده، بس است و وعده‌ها دادیم که معلمش را دم خورشید کباب کنیم و از نان خوردن بیندازیم. یعنی اول ناظم شروع کرد که از دست او دل پری داشت و من هم دنبالش را گرفتم. برای دک کردن او چاره‌ای جز این نبود. و بعد رفت، ما دو نفری ماندیم با شش تا عکس زن لخت. حواسم که جمع شد به ناظم سپردم صدایش را در نیاورد و یک هفته‌ی تمام مطلب را با عکس‌ها، توی کشوی میزم قفل کردم و بعد پسرک را صدا زدم. نه عزیزدُردانه می‌نمود و نه هیچ جور دیگر. داد می‌زد که از خانواده‌ی عیال‌واری است. کم‌خونی و فقر. دیدم معلمش زیاد هم بد تشخیص نداده. یعنی زیاد بی‌گدار به آب نزده. گفتم: - خواهر برادر هم داری؟ - آ... آ...آقا داریم آقا. - چند تا؟ - آ... آقا چهار تا آقا. - عکس‌ها رو خودت به بابات نشون دادی؟ - نه به خدا آقا... به خدا قسم... - پس چه طور شد؟ و دیدم از ترس دارد قالب تهی می‌کند. گرچه چوب‌های ناظم شکسته بود، اما ترس او از من که مدیر باشم و از ناظم و از مدرسه و از تنبیه سالم مانده بود. - نترس بابا. کاریت نداریم. تقصیر آقا معلمه که عکس‌ها رو داده... تو کار بدی نکردی بابا جان. فهمیدی؟ اما می‌خواهم ببینم چه طور شد که عکس‌ها دست بابات افتاد. - آ.. آ... آخه آقا... آخه... می‌دانستم که باید کمکش کنم تا به حرف بیاید. گفتم: - می‌دونی بابا؟ عکس‌هام چیز بدی نبود. تو خودت فهمیدی چی بود؟ - آخه آقا...نه آقا.... خواهرم آقا... خواهرم می‌گفت... - خواهرت؟ از تو کوچک‌تره؟ - نه آقا. بزرگ‌تره. می‌گفتش که آقا... می‌گفتش که آقا... هیچ چی سر عکس‌ها دعوامون شد. دیگر تمام بود. عکس‌ها را به خواهرش نشان داده بود که لای دفترچه پر بوده از عکس آرتیست‌ها. به او پز داده بوده. اما حاضر نبوده، حتی یکی از آن‌ها را به خواهرش بدهد. آدم مورد اعتماد معلم باشد و چنین خبطی بکند؟ و تازه جواب معلم را چه بدهد؟ ناچار خواهر او را لو داده بوده. بعد از او معلم را احضار کردم. علت احضار را می‌دانست. و داد می‌زد که چیزی ندارد بگوید. پس از یک هفته مهلت، هنوز از وقاحتی که من پیدا کرده بودم، تا از آدم خلع سلاح‌شده‌ای مثل او، دست بر ندارم، در تعجب بود. به او سیگار تعارف کردم و این قصه را برایش تعریف کردم که در اوایل تأسیس وزارت معارف، یک روز به وزیر خبر می‌دهند که فلان معلم با فلان بچه روابطی دارد. وزیر فوراً او را می‌خواهد و حال و احوال او را می‌پرسد و این‌که چرا تا به حال زن نگرفته و ناچار تقصیر گردن بی‌پولی می‌افتد و دستور که فلان قدر به او کمک کنند تا عروسی راه بیندازد و خود او هم دعوت بشود و قضیه به همین سادگی تمام می‌شود. و بعد گفتم که خیلی جوان‌ها هستند که نمی‌توانند زن بگیرند و وزرای فرهنگ هم این روزها گرفتار مصاحبه‌های روزنامه‌ای و رادیویی هستند. اما در نجیب‌خانه‌ها که باز است و ازین مزخرفات... و هم‌دردی و نگذاشتم یک کلمه حرف بزند. بعد هم عکس را که توی پاکت گذاشته بودم، به دستش دادم و وقاحت را با این جمله به حد اعلا رساندم که: - اگر به تخته نچسبونید، ضررشون کم‌تره. تا حقوقم به لیست اداره‌ی فرهنگ برسه، سه ماه طول کشید. فرهنگی‌های گداگشنه و خزانه‌ی خالی و دست‌های از پا درازتر! اما خوبیش این بود که در مدرسه‌ی ما فراش جدیدمان پولدار بود و به همه‌شان قرض داد. کم کم بانک مدرسه شده بود. از سیصد و خرده‌ای تومان که می‌گرفت، پنجاه تومان را هم خرج نمی‌کرد. نه سیگار می‌کشید و نه اهل سینما بود و نه برج دیگری داشت. از این گذشته، باغبان یکی از دم‌کلفت‌های همان اطراف بود و باغی و دستگاهی و سور و ساتی و لابد آشپزخانه‌ی مرتبی. خیلی زود معلم‌ها فهمیدند که یک فراش پولدار خیلی بیش‌تر به درد می‌خورد تا یک مدیر بی‌بو و خاصیت. این از معلم‌ها. حقوق مرا هم هنوز از مرکز می‌دادند. با حقوق ماه بعد هم اسم مرا هم به لیست اداره منتقل کردند. درین مدت خودم برای خودم ورقه انجام کار می‌نوشتم و امضا می‌کردم و می‌رفتم از مدرسه‌ای که قبلاً در آن درس می‌دادم، حقوقم را می‌گرفتم. سر و صدای حقوق که بلند می‌شد معلم‌ها مرتب می‌شدند و کلاس ماهی سه چهار روز کاملاً دایر بود. تا ورقه‌ی انجام کار به دستشان بدهم. غیر از همان یک بار - در اوایل کار- که برای معلم حساب پنج و شش قرمز توی دفتر گذاشتیم، دیگر با مداد قرمز کاری نداشتیم و خیال همه‌شان راحت بود. وقتی برای گرفتن حقوقم به اداره رفتم، چنان شلوغی بود که به خودم گفتم کاش اصلاً حقوقم را منتقل نکرده بودم. نه می‌توانستم سر صف بایستم و نه می‌توانستم از حقوقم بگذرم. تازه مگر مواجب‌بگیر دولت چیزی جز یک انبان گشاده‌ی پای صندوق است؟..... و اگر هم می‌ماندی با آن شلوغی باید تا دو بعداز ظهر سر پا بایستی. همه‌ی جیره‌خوارهای اداره بو برده بودند که مدیرم. و لابد آن‌قدر ساده لوح بودند که فکر کنند روزی گذارشان به مدرسه‌ی ما بیفتد. دنبال سفته‌ها می‌گشتند، به حسابدار قبلی فحش می‌دادند، التماس می‌کردند که این ماه را ندیده بگیرید و همه‌ی حق و حساب‌دان شده بودند و یکی که زودتر از نوبت پولش را می‌گرفت صدای همه در می‌آمد. در لیست مدرسه، بزرگ‌ترین رقم مال من بود. درست مثل بزرگ‌ترین گناه در نامه‌ی عمل. دو برابر فراش جدیدمان حقوق می‌گرفتم. از دیدن رقم‌های مردنی حقوق دیگران چنان خجالت کشیدم که انگار مال آن‌ها را دزدیده‌ام. و تازه خلوت که شد و ده پانزده تا امضا که کردم، صندوق‌دار چشمش به من افتاد و با یک معذرت، شش صد تومان پول دزدی را گذاشت کف دستم... مرده شور! هنوز برف اول نباریده بود که یک روز عصر، معلم کلاس چهار رفت زیر ماشین. زیر یک سواری. مثل همه‌ی عصرها من مدرسه نبودم. دم غروب بود که فراش قدیمی مدرسه دم در خونه‌مون، خبرش را آورد. که دویدم به طرف لباسم و تا حاضر بشوم، می‌شنیدم که دارد قضیه را برای زنم تعریف می‌کند. ماشین برای یکی از آمریکایی‌ها بوده. باقیش را از خانه که در آمدیم برایم تعریف کرد. گویا یارو خودش پشت فرمون بوده و بعد هم هول شده و در رفته. بچه‌ها خبر را به مدرسه برگردانده‌اند و تا فراش و زنش برسند، جمعیت و پاسبان‌ها سوارش کرده بودند و فرستاده بوده‌اند مریض‌خانه. به اتوبوس که رسیدم، دیدم لاک پشت است. فراش را مرخص کردم و پریدم توی تاکسی. اول رفتم سراغ پاسگاه جدید کلانتری. تعاریف تکه و پاره‌ای از پرونده مطلع بود. اما پرونده تصریحی نداشت که راننده که بوده. اما هیچ کس نمی‌دانست عاقبت چه بلایی بر سر معلم کلاس چهار ما آمده است. کشیک پاسگاه همین قدر مطلع بود که درین جور موارد «طبق جریان اداری» اول می‌روند سرکلانتری، بعد دایره‌ی تصادفات و بعد بیمارستان. اگر آشنا در نمی‌آمدیم، کشیک پاسگاه مسلماً نمی‌گذاشت به پرونده نگاه چپ بکنم. احساس کردم میان اهل محل کم‌کم دارم سرشناس می‌شوم. و از این احساس خنده‌ام گرفت. ساعت ۸ دم در بیمارستان بودم، اگر سالم هم بود حتماً یه چیزیش شده بود. همان طور که من یه چیزیم می‌شد. روی در بیمارستان نوشته شده بود: «از ساعت ۷ به بعد ورود ممنوع». در زدم. از پشت در کسی همین آیه را صادر کرد. دیدم فایده ندارد و باید از یک چیزی کمک بگیرم. از قدرتی، از مقامی، از هیکلی، از یک چیزی. صدایم را کلفت کردم و گفتم:« من...» می‌خواستم بگویم من مدیر مدرسه‌ام. ولی فوراً پشیمان شدم. یارو لابد می‌گفت مدیر مدرسه کدام سگی است؟ این بود با کمی مکث و طمطراق فراوان جمله‌ام را این طور تمام کردم: - ...بازرس وزارت فرهنگم. که کلون صدایی کرد و لای در باز شد. یارو با چشم‌هایش سلام کرد. رفتم تو و با همان صدا پرسیدم: - این معلمه مدرسه که تصادف کرده... تا آخرش را خواند. یکی را صدا زد و دنبالم فرستاد که طبقه‌ی فلان، اتاق فلان. از حیاط به راهرو و باز به حیاط دیگر که نصفش را برف پوشانده بود و من چنان می‌دویدم که یارو از عقب سرم هن هن می‌کرد. طبقه‌ی اول و دوم و چهارم. چهار تا پله یکی. راهرو تاریک بود و پر از بوهای مخصوص بود. هن هن کنان دری را نشان داد که هل دادم و رفتم تو. بو تندتر بود و تاریکی بیشتر. تالاری بود پر از تخت و جیرجیر کفش و خرخر یک نفر. دور یک تخت چهار نفر ایستاده بودند. حتماً خودش بود. پای تخت که رسیدم، احساس کردم همه‌ی آنچه از خشونت و تظاهر و ابهت به کمک خواسته بودم آب شد و بر سر و صورتم راه افتاد. و این معلم کلاس چهارم مدرسه‌ام بود. سنگین و با شکم بر آمده دراز کشیده بود. خیلی کوتاه‌تر از زمانی که سر پا بود به نظرم آمد. صورت و سینه‌اش از روپوش چرک‌مُرد بیرون بود. صورتش را که شسته بودند کبود کبود بود، درست به رنگ جای سیلی روی صورت بچه‌ها. مرا که دید، لبخند و چه لبخندی! شاید می‌خواست بگوید مدرسه‌ای که مدیرش عصرها سر کار نباشد، باید همین جورها هم باشد. خنده توی صورت او همین طور لرزید و لرزید تا یخ زد. «آخر چرا تصادف کردی؟...» مثل این که سوال را ازو کردم. اما وقتی که دیدم نمی‌تواند حرف بزند و به جای هر جوابی همان خنده‌ی یخ‌بسته را روی صورت دارد، خودم را به عنوان او دم چک گرفتم. «آخه چرا؟ چرا این هیکل مدیر کلی را با خودت این قد این ور و آن ور می‌بری تا بزنندت؟ تا زیرت کنند؟ مگر نمی‌دانستی که معلم حق ندارد این قدر خوش‌هیکل باشد؟ آخر چرا تصادف کردی؟» به چنان عتاب و خطابی این‌ها را می‌گفتم که هیچ مطمئن نیستم بلند بلند به خودش نگفته باشم. و یک مرتبه به کله‌ام زد که «مبادا خودت چشمش زده باشی؟» و بعد: «احمق خاک بر سر! بعد از سی و چند سال عمر، تازه خرافاتی شدی!» و چنان از خودم بیزاریم گرفت که می‌خواستم به یکی فحش بدهم، کسی را بزنم. که چشمم به دکتر کشیک افتاد. - مرده شور این مملکتو ببره. ساعت چهار تا حالا از تن این مرد خون می‌ره. حیفتون نیومد؟... دستی روی شانه‌ام نشست و فریادم را خواباند. برگشتم پدرش بود. او هم می‌خندید. دو نفر دیگر هم با او بودند. همه دهاتی‌وار؛ همه خوش قد و قواره. حظ کردم! آن دو تا پسرهایش بودند یا برادرزاده‌هایش یا کسان دیگرش. تازه داشت گل از گلم می‌شکفت که شنیدم: - آقا کی باشند؟ این راهم دکتر کشیک گفت که من باز سوار شدم: - مرا می‌گید آقا؟ من هیشکی. یک آقا مدیر کوفتی. این هم معلمم. که یک مرتبه عقل هی زد و «پسر خفه شو» و خفه شدم. بغض توی گلویم بود. دلم می‌خواست یک کلمه دیگر بگوید. یک کنایه بزند... نسبت به مهارت هیچ دکتری تا کنون نتوانسته‌ام قسم بخورم. دستش را دراز کرد که به اکراه فشار دادم و بعد شیشه‌ی بزرگی را نشانم داد که وارونه بالای تخت آویزان بود و خرفهمم کرد که این جوری غذا به او می‌رسانند و عکس هم گرفته‌اند و تا فردا صبح اگر زخم‌ها چرک نکند، جا خواهند انداخت و گچ خواهند کرد. که یکی دیگر از راه رسید. گوشی به دست و سفید پوش و معطر. با حرکاتی مثل آرتیست سینما. سلامم کرد. صدایش در ته ذهنم چیزی را مختصر تکانی داد. اما احتیاجی به کنجکاوی نبود. یکی از شاگردهای نمی‌دانم چند سال پیشم بود. خودش خودش را معرفی کرد. آقای دکتر...! عجب روزگاری! هر تکه از وجودت را با مزخرفی از انبان مزخرفاتت، مثل ذره‌ای روزی در خاکی ریخته‌ای که حالا سبز کرده. چشم داری احمق. این تویی که روی تخت دراز کشیده‌ای. ده سال آزگار از پلکان ساعات و دقایق عمرت هر لحظه یکی بالا رفته و تو فقط خستگی این بار را هنوز در تن داری. این جوجه‌فکلی و جوجه‌های دیگر که نمی‌شناسی‌شان، همه از تخمی سر در آورده‌اند که روزی حصار جوانی تو بوده و حالا شکسته و خالی مانده. دستش را گرفتم و کشیدمش کناری و در گوشش هر چه بد و بی‌راه می‌دانستم، به او و همکارش و شغلش دادم. مثلاً می‌خواستم سفارش معلم کلاس چهار مدرسه‌ام را کرده باشم. بعد هم سری برای پدر تکان دادم و گریختم. از در که بیرون آمدم، حیاط بود و هوای بارانی. از در بزرگ که بیرون آمدم به این فکر می‌کردم که «اصلا به تو چه؟ اصلاً چرا آمدی؟ می‌خواستی کنجکاوی‌ات را سیرکنی؟» و دست آخر به این نتیجه رسیدم که «طعمه‌ای برای میزنشین‌های شهربانی و دادگستری به دست آمده و تو نه می‌توانی این طعمه را از دستشان بیرون بیاوری و نه هیچ کار دیگری می‌توانی بکنی...» و داشتم سوار تاکسی می‌شدم تا برگردم خانه که یک دفعه به صرافت افتادم که اقلاً چرا نپرسیدی چه بلایی به سرش آمده؟» خواستم عقب‌گرد کنم، اما هیکل کبود معلم کلاس چهارم روی تخت بود و دیدم نمی‌توانم. خجالت می‌کشیدم و یا می‌ترسیدم. آن شب تا ساعت دو بیدار بودم و فردا یک گزارش مفصل به امضای مدیر مدرسه و شهادت همه‌ی معلم‌ها برای اداره‌ی فرهنگ و کلانتری محل و بعد هم دوندگی در اداره‌ی بیمه و قرار بر این که روزی نه تومان بودجه برای خرج بیمارستان او بدهند و عصر پس از مدتی رفتم مدرسه و کلاس‌ها را تعطیل کردم و معلم‌ها و بچه‌های ششم را فرستادم عیادتش و دسته گل و ازین بازی‌ها... و یک ساعتی در مدرسه تنها ماندم و فارغ از همه چیز برای خودم خیال بافتم.... و فردا صبح پدرش آمد سلام و احوالپرسی و گفت یک دست و یک پایش شکسته و کمی خونریزی داخل مغز و از طرف یارو آمریکاییه آمده‌اند عیادتش و وعده و وعید که وقتی خوب شد، در اصل چهار استخدامش کنند و با زبان بی‌زبانی حالیم کرد که گزارش را بیخود داده‌ام و حالا هم داده‌ام، دنبالش نکنم و رضایت طرفین و کاسه‌ی از آش داغ‌تر و از این حرف‌ها... خاک بر سر مملکت. اوایل امر توجهی به بچه‌ها نداشتم. خیال می‌کردم اختلاف سِنی میان‌مان آن قدر هست که کاری به کار همدیگر نداشته باشیم. همیشه سرم به کار خودم بود. در دفتر را می‌بستم و در گرمای بخاری دولت قلم صد تا یک غاز می‌زدم. اما این کار مرتب سه چهار هفته بیش‌تر دوام نکرد. خسته شدم. ناچار به مدرسه بیشتر می‌رسیدم. یاد روزهای قدیمی با دوستان قدیمی به خیر چه آدم‌های پاک و بی‌آلایشی بودند، چه شخصیت‌های بی‌نام و نشانی و هر کدام با چه زبانی و با چه ادا و اطوارهای مخصوص به خودشان و این جوان‌های چلفته‌ای. چه مقلدهای بی‌دردسری برای فرهنگی‌مابی! نه خبری از دیروزشان داشتند و نه از املاک تازه‌ای که با هفتاد واسطه به دست‌شان داده بودند، چیزی سرشان می‌شد. بدتر از همه بی‌دست و پایی‌شان بود. آرام و مرتب درست مثل واگن شاه عبدالعظیم می‌آمدند و می‌رفتند. فقط بلد بودند روزی ده دقیقه دیرتر بیایند و همین. و از این هم بدتر تنگ‌نظری‌شان بود. سه بار شاهد دعواهایی بودم که سر یک گلدان میخک یا شمعدانی بود. بچه‌باغبان‌ها زیاد بودند و هر کدام‌شان حداقل ماهی یک گلدان میخک یا شمعدانی می‌آوردند که در آن برف و سرما نعمتی بود. اول تصمیم گرفتم، مدرسه را با آن‌ها زینت دهم. ولی چه فایده؟ نه کسی آب‌شان می‌داد و نه مواظبتی. و باز بدتر از همه‌ی این‌ها، بی‌شخصیتی معلم‌ها بود که درمانده‌ام کرده بود. دو کلمه نمی‌توانستند حرف بزنند. عجب هیچ‌کاره‌هایی بودند! احساس کردم که روز به روز در کلاس‌ها معلم‌ها به جای دانش‌آموزان جاافتاده‌تر می‌شوند. در نتیجه گفتم بیش‌تر متوجه بچه‌ها باشم. آن‌ها که تنها با ناظم سر و کار داشتند و مثل این بود که به من فقط یک سلام نیمه‌جویده بدهکارند. با این همه نومیدکننده نبودند. توی کوچه مواظب‌شان بودم. می‌خواستم حرف و سخن‌ها و درد دل‌ها و افکارشان را از یک فحش نیمه‌کاره یا از یک ادای نیمه‌تمام حدس بزنم، که سلام‌نکرده در می‌رفتند. خیلی کم تنها به مدرسه می‌آمدند. پیدا بود که سر راه همدیگر می‌ایستند یا در خانه‌ی یکدیگر می‌روند. سه چهار نفرشان هم با اسکورت می‌آمدند. از بیست سی نفری که ناهار می‌ماندند، فقط دو نفرشان چلو خورش می‌آوردند؛ فراش اولی مدرسه برایم خبر می‌آورد. بقیه گوشت‌کوبیده، پنیر گردوئی، دم پختکی و از این جور چیزها. دو نفرشان هم بودند که نان سنگک خالی می‌آوردند. برادر بودند. پنجم و سوم. صبح که می‌آمدند، جیب‌هاشان باد کرده بود. سنگک را نصف می‌کردند و توی جیب‌هاشان می‌تپاندند و ظهر می‌شد، مثل آن‌هایی که ناهارشان را در خانه می‌خورند، می‌رفتند بیرون. من فقط بیرون رفتن‌شان را می‌دیدم. اما حتی همین‌ها هر کدام روزی، یکی دو قران از فراش مدرسه خرت و خورت می‌خریدند. از همان فراش قدیمی مدرسه که ماهی پنج تومان سرایداریش را وصول کرده بودم. هر روز که وارد اتاقم می‌شدم پشت سر من می‌آمد بارانی‌ام را بر می‌داشت و شروع می‌کرد به گزارش دادن، که دیروز باز دو نفر از معلم‌ها سر یک گلدان دعوا کرده‌اند یا مأمور فرماندار نظامی آمده یا دفتردار عوض شده و از این اباطیل... پیدا بود که فراش جدید هم در مطالبی که او می‌گفت، سهمی دارد. یک روز در حین گزارش دادن، اشاره‌ای کرد به این مطلب که دیروز عصر یکی از بچه‌های کلاس چهار دو تا کله قند به او فروخته است. درست مثل اینکه سر کلاف را به دستم داده باشد پرسیدم: - چند؟ - دو تومنش دادم آقا. - زحمت کشیدی. نگفتی از کجا آورده؟ - من که ضامن بهشت و جهنمش نبودم آقا. بعد پرسیدم: - چرا به آقای ناظم خبر ندادی؟ می‌دانستم که هم او و هم فراش جدید، ناظم را هووی خودشان می‌دانند و خیلی چیزهاشان از او مخفی بود. این بود که میان من و ناظم خاصه‌خرجی می‌کردند. در جوابم همین طور مردد مانده بود که در باز شد و فراش جدید آمد تو. که: - اگه خبرش می‌کرد آقا بایست سهمش رو می‌داد... اخمم را درهم کشیدم و گفتم: - تو باز رفتی تو کوک مردم! اونم این جوری سر نزده که نمی‌آیند تو اتاق کسی، پیرمرد! و بعد اسم پسرک را ازشان پرسیدم و حالی‌شان کردم که چندان مهم نیست و فرستادمشان برایم چای بیاورند. بعد کارم را زودتر تمام کردم و رفتم به اتاق دفتر احوالی از مادر ناظم پرسیدم و به هوای ورق زدن پرونده‌ها فهمیدم که پسرک شاگرد دوساله است و پدرش تاجر بازار. بعد برگشتم به اتاقم. یادداشتی برای پدر نوشتم که پس فردا صبح، بیاید مدرسه و دادم دست فراش جدید که خودش برساند و رسیدش را بیاورد. و پس فردا صبح یارو آمد. باید مدیر مدرسه بود تا دانست که اولیای اطفال چه راحت تن به کوچک‌ترین خرده‌فرمایش‌های مدرسه می‌دهند. حتم دارم که اگر از اجرای ثبت هم دنبال‌شان بفرستی به این زودی‌ها آفتابی نشوند. چهل و پنج ساله مردی بود با یخه‌ی بسته بی‌کراوات و پالتویی که بیش‌تر به قبا می‌ماند. و خجالتی می‌نمود. هنوز ننشسته، پرسیدم: - شما دو تا زن دارید آقا؟ درباره‌ی پسرش برای خودم پیش‌گویی‌هایی کرده بودم و گفتم این طوری به او رودست می‌زنم. پیدا بود که از سؤالم زیاد یکه نخورده است. گفتم برایش چای آوردند و سیگاری تعارفش کردم که ناشیانه دود کرد از ترس این که مبادا جلویم در بیاید که - به شما چه مربوط است و از این اعتراض‌ها - امانش ندادم و سؤالم را این جور دنبال کردم: - البته می‌بخشید. چون لابد به همین علت بچه شما دو سال در یک کلاس مانده. شروع کرده بودم برایش یک میتینگ بدهم که پرید وسط حرفم: - به سر شما قسم، روزی چهار زار پول تو جیبی داره آقا. پدرسوخته‌ی نمک به حروم...! حالیش کردم که علت، پول تو جیبی نیست و خواستم که عصبانی نشود و قول گرفتم که اصلاً به روی پسرش هم نیاورد و آن وقت میتینگم را برایش دادم که لابد پسر در خانه مهر و محبتی نمی‌بیند و غیب‌گویی‌های دیگر... تا عاقبت یارو خجالتش ریخت و سرِ درد دلش باز شد که عفریته زن اولش همچه بوده و همچون بوده و پسرش هم به خودش برده و کی طلاقش داده و از زن دومش چند تا بچه دارد و این نره‌خر حالا باید برای خودش نان‌آور شده باشد و زنش حق دارد که با دو تا بچه‌ی خرده‌پا به او نرسد... من هم کلی برایش صحبت کردم. چایی دومش را هم سر کشید و قول‌هایش را که داد و رفت، من به این فکر افتادم که «نکند علمای تعلیم و تربیت هم، همین جورها تخم دوزرده می‌کنند!» یک روز صبح که رسیدم، ناظم هنوز نیامده بود. از این اتفاق‌ها کم می‌افتاد. ده دقیقه‌ای از زنگ می‌گذشت و معلم‌ها در دفتر سرگرم اختلاط بودند. خودم هم وقتی معلم بودم به این مرض دچار بودم. اما وقتی مدیر شدم تازه فهمیدم که معلم‌ها چه لذتی می‌برند. حق هم داشتند. آدم وقتی مجبور باشد شکلکی را به صورت بگذارد که نه دیگران از آن می‌خندند و نه خود آدم لذتی می‌برد، پیداست که رفع تکلیف می‌کند. زنگ را گفتم زدند و بچه‌ها سر کلاس رفتند. دو تا از کلاس‌ها بی‌معلم بود. یکی از ششمی‌ها را فرستادم سر کلاس سوم که برای‌شان دیکته بگوید و خودم رفتم سر کلاس چهار. مدیر هم که باشی، باز باید تمرین کنی که مبادا فوت و فن معلمی از یادت برود. در حال صحبت با بچه‌ها بودم که فراش خبر آورد که خانمی توی دفتر منتظرم است. خیال کردم لابد همان زنکه‌ی بیکاره‌ای است که هفته‌ای یک بار به هوای سرکشی، به وضع درس و مشق بچه‌اش سری می‌زند. زن سفیدرویی بود با چشم‌های درشت محزون و موی بور. بیست و پنج ساله هم نمی‌نمود. اما بچه‌اش کلاس سوم بود. روز اول که دیدمش لباس نارنجی به تن داشت و تن بزک کرده بود. از زیارت من خیلی خوشحال شد و از مراتب فضل و ادبم خبر داشت. خیلی ساده آمده بود تا با دو تا مرد حرفی زده باشد. آن طور که ناظم خبر می‌داد، یک سالی طلاق گرفته بود و روی هم رفته آمد و رفتنش به مدرسه باعث دردسر بود. وسط بیابان و مدرسه‌ای پر از معلم‌های عزب و بی‌دست و پا و یک زن زیبا... ناچار جور در نمی‌آمد. این بود که دفعات بعد دست به سرش می‌کردم، اما از رو نمی‌رفت. سراغ ناظم و اتاق دفتر را می‌گرفت و صبر می‌کرد تا زنگ را بزنند و معلم‌ها جمع بشوند و لابد حرف و سخنی و خنده‌ای و بعد از معلم کلاس سوم سراغ کار و بار و بچه‌اش را می‌گرفت و زنگ بعد را که می‌زدند، خداحافظی می‌کرد و می‌رفت. آزاری نداشت. با چشم‌هایش نفس معلم‌ها را می‌برید. و حالا باز هم همان زن بود و آمده بود و من تا از پلکان پایین بروم در ذهنم جملات زننده‌ای ردیف می‌کردم، تا پایش را از مدرسه ببرد که در را باز کردم و سلام... عجب! او نبود. دخترک یکی دو ساله‌ای بود با دهان گشاد و موهای زبرش را به زحمت عقب سرش گلوله کرده بود و بفهمی نفهمی دستی توی صورتش برده بود. روی هم رفته زشت نبود. اما داد می‌زد که معلم است. گفتم که مدیر مدرسه‌ام و حکمش را داد دستم که دانشسرا دیده بود و تازه استخدام شده بود. برایمان معلم فرستاده بودند. خواستم بگویم «مگر رئیس فرهنگ نمی‌داند که این جا بیش از حد مرد است» ولی دیدم لزومی ندارد و فکر کردم این هم خودش تنوعی است. به هر صورت زنی بود و می‌توانست محیط خشن مدرسه را که به طرز ناشیانه‌ای پسرانه بود، لطافتی بدهد و خوش‌آمد گفتم و چای آوردند که نخورد و بردمش کلاس‌های سوم و چهارم را نشانش دادم که هر کدام را مایل است، قبول کند و صحبت از هجده ساعت درس که در انتظار او بود و برگشتیم به دفتر .پرسید غیر از او هم، معلم زن داریم. گفتم: - متأسفانه راه مدرسه‌ی ما را برای پاشنه‌ی کفش خانم‌ها نساخته‌اند. که خندید و احساس کردم زورکی می‌خندد. بعد کمی این دست و آن دست کرد و عاقبت: - آخه من شنیده بودم شما با معلماتون خیلی خوب تا می‌کنید. صدای جذابی داشت. فکر کردم حیف که این صدا را پای تخته سیاه خراب خواهد کرد. و گفتم: - اما نه این قدر که مدرسه تعطیل بشود خانم! و لابد به عرض‌تون رسیده که همکارهای شما، خودشون نشسته‌اند و تصمیم گرفته‌اند که هجده ساعت درس بدهند. بنده هیچ‌کاره‌ام. - اختیار دارید. و نفهمیدم با این «اختیار دارید» چه می‌خواست بگوید. اما پیدا بود که بحث سر ساعات درس نیست. آناً تصمیم گرفتم، امتحانی بکنم: - این را هم اطلاع داشته باشید که فقط دو تا از معلم‌های ما متأهل‌اند. که قرمز شد و برای این که کار دیگری نکرده باشد، برخاست و حکمش را از روی میز برداشت. پا به پا می‌شد که دیدم باید به دادش برسم. ساعت را از او پرسیدم. وقت زنگ بود. فراش را صدا کردم که زنگ را بزند و بعد به او گفتم، بهتر است مشورت دیگری هم با رئیس فرهنگ بکند و ما به هر صورت خوشحال خواهیم شد که افتخار همکاری با خانمی مثل ایشان را داشته باشیم و خداحافظ شما. از در دفتر که بیرون رفت، صدای زنگ برخاست و معلم‌ها انگار موشان را آتش زده‌اند، به عجله رسیدند و هر کدام از پشت سر، آن قدر او را پاییدند تا از در بزرگ آهنی مدرسه بیرون رفت. فردا صبح معلوم شد که ناظم، دنبال کار مادرش بوده است که قرار بود بستری شود، تا جای سرطان گرفته را یک دوره برق بگذارند. کل کار بیمارستان را من به کمک دوستانم انجام دادم و موقع آن رسیده بود که مادرش برود بیمارستان اما وحشتش گرفته بود و حاضر نبود به بیمارستان برود. و ناظم می‌خواست رسماً دخالت کنم و با هم برویم خانه‌شان و با زبان چرب و نرمی که به قول ناظم داشتم مادرش را راضی کنم. چاره‌ای نبود. مدرسه را به معلم‌ها سپردیم و راه افتادیم. بالاخره به خانه‌ی آن‌ها رسیدیم. خانه‌ای بسیار کوچک و اجاره‌ای. مادر با چشم‌های گود نشسته و انگار زغال به صورت مالیده! سیاه نبود اما رنگش چنان تیره بود که وحشتم گرفت. اصلاً صورت نبود. زخم سیاه شده‌ای بود که انگار از جای چشم‌ها و دهان سر باز کرده است. کلی با مادرش صحبت کردم. از پسرش و کلی دروغ و دونگ، و چادرش را روی چارقدش انداختیم و علی... و خلاصه در بیمارستان بستری شدند. فردا که به مدرسه آمدم، ناظم سرحال بود و پیدا بود که از شر چیزی خلاص شده است و خبر داد که معلم کلاس سه را گرفته‌اند. یک ماه و خرده‌ای می‌شد که مخفی بود و ما ورقه‌ی انجام کارش را به جانشین غیر رسمی‌اش داده بودیم و حقوقش لنگ نشده بود و تا خبر رسمی بشنود و در روزنامه‌ای بیابد و قضیه به اداره‌ی فرهنگ و لیست حقوق بکشد، باز هم می‌دادیم. اما خبر که رسمی شد، جانشین واجد شرایط هم نمی‌توانست بفرستد و باید طبق مقررات رفتار می‌کردیم و بدیش همین بود. کم کم احساس کردم که مدرسه خلوت شده است و کلاس‌ها اغلب اوقات بی‌کارند. جانشین معلم کلاس چهار هنوز سر و صورتی به کارش نداده بود و حالا یک کلاس دیگر هم بی‌معلم شد. این بود که باز هم به سراغ رئیس فرهنگ رفتم. معلوم شد آن دخترک ترسیده و «نرسیده متلک پیچش کرده‌اید» رئیس فرهنگ این طور می‌گفت. و ترجیح داده بود همان زیر نظر خودش دفترداری کند. و بعد قول و قرار و فردا و پس فردا و عاقبت چهار روز دوندگی تا دو تا معلم گرفتم. یکی جوانکی رشتی که گذاشتیمش کلاس چهار و دیگری باز یکی ازین آقاپسرهای بریانتین‌زده که هر روز کراوات عوض می‌کرد، با نقش‌ها و طرح‌های عجیب. عجب فرهنگ را با قرتی‌ها در آمیخته بودند! باداباد. او را هم گذاشتیم سر کلاس سه. اواخر بهمن، یک روز ناظم آمد اتاقم که بودجه‌ی مدرسه را زنده کرده است. گفتم: - مبارکه، چه قدر گرفتی؟ - هنوز هیچ چی آقا. قراره فردا سر ظهر بیاند این جا آقا و همین جا قالش رو بکنند. و فردا اصلاً مدرسه نرفتم. حتماً می‌خواست من هم باشم و در بده بستان ماهی پانزده قران، حق نظافت هر اتاق نظارت کنم و از مدیریتم مایه بگذارم تا تنخواه‌گردان مدرسه و حق آب و دیگر پول‌های عقب‌افتاده وصول بشود... فردا سه نفری آمده بودند مدرسه. ناهار هم به خرج ناظم خورده بودند. و قرار دیگری برای یک سور حسابی گذاشته بودند و رفته بودند و ناظم با زبان بی‌زبانی حالیم کرد که این بار حتماً باید باشم و آن طور که می‌گفت، جای شکرش باقی بود که مراعات کرده بودند و حق بوقی نخواسته بودند. اولین باری بود که چنین اهمیتی پیدا می‌کردم. این هم یک مزیت دیگر مدیری مدرسه بود! سی صد تومان از بودجه‌ی دولت بسته به این بود که به فلان مجلس بروی یا نروی. تا سه روز دیگر موعد سور بود، اصلاً یادم نیست چه کردم. اما همه‌اش در این فکر بودم که بروم یا نروم؟ یک بار دیگر استعفانامه‌ام را توی جیبم گذاشتم و بی این که صدایش را در بیاورم، روز سور هم نرفتم. بعد دیدم این طور که نمی‌شود. گفتم بروم قضایا را برای رئیس فرهنگ بگویم. و رفتم. سلام و احوالپرسی نشستم. اما چه بگویم؟ بگویم چون نمی‌خواستم در خوردن سور شرکت کنم، استعفا می‌دهم؟... دیدم چیزی ندارم که بگویم. و از این گذشته خفت‌آور نبود که به خاطر سیصد تومان جا بزنم و استعفا بدهم؟ و «خداحافظ؛ فقط آمده بودم سلام عرض کنم.» و از این دروغ‌ها و استعفانامه‌ام را توی جوی آب انداختم. اما ناظم؛ یک هفته‌ای مثل سگ بود. عصبانی، پر سر و صدا و شارت و شورت! حتی نرفتم احوال مادرش را بپرسم. یک هفته‌ی تمام می‌رفتم و در اتاقم را می‌بستم و سوراخ‌های گوشم را می‌گرفتم و تا اِز و چِزّ بچه‌ها بخوابد، از این سر تا آن سر اتاق را می‌کوبیدم. ده روز تمام، قلب من و بچه‌ها با هم و به یک اندازه از ترس و وحشت تپید. تا عاقبت پول‌ها وصول شد. منتها به جای سیصد و خرده‌ای، فقط صد و پنجاه تومان. علت هم این بود که در تنظیم صورت حساب‌ها اشتباهاتی رخ داده بود که ناچار اصلاحش کرده بودند! غیر از آن زنی که هفته‌ای یک بار به مدرسه سری می‌زد، از اولیای اطفال دو سه نفر دیگر هم بودند که مرتب بودند. یکی همان پاسبانی که با کمربند، پاهای پسرش را بست و فلک کرد. یکی هم کارمند پست و تلگرافی بود که ده روزی یک بار می‌آمد و پدر همان بچه‌ی شیطان. و یک استاد نجار که پسرش کلاس اول بود و خودش سواد داشت و به آن می‌بالید و کارآمد می‌نمود. یک مقنی هم بود درشت استخوان و بلندقد که بچه‌اش کلاس سوم بود و هفته‌ای یک بار می‌آمد و همان توی حیاط، ده پانزده دقیقه‌ای با فراش‌ها اختلاط می‌کرد و بی سر و صدا می‌رفت. نه کاری داشت، نه چیزی از آدم می‌خواست و همان طور که آمده بود چند دقیقه‌ای را با فراش صحبت می‌کرد و بعد می رفت. فقط یک روز نمی‌دانم چرا رفته بود بالای دیوار مدرسه. البته اول فکر کردم مأمور اداره برق است ولی بعد متوجه شدم که همان مرد مقنی است. بچه‌ها جیغ و فریاد می‌کردند و من همه‌اش درین فکر بودم که چه طور به سر دیوار رفته است؟ ماحصل داد و فریادش این بود که چرا اسم پسر او را برای گرفتن کفش و لباس به انجمن ندادیم. وقتی به او رسیدم نگاهی به او انداختم و بعد تشری به ناظم و معلم ها زدم که ولش کردند و بچه‌ها رفتند سر کلاس و بعد بی این که نگاهی به او بکنم، گفتم: - خسته نباشی اوستا. و همان طور که به طرف دفتر می‌رفتم رو به ناظم و معلم‌ها افزودم: - لابد جواب درست و حسابی نشنیده که رفته سر دیوار. که پشت سرم گرپ صدایی آمد و از در دفتر که رفتم تو، او و ناظم با هم وارد شدند. گفتم نشست. و به جای این‌که حرفی بزند به گریه افتاد. هرگز گمان نمی‌کردم از چنان قد و قامتی صدای گریه در بیاید. این بود که از اتاق بیرون آمدم و فراش را صدا زدم که آب برایش بیاورد و حالش که جا آمد، بیاوردش پهلوی من. اما دیگر از او خبری نشد که نشد. نه آن روز و نه هیچ روز دیگر. آن روز چند دقیقه‌ای بعد از شیشه‌ی اتاق خودم دیدمش که دمش را لای پایش گذاشته بود از در مدرسه بیرون می‌رفت و فراش جدید آمد که بله می‌گفتند از پسرش پنج تومان خواسته بودند تا اسمش را برای کفش و لباس به انجمن بدهند. پیدا بود باز توی کوک ناظم رفته است. مرخصش کردم و ناظم را خواستم. معلوم شد می‌خواسته ناظم را بزند. همین جوری و بی‌مقدمه. اواخر بهمن بود که یکی از روزهای برفی با یکی دیگر از اولیای اطفال آشنا شدم. یارو مرد بسیار کوتاهی بود؛ فرنگ مآب و بزک کرده و اتو کشیده که ننشسته از تحصیلاتش و از سفرهای فرنگش حرف زد. می‌خواست پسرش را آن وقت سال از مدرسه‌ی دیگر به آن جا بیاورد. پسرش از آن بچه‌هایی بود که شیر و مربای صبحانه‌اش را با قربان صدقه توی حلقشان می‌تپانند. کلاس دوم بود و ثلث اول دو تا تجدید آورده بود. می‌گفت در باغ ییلاقی‌اش که نزدیک مدرسه است، باغبانی دارند که پسرش شاگرد ماست و درس‌خوان است و پیدا است که بچه‌ها زیر سایه شما خوب پیشرفت می‌کنند. و از این پیزرها. و حال به خاطر همین بچه، توی این برف و سرما، آمده‌اند ساکن باغ ییلاقی شده‌اند. بلند شدم ناظم را صدا کردم و دست او و بچه‌اش را توی دست ناظم گذاشتم و خداحافظ شما... و نیم ساعت بعد ناظم برگشت که یارو خانه‌ی شهرش را به یک دبیرستان اجاره داده، به ماهی سه هزار و دویست تومان، و التماس دعا داشته، یعنی معلم سرخانه می‌خواسته و حتی بدش نمی‌آمده است که خود مدیر زحمت بکشند و ازین گنده‌گوزی‌ها... احساس کردم که ناظم دهانش آب افتاده است. و من به ناظم حالی کردم خودش برود بهتر است و فقط کاری بکند که نه صدای معلم‌ها در بیاید و نه آخر سال، برای یک معدل ده احتیاجی به من بمیرم و تو بمیری پیدا کند. همان روز عصر ناظم رفته بود و قرار و مدار برای هر روز عصر یک ساعت به ماهی صد و پنجاه تومان. دیگر دنیا به کام ناظم بود. حال مادرش هم بهتر بود و از بیمارستان مرخصش کرده بودند و به فکر زن گرفتن افتاده بود. و هر روز هم برای یک نفر نقشه می‌کشید حتی برای من هم. یک روز در آمد که چرا ما خودمان «انجمن خانه و مدرسه» نداشته باشیم؟ نشسته بود و حسابش را کرده بود دیده بود که پنجاه شصت نفری از اولیای مدرسه دستشان به دهان‌شان می‌رسد و از آن هم که به پسرش درس خصوصی می‌داد قول مساعد گرفته بود. حالیش کردم که مواظب حرف و سخن اداره‌ای باشد و هر کار دلش می‌خواهد بکند. کاغذ دعوت را هم برایش نوشتم با آب و تاب و خودش برای اداره‌ی فرهنگ، داد ماشین کردند و به وسیله‌ی خود بچه‌ها فرستاد. و جلسه با حضور بیست و چند نفری از اولیای بچه‌ها رسمی شد. خوبیش این بود که پاسبان کشیک پاسگاه هم آمده بود و دم در برای همه، پاشنه‌هایش را به هم می‌کوبید و معلم‌ها گوش تا گوش نشسته بودند و مجلس ابهتی داشت و ناظم، چای و شیرینی تهیه کرده بود و چراغ زنبوری کرایه کرده بود و باران هم گذاشت پشتش و سالون برای اولین بار در عمرش به نوایی رسید. یک سرهنگ بود که رئیسش کردیم و آن زن را که هفته‌ای یک بار می‌آمد نایب رئیس. آن که ناظم به پسرش درس خصوصی می‌داد نیامده بود. اما پاکت سربسته‌ای به اسم مدیر فرستاده بود که فی‌المجلس بازش کردیم. عذرخواهی از این‌که نتوانسته بود بیاید و وجه ناقابلی جوف پاکت. صد و پنجاه تومان. و پول را روی میز صندوق‌دار گذاشتیم که ضبط و ربط کند. نائب رئیس بزک کرده و معطر شیرینی تعارف می‌کرد و معلم‌ها با هر بار که شیرینی بر می‌داشتند، یک بار تا بناگوش سرخ می‌شدند و فراش‌ها دست به دست چای می‌آوردند. در فکر بودم که یک مرتبه احساس کردم، سیصد چهارصد تومان پول نقد، روی میز است و هشت صد تومان هم تعهد کرده بودند. پیرزن صندوقدار که کیف پولش را همراهش نیاورده بود ناچار حضار تصویب کردند که پول‌ها فعلاً پیش ناظم باشد. و صورت مجلس مرتب شد و امضاها ردیف پای آن و فردا فهمیدم که ناظم همان شب روی خشت نشسته بوده و به معلم‌ها سور داده بوده است. اولین کاری که کردم رونوشت مجلس آن شب را برای اداره‌ی فرهنگ فرستادم. و بعد همان استاد نجار را صدا کردم و دستور دادم برای مستراح‌ها دو روزه در بسازد که ناظم خیلی به سختی پولش را داد. و بعد در کوچه‌ی مدرسه درخت کاشتیم. تور والیبال را تعویض و تعدادی توپ در اختیار بچه‌ها گذاشتیم برای تمرین در بعد از ظهرها و آمادگی برای مسابقه با دیگر مدارس و در همین حین سر و کله‌ی بازرس تربیت بدنی هم پیدا شد و هر روز سرکشی و بیا و برو. تا یک روز که به مدرسه رسیدم شنیدم که از سالون سر و صدا می‌آید. صدای هالتر بود. ناظم سر خود رفته بود و سرخود دویست سیصد تومان داده بود و هالتر خریده بود و بچه‌های لاغر زیر بار آن گردن خود را خرد می‌کردند. من در این میان حرفی نزدم. می‌توانستم حرفی بزنم؟ من چیکاره بودم؟ اصلاً به من چه ربطی داشت؟ هر کار که دلشان می‌خواهد بکنند. مهم این بود که سالون مدرسه رونقی گرفته بود. ناظم هم راضی بود و معلم‌ها هم. چون نه خبر از حسادتی بود و نه حرف و سخنی پیش آمد. فقط می‌بایست به ناظم سفارش می کردم که فکر فراش‌ها هم باشد. کم کم خودمان را برای امتحان‌های ثلث دوم آماده می‌کردیم. این بود که اوایل اسفند، یک روز معلم‌ها را صدا زدم و در شورا مانندی که کردیم بی‌مقدمه برایشان داستان یکی از همکاران سابقم را گفتم که هر وقت بیست می‌داد تا دو روز تب داشت. البته معلم‌ها خندیدند. ناچار تشویق شدم و داستان آخوندی را گفتم که در بچگی معلم شرعیاتمان بود و زیر عبایش نمره می‌داد و دستش چنان می‌لرزید که عبا تکان می‌خورد و درست ده دقیقه طول می‌کشید. و تازه چند؟ بهترین شاگردها دوازده. و البته باز هم خندیدند. که این بار کلافه‌ام کرد. و بعد حالیشان کردم که بد نیست در طرح سؤال‌ها مشورت کنیم و از این حرف‌ها... و از شنبه‌ی بعد، امتحانات شروع شد. درست از نیمه‌ی دوم اسفند. سؤال‌ها را سه نفری می‌دیدیم. خودم با معلم هر کلاس و ناظم. در سالون میزها را چیده بودیم البته از وقتی هالتردار شده بود خیلی زیباتر شده بود. در سالون کاردستی‌های بچه‌ها در همه جا به چشم می‌خورد. هر کسی هر چیزی را به عنوان کاردستی درست کرده بودند و آورده بودند. که برای این کاردستی‌ها چه پول‌ها که خرج نشده بود و چه دست‌ها که نبریده بود و چه دعواها که نشده بود و چه عرق‌ها که ریخته نشده بود. پیش از هر امتحان که می‌شد، خودم یک میتینگ برای بچه‌ها می‌دادم که ترس از معلم و امتحان بی‌جا است و باید اعتماد به نفس داشت و ازین مزخرفات....ولی مگر حرف به گوش کسی می‌رفت؟ از در که وارد می‌شدند، چنان هجومی می‌بردند که نگو! به جاهای دور از نظر. یک بار چنان بود که احساس کردم مثل این‌که از ترس، لذت می‌برند. اگر معلم نبودی یا مدیر، به راحتی می‌توانستی حدس بزنی که کی‌ها با هم قرار و مداری دارند و کدام یک پهلو دست کدام یک خواهد نشست. یکی دو بار کوشیدم بالای دست یکی‌شان بایستم و ببینم چه می‌نویسد. ولی چنان مضطرب می‌شدند و دستشان به لرزه می‌افتاد که از نوشتن باز می‌ماندند. می‌دیدم که این مردان آینده، درین کلاس‌ها و امتحان‌ها آن قدر خواهند ترسید که وقتی دیپلمه بشوند یا لیسانسه، اصلاً آدم نوع جدیدی خواهند شد. آدمی انباشته از وحشت، انبانی از ترس و دلهره. به این ترتیب یک روز بیشتر دوام نیاوردم. چون دیدم نمی‌توانم قلب بچگانه‌ای داشته باشم تا با آن ترس و وحشت بچه‌ها را درک کنم و هم‌دردی نشان بدهم.این جور بود که می‌دیدم که معلم مدرسه هم نمی‌توانم باشم. دو روز قبل از عید کارنامه‌ها آماده بود و منتظر امضای مدیر. دویست و سی و شش تا امضا اقلاً تا ظهر طول می‌کشید. پیش از آن هم تا می‌توانستم از امضای دفترهای حضور و غیاب می‌گریختم. خیلی از جیره‌خورهای دولت در ادارات دیگر یا در میان همکارانم دیده بودم که در مواقع بیکاری تمرین امضا می‌کنند. پیش از آن نمی‌توانستم بفهمم چه طور از مدیری یک مدرسه یا کارمندی ساده یک اداره می‌شود به وزارت رسید. یا اصلاً آرزویش را داشت. نیم‌قراضه امضای آماده و هر کدام معرف یک شخصیت، بعد نیم‌ذرع زبان چرب و نرم که با آن، مار را از سوراخ بیرون بکشی، یا همه جا را بلیسی و یک دست هم قیافه. نه یک جور. دوازده جور. در این فکرها بودم که ناگهان در میان کارنامه‌ها چشمم به یک اسم آشنا افتاد. به اسم پسران جناب سرهنگ که رئیس انجمن بود. رفتم توی نخ نمراتش. همه متوسط بود و جای ایرادی نبود. و یک مرتبه به صرافت افتادم که از اول سال تا به حال بچه‌های مدرسه را فقط به اعتبار وضع مالی پدرشان قضاوت کرده‌ام. درست مثل این پسر سرهنگ که به اعتبار کیابیای پدرش درس نمی‌خواند. دیدم هر کدام که پدرشان فقیرتر است به نظر من باهوش‌تر می‌آمده‌اند. البته ناظم با این حرف‌ها کاری نداشت. مر قانونی را عمل می‌کرد. از یکی چشم می‌پوشید به دیگری سخت می‌گرفت. اما من مثل این که قضاوتم را درباره‌ی بچه‌ها از پیش کرده باشم و چه خوب بود که نمره‌ها در اختیار من نبود و آن یکی هم «انظباط» مال آخر سال بود. مسخره‌ترین کارها آن است که کسی به اصلاح وضعی دست بزند، اما در قلمروی که تا سر دماغش بیشتر نیست. و تازه مدرسه‌ی من، این قلمروی فعالیت من، تا سر دماغم هم نبود. به همان توی ذهنم ختم می‌شد. وضعی را که دیگران ترتیب داده بودند. به این ترتیب بعد از پنج شش ماه، می‌فهمیدم که حسابم یک حساب عقلایی نبوده است. احساساتی بوده است. ضعف‌های احساساتی مرا خشونت‌های عملی ناظم جبران می‌کرد و این بود که جمعاً نمی‌توانستم ازو بگذرم. مرد عمل بود. کار را می‌برید و پیش می‌رفت. در زندگی و در هر کاری، هر قدمی بر می‌داشت، برایش هدف بود. و چشم از وجوه دیگر قضیه می‌پوشید. این بود که برش داشت. و من نمی‌توانستم. چرا که اصلاً مدیر نبودم. خلاص... و کارنامه‌ی پسر سرهنگ را که زیر دستم عرق کرده بود، به دقت و احتیاج خشک کردم و امضایی زیر آن گذاشتم به قدری بد خط و مسخره بود که به یاد امضای فراش جدیدمان افتادم. حتماً جناب سرهنگ کلافه می‌شد که چرا چنین آدم بی‌سوادی را با این خط و ربط امضا مدیر مدرسه کرده‌اند. آخر یک جناب سرهنگ هم می‌داند که امضای آدم معرف شخصیت آدم است. اواخر تعطیلات نوروز رفتم به ملاقات معلم ترکه‌ای کلاس سوم. ناظم که با او میانه‌ی خوشی نداشت. ناچار با معلم حساب کلاس پنج و شش قرار و مداری گذاشته بودم که مختصری علاقه‌ای هم به آن حرف و سخن‌ها داشت. هم به وسیله‌ی او بود که می‌دانستم نشانی‌اش کجا است و توی کدام زندان است. در راه قبل از هر چیز خبر داد که رئیس فرهنگ عوض شده و این طور که شایع است یکی از هم دوره‌ای‌های من، جایش آمده. گفتم: - عجب! چرا؟ مگه رئیس قبلی چپش کم بود؟ - چه عرض کنم. می‌گند پا تو کفش یکی از نماینده‌ها کرده. شما خبر ندارید؟ - چه طور؟ از کجا خبر داشته باشم؟ - هیچ چی... می گند دو تا از کارچاق‌کن‌های انتخاباتی یارو از صندوق فرهنگ حقوق می‌گرفته‌اند؛ شب عیدی رئیس فرهنگ حقوق‌شون رو زده. - عجب! پس اونم می‌خواسته اصلاحات کنه! بیچاره. و بعد از این حرف زدیم که الحمدالله مدرسه مرتب است و آرام و معلم‌ها همکاری می‌کنند و ناظم بیش از اندازه همه‌کاره شده است. و من فهمیدم که باز لابد مشتری خصوصی تازه‌ای پیدا شده است که سر و صدای همه همکارها بلند شده. دم در زندان شلوغ بود. کلاه مخملی‌ها، عم‌قزی گل‌بته‌ها، خاله خانباجی‌ها و... اسم نوشتیم و نوبت گرفتیم و به جای پاها، دست‌هامان زیر بار کوچکی که داشتیم، خسته شد و خواب رفت تا نوبتمان شد. از این اتاق به آن اتاق و عاقبت نرده‌های آهنی و پشت آن معلم کلاس سه و... عجب چاق شده بود!درست مثل یک آدم حسابی شده بود. خوشحال شدیم و احوالپرسی و تشکر؛ و دیگر چه بگویم؟ بگویم چرا خودت را به دردسر انداختی؟ پیدا بود از مدرسه و کلاس به او خوش‌تر می‌گذرد. ایمانی بود و او آن را داشت و خوشبخت بود و دردسری نمی‌دید و زندان حداقل برایش کلاس درس بود. عاقبت پرسیدم: - پرونده‌ای هم برات درست کردند یا هنوز بلاتکلیفی؟ - امتحانمو دادم آقا مدیر، بد از آب در نیومد. - یعنی چه؟ - یعنی بی‌تکلیف نیستم. چون اسمم تو لیست جیره‌ی زندون رفته. خیالم راحته. چون سختی‌هاش گذشته. دیگر چه بگویم. دیدم چیزی ندارم خداحافظی کردم و او را با معلم حساب تنها گذاشتم و آمدم بیرون و تا مدت ملاقات تمام بشود، دم در زندان قدم زدم و به زندانی فکر کردم که برای خودم ساخته بودم. یعنی آن خرپول فرهنگ‌دوست ساخته بود. و من به میل و رغبت خودم را در آن زندانی کرده بودم. این یکی را به ضرب دگنک این جا آورده بودند. ناچار حق داشت که خیالش راحت باشد. اما من به میل و رغبت رفته بودم و چه بکنم؟ ناظم چه طور؟ راستی اگر رئیس فرهنگ از هم دوره‌ای‌های خودم باشد؛ چه طور است بروم و ازو بخواهم که ناظم را جای من بگذارد، یا همین معلم حساب را؟... که معلم حساب در آمد و راه افتادیم. با او هم دیگر حرفی نداشتم. سر پیچ خداحافظ شما و تاکسی گرفتم و یک سر به اداره‌ی فرهنگ زدم. گرچه دهم عید بود، اما هنوز رفت و آمد سال نو تمام نشده بود. برو و بیا و شیرینی و چای دو جانبه. رفتم تو. سلام و تبریک و همین تعارفات را پراندم. بله خودش بود. یکی از پخمه‌های کلاس. که آخر سال سوم کشتیارش شدم دو بیت شعر را حفظ کند، نتوانست که نتوانست. و حالا او رئیس بود و من آقا مدیر. راستی حیف از من، که حتی وزیر چنین رئیس فرهنگ‌هایی باشم! میز همان طور پاک بود و رفته. اما زیرسیگاری انباشته از خاکستر و ته سیگار. بلند شد و چلپ و چولوپ روبوسی کردیم و پهلوی خودش جا باز کرد و گوش تا گوش جیره‌خورهای فرهنگ تبریکات صمیمانه و بدگویی از ماسبق و هندوانه و پیزرها! و دو نفر که قد و قواره‌شان به درد گود زورخانه می‌خورد یا پای صندوق انتخابات شیرینی به مردم می‌دادند. نزدیک بود شیرینی را توی ظرفش بیندازم که دیدم بسیار احمقانه است. سیگارم که تمام شد قضیه‌ی رئیس فرهنگ قبلی و آن دو نفر را در گوشی ازش پرسیدم، حرفی نزد. فقط نگاهی می‌کرد که شبیه التماس بود و من فرصت جستم تا وضع معلم کلاس سوم را برایش روشن کنم و از او بخواهم تا آن جا که می‌تواند جلوی حقوقش را نگیرد. و از در که آمدم بیرون، تازه یادم آمد که برای کار دیگری پیش رئیس فرهنگ بودم. باز دیروز افتضاحی به پا شد. معقول یک ماهه‌ی فروردین راحت بودیم. اول اردیبهشت ماه جلالی و کوس رسوایی سر دیوار مدرسه. نزدیک آخر وقت یک جفت پدر و مادر، بچه‌شان در میان، وارد اتاق شدند. یکی بر افروخته و دیگری رنگ و رو باخته و بچه‌شان عیناً مثل این عروسک‌های کوکی. سلام و علیک و نشستند. خدایا دیگر چه اتفاقی افتاده است؟ - چه خبر شده که با خانوم سرافرازمون کردید؟ مرد اشاره‌ای به زنش کرد که بلند شد و دست بچه را گرفت و رفت بیرون و من ماندم و پدر. اما حرف نمی‌زد. به خودش فرصت می‌داد تا عصبانیتش بپزد. سیگارم را در آوردم و تعارفش کردم. مثل این که مگس مزاحمی را از روی دماغش بپراند، سیگار را رد کرد و من که سیگارم را آتش می‌زدم، فکر کردم لابد دردی دارد که چنین دست و پا بسته و چنین متکی به خانواده به مدرسه آمده. باز پرسیدم: - خوب، حالا چه فرمایش داشتید؟ که یک مرتبه ترکید: - اگه من مدیر مدرسه بودم و هم‌چه اتفاقی می‌افتاد، شیکم خودمو پاره می‌کردم. خجالت بکش مرد! برو استعفا بده. تا اهل محل نریختن تیکه تیکه‌ات کنند، دو تا گوشتو وردار و دررو. بچه‌های مردم می‌آن این جا درس بخونن و حسن اخلاق. نمی‌آن که... - این مزخرفات کدومه آقا! حرف حساب سرکار چیه؟ و حرکتی کردم که او را از در بیندازم بیرون. اما آخر باید می‌فهمیدم چه مرگش است. «ولی آخر با من چه کار دارد؟» - آبروی من رفته. آبروی صد ساله‌ی خونواده‌ام رفته. اگه در مدرسه‌ی تو رو تخته نکنم، تخم بابام نیستم. آخه من دیگه با این بچه چی کار کنم؟ تو این مدرسه ناموس مردم در خطره. کلانتری فهمیده؛ پزشک قانونی فهمیده؛ یک پرونده درست شده پنجاه ورق؛ تازه می‌گی حرف حسابم چیه؟ حرف حسابم اینه که صندلی و این مقام از سر تو زیاده. حرف حسابم اینه که می‌دم محاکمه‌ات کنند و از نون خوردن بندازنت... او می‌گفت و من گوش می‌کردم و مثل دو تا سگ هار به جان هم افتاده بودیم که در باز شد و ناظم آمد تو. به دادم رسید. در همان حال که من و پدر بچه در حال دعوا بودیم زن و بچه همان آقا رفته بودند و قضایا را برای ناظم تعریف کرده بودند و او فرستاده بوده فاعل را از کلاس کشیده بودند بیرون... و گفت چه طور است زنگ بزنیم و جلوی بچه‌ها ادبش کنیم و کردیم. یعنی این بار خود من رفتم میدان. پسرک نره‌خری بود از پنجمی‌ها با لباس مرتب و صورت سرخ و سفید و سالکی به گونه. جلوی روی بچه‌ها کشیدمش زیر مشت و لگد و بعد سه تا از ترکه‌ها را که فراش جدید فوری از باغ همسایه آورده بود، به سر و صورتش خرد کردم. چنان وحشی شده بودم که اگر ترکه‌ها نمی‌رسید، پسرک را کشته بودم. این هم بود که ناظم به دادش رسید و وساطت کرد و لاشه‌اش را توی دفتر بردند و بچه‌ها را مرخص کردند و من به اتاقم برگشتم و با حالی زار روی صندلی افتادم، نه از پدر خبری بود و نه از مادر و نه از عروسک‌های کوکی‌شان که ناموسش دست کاری شده بود. و تازه احساس کردم که این کتک‌کاری را باید به او می‌زدم. خیس عرق بودم و دهانم تلخ بود. تمام فحش‌هایی که می‌بایست به آن مردکه‌ی دبنگ می‌دادم و نداده بودم، در دهانم رسوب کرده بود و مثل دم مار تلخ شده بود. اصلاً چرا زدمش؟ چرا نگذاشتم مثل همیشه ناظم میدان‌داری کند که هم کارکشته‌تر بود و هم خونسردتر. لابد پسرک با دخترعمه‌اش هم نمی‌تواند بازی کند. لابد توی خانواده‌شان، دخترها سر ده دوازده سالگی باید از پسرهای هم سن رو بگیرند. نکند عیبی کرده باشد؟ و یک مرتبه به صرافت افتادم که بروم ببینم چه بلایی به سرش آورده‌ام. بلند شدم و یکی از فراش‌ها را صدا کردم که فهمیدم روانه‌اش کرده‌اند. آبی آورد که روی دستم می‌ریخت و صورتم را می‌شستم و می‌کوشیدم که لرزش دست‌هایم را نبیند. و در گوشم آهسته گفت که پسر مدیر شرکت اتوبوسرانی است و بدجوری کتک خورده و آن‌ها خیلی سعی کرده‌اند که تر و تمیزش کنند... احمق مثلا داشت توی دل مرا خالی می‌کرد. نمی‌دانست که من اول تصمیم را گرفتم، بعد مثل سگ هار شدم. و تازه می‌فهمیدم کسی را زده‌ام که لیاقتش را داشته. حتماً از این اتفاق‌ها جای دیگر هم می‌افتد. آدم بردارد پایین تنه بچه‌ی خودش را، یا به قول خودش ناموسش را بگذارد سر گذر که کلانتر محل و پزشک معاینه کنند! تا پرونده درست کنند؟ با این پدرو مادرها بچه‌ها حق دارند که قرتی و دزد و دروغگو از آب در بیایند. این مدرسه‌ها را اول برای پدر و مادرها باز کنند... با این افکار به خانه رسیدم. زنم در را که باز کرد؛ چشم‌هایش گرد شد. همیشه وقتی می‌ترسد این طور می‌شود. برای اینکه خیال نکند آدم کشته‌ام، زود قضایا را برایش گفتم. و دیدم که در ماند. یعنی ساکت ماند. آب سرد، عرق بیدمشک، سیگار پشت سیگار فایده نداشت، لقمه از گلویم پایین نمی‌رفت و دست‌ها هنوز می‌لرزید. هر کدام به اندازه‌ی یک ماه فعالیت کرده بودند. با سیگار چهارم شروع کردم: - می‌دانی زن؟ بابای یارو پول‌داره. مسلماً کار به دادگستری و این جور خنس‌ها می‌کشه. مدیریت که الفاتحه. اما خیلی دلم می‌خواد قضیه به دادگاه برسه. یک سال آزگار رو دل کشیده‌ام و دیگه خسته شده‌ام. دلم می‌خواد یکی بپرسه چرا بچه‌ی مردم رو این طوری زدی، چرا تنبیه بدنی کردی! آخه یک مدیر مدرسه هم حرف‌هایی داره که باید یک جایی بزنه... که بلند شد و رفت سراغ تلفن. دو سه تا از دوستانم را که در دادگستری کاره‌ای بودند، گرفت و خودم قضیه را برایشان گفتم که مواظب باشند. فردا پسرک فاعل به مدرسه نیامده بود. و ناظم برایم گفت که قضیه ازین قرار بوده است که دوتایی به هوای دیدن مجموعه تمبرهای فاعل با هم به خانه‌ای می‌روند و قضایا همان جا اتفاق می‌افتد و داد و هوار و دخالت پدر و مادرهای طرفین و خط و نشان و شبانه کلانتری؛ و تمام اهل محل خبر دارند. او هم نظرش این بود که کار به دادگستری خواهد کشید. و من یک هفته‌ی تمام به انتظار اخطاریه‌ی دادگستری صبح و عصر به مدرسه رفتم و مثل بخت‌النصر پشت پنجره ایستادم. اما در تمام این مدت نه از فاعل خبری شد، نه از مفعول و نه از پدر و مادر ناموس‌پرست و نه از مدیر شرکت اتوبوسرانی. انگار نه انگار که اتفاقی افتاده. بچه‌ها می‌آمدند و می‌رفتند؛ برای آب خوردن عجله می‌کردند؛ به جای بازی کتک‌کاری می‌کردند و همه چیز مثل قبل بود. فقط من ماندم و یک دنیا حرف و انتظار. تا عاقبت رسید.... احضاریه‌ای با تعیین وقت قبلی برای دو روز بعد، در فلان شعبه و پیش فلان بازپرس دادگستری. آخر کسی پیدا شده بود که به حرفم گوش کند. تا دو روز بعد که موعد احضار بود، اصلاً از خانه در نیامدم. نشستم و ماحصل حرف‌هایم را روی کاغذ آوردم. حرف‌هایی که با همه‌ی چرندی هر وزیر فرهنگی می‌توانست با آن یک برنامه‌ی هفت ساله برای کارش بریزد. و سر ساعت معین رفتم دادگستری. اتاق معین و بازپرس معین. در را باز کردم و سلام، و تا آمدم خودم را معرفی کنم و احضاریه را در بیاورم، یارو پیش‌دستی کرد و صندلی آورد و چای سفارش داد و «احتیاجی به این حرف‌ها نیست و قضیه‌ی کوچک بود و حل شد و راضی به زحمت شما نبودیم...» که عرق سرد بر بدن من نشست. چایی‌ام را که خوردم، روی همان کاغذ نشان‌دار دادگستری استعفانامه‌ام را نوشتم و به نام هم‌کلاسی پخمه‌ام که تازه رئیس شده بود، دم در پست کردم. EOT; } generator->parse($format)); } public function domainName() { return static::randomElement(static::$lastNameAscii) . '.' . $this->tld(); } } generator->parse($format); } public static function street() { return static::randomElement(static::$street); } public static function buildingNumber() { return static::numberBetween(1, 999); } } generator->parse($lastNameRandomElement); } public static function lastNameMale() { return static::randomElement(static::$lastNameMale); } public static function lastNameFemale() { return static::randomElement(static::$lastNameFemale); } public static function suffix() { return static::randomElement(static::$suffix); } } getTimestamp(); } return strtotime(empty($max) ? 'now' : $max); } public static function unixTime($max = 'now') { return mt_rand(0, static::getMaxTimestamp($max)); } public static function dateTime($max = 'now') { return new \DateTime('@' . static::unixTime($max)); } public static function dateTimeAD($max = 'now') { return new \DateTime('@' . mt_rand(-62135597361, static::getMaxTimestamp($max))); } public static function iso8601($max = 'now') { return static::date(\DateTime::ISO8601, $max); } public static function date($format = 'Y-m-d', $max = 'now') { return static::dateTime($max)->format($format); } public static function time($format = 'H:i:s', $max = 'now') { return static::dateTime($max)->format($format); } public static function dateTimeBetween($startDate = '-30 years', $endDate = 'now') { $startTimestamp = $startDate instanceof \DateTime ? $startDate->getTimestamp() : strtotime($startDate); $endTimestamp = static::getMaxTimestamp($endDate); if ($startTimestamp > $endTimestamp) { throw new \InvalidArgumentException('Start date must be anterior to end date.'); } $timestamp = mt_rand($startTimestamp, $endTimestamp); $ts = new \DateTime('@' . $timestamp); $ts->setTimezone(new \DateTimeZone(date_default_timezone_get())); return $ts; } public static function dateTimeThisCentury($max = 'now') { return static::dateTimeBetween('-100 year', $max); } public static function dateTimeThisDecade($max = 'now') { return static::dateTimeBetween('-10 year', $max); } public static function dateTimeThisYear($max = 'now') { return static::dateTimeBetween('-1 year', $max); } public static function dateTimeThisMonth($max = 'now') { return static::dateTimeBetween('-1 month', $max); } public static function amPm($max = 'now') { return static::dateTime($max)->format('a'); } public static function dayOfMonth($max = 'now') { return static::dateTime($max)->format('d'); } public static function dayOfWeek($max = 'now') { return static::dateTime($max)->format('l'); } public static function month($max = 'now') { return static::dateTime($max)->format('m'); } public static function monthName($max = 'now') { return static::dateTime($max)->format('F'); } public static function year($max = 'now') { return static::dateTime($max)->format('Y'); } public static function century() { return static::randomElement(static::$century); } public static function timezone() { return static::randomElement(\DateTimeZone::listIdentifiers()); } } generator->numerify('#########'); $n .= check_digit($n); $n .= check_digit($n); return $formatted? vsprintf('%d%d%d.%d%d%d.%d%d%d-%d%d', str_split($n)) : $n; } public function rg($formatted = true) { $n = $this->generator->numerify('########'); $n .= check_digit($n); return $formatted? vsprintf('%d%d.%d%d%d.%d%d%d-%s', str_split($n)) : $n; } } '')); } return $number; } public static function landline($formatted = true) { $number = static::numerify(static::randomElement(static::$landlineFormats)); if (!$formatted) { $number = strtr($number, array('-' => '')); } return $number; } public static function phone($formatted = true) { $options = static::randomElement(array( array('cellphone', false), array('cellphone', true), array('landline', null), )); return call_user_func("static::{$options[0]}", $formatted, $options[1]); } protected static function anyPhoneNumber($type, $formatted = true) { $area = static::areaCode(); $number = ($type == 'cellphone')? static::cellphone($formatted, in_array($area, static::$ninthDigitAreaCodes)) : static::landline($formatted); return $formatted? "($area) $number" : $area.$number; } public static function cellphoneNumber($formatted = true) { return static::anyPhoneNumber('cellphone', $formatted); } public static function landlineNumber($formatted = true) { return static::anyPhoneNumber('landline', $formatted); } public static function phoneNumber() { $method = static::randomElement(array('cellphoneNumber', 'landlineNumber')); return call_user_func("static::$method", true); } public static function phoneNumberCleared() { $method = static::randomElement(array('cellphoneNumber', 'landlineNumber')); return call_user_func("static::$method", false); } } = 12; $verifier = 0; for ($i = 1; $i <= $length; $i++) { if (!$second_algorithm) { $multiplier = $i+1; } else { $multiplier = ($i >= 9)? $i-7 : $i+1; } $verifier += $numbers[$length-$i] * $multiplier; } $verifier = 11 - ($verifier % 11); if ($verifier >= 10) { $verifier = 0; } return $verifier; } generator->numerify('########0001'); $n .= check_digit($n); $n .= check_digit($n); return $formatted? vsprintf('%d%d.%d%d%d.%d%d%d/%d%d%d%d-%d%d', str_split($n)) : $n; } } generator->parse($format); } public function county() { return static::randomElement(static::$counties); } public function address() { $format = static::randomElement(static::$addressFormats); return $this->generator->parse($format); } public function streetAddress() { $format = static::randomElement(static::$streetAddressFormats); return $this->generator->parse($format); } } '01', 'AR' => '02', 'AG' => '03', 'B' => '40', 'BC' => '04', 'BH' => '05', 'BN' => '06', 'BT' => '07', 'BV' => '08', 'BR' => '09', 'BZ' => '10', 'CS' => '11', 'CL' => '51', 'CJ' => '12', 'CT' => '13', 'CV' => '14', 'DB' => '15', 'DJ' => '16', 'GL' => '17', 'GR' => '52', 'GJ' => '18', 'HR' => '19', 'HD' => '20', 'IL' => '21', 'IS' => '22', 'IF' => '23', 'MM' => '24', 'MH' => '25', 'MS' => '26', 'NT' => '27', 'OT' => '28', 'PH' => '29', 'SM' => '30', 'SJ' => '31', 'SB' => '32', 'SV' => '33', 'TR' => '34', 'TM' => '35', 'TL' => '36', 'VS' => '37', 'VL' => '38', 'VN' => '39', 'B1' => '41', 'B2' => '42', 'B3' => '43', 'B4' => '44', 'B5' => '45', 'B6' => '46' ); public function cnp($gender = null, $century = null, $county = null) { if (is_null($county) || !array_key_exists($county, static::$cnpCountyCodes)) { $countyCode = static::randomElement(array_values(static::$cnpCountyCodes)); } else { $countyCode = static::$cnpCountyCodes[$county]; } $cnp = (string) static::cnpFirstDigit($gender, $century) . static::numerify('##') . sprintf('%02d', $this->generator->month()) . sprintf('%02d', $this->generator->dayOfMonth()) . $countyCode . static::numerify('##%') ; $cnp = static::cnpAddChecksum($cnp); return $cnp; } protected static function cnpFirstDigit($gender = null, $century = null) { switch ($century) { case 1800: case 3: case 4: $centuryCode = 2; break; case 1900: case 1: case 2: $centuryCode = 0; break; case 2000: case 5: case 6: $centuryCode = 4; break; default: $centuryCode = static::randomElement(array(0, 2, 4, 6, 9)); } switch (strtolower($gender)) { case 'm': case 1: $genderCode = 1; break; case 'f': case 2: $genderCode = 2; break; default: $genderCode = static::randomElement(array(1, 2)); } $firstDigit = $centuryCode + $genderCode; return ($firstDigit > 9) ? 9 : $firstDigit; } protected static function cnpAddChecksum($cnp) { $checkNumber = 279146358279; $checksum = 0; foreach (range(0, 11) as $digit) { $checksum += substr($cnp, $digit, 1) * substr($checkNumber, $digit, 1); } $checksum = $checksum % 11; return substr($cnp, 0, 12) . ($checksum == 10 ? 1 : $checksum); } } array( '021#######', '023#######', '024#######', '025#######', '026#######', '027#######', '031#######', '033#######', '034#######', '035#######', '036#######', '037#######', ), 'mobile' => array( '07########', ) ); protected static $specialFormats = array( 'toll-free' => array( '0800######', '0801######', '0802######', '0806######', '0807######', '0870######', ), 'premium-rate' => array( '0900######', '0903######', '0906######', ) ); public static function phoneNumber() { $type = static::randomElement(array_keys(static::$normalFormats)); $number = static::numerify(static::randomElement(static::$normalFormats[$type])); return $number; } public static function tollFreePhoneNumber() { $number = static::numerify(static::randomElement(static::$specialFormats['toll-free'])); return $number; } public static function premiumRatePhoneNumber() { $number = static::numerify(static::randomElement(static::$specialFormats['premium-rate'])); return $number; } } 'atom', 'application/ecmascript' => 'ecma', 'application/emma+xml' => 'emma', 'application/epub+zip' => 'epub', 'application/java-archive' => 'jar', 'application/java-vm' => 'class', 'application/javascript' => 'js', 'application/json' => 'json', 'application/jsonml+json' => 'jsonml', 'application/lost+xml' => 'lostxml', 'application/mathml+xml' => 'mathml', 'application/mets+xml' => 'mets', 'application/mods+xml' => 'mods', 'application/mp4' => 'mp4s', 'application/msword' => array('doc', 'dot'), 'application/octet-stream' => array( 'bin', 'dms', 'lrf', 'mar', 'so', 'dist', 'distz', 'pkg', 'bpk', 'dump', 'elc', 'deploy' ), 'application/ogg' => 'ogx', 'application/omdoc+xml' => 'omdoc', 'application/pdf' => 'pdf', 'application/pgp-encrypted' => 'pgp', 'application/pgp-signature' => array('asc', 'sig'), 'application/pkix-pkipath' => 'pkipath', 'application/pkixcmp' => 'pki', 'application/pls+xml' => 'pls', 'application/postscript' => array('ai', 'eps', 'ps'), 'application/pskc+xml' => 'pskcxml', 'application/rdf+xml' => 'rdf', 'application/reginfo+xml' => 'rif', 'application/rss+xml' => 'rss', 'application/rtf' => 'rtf', 'application/sbml+xml' => 'sbml', 'application/vnd.adobe.air-application-installer-package+zip' => 'air', 'application/vnd.adobe.xdp+xml' => 'xdp', 'application/vnd.adobe.xfdf' => 'xfdf', 'application/vnd.ahead.space' => 'ahead', 'application/vnd.dart' => 'dart', 'application/vnd.data-vision.rdz' => 'rdz', 'application/vnd.dece.data' => array('uvf', 'uvvf', 'uvd', 'uvvd'), 'application/vnd.dece.ttml+xml' => array('uvt', 'uvvt'), 'application/vnd.dece.unspecified' => array('uvx', 'uvvx'), 'application/vnd.dece.zip' => array('uvz', 'uvvz'), 'application/vnd.denovo.fcselayout-link' => 'fe_launch', 'application/vnd.dna' => 'dna', 'application/vnd.dolby.mlp' => 'mlp', 'application/vnd.dpgraph' => 'dpg', 'application/vnd.dreamfactory' => 'dfac', 'application/vnd.ds-keypoint' => 'kpxx', 'application/vnd.dvb.ait' => 'ait', 'application/vnd.dvb.service' => 'svc', 'application/vnd.dynageo' => 'geo', 'application/vnd.ecowin.chart' => 'mag', 'application/vnd.enliven' => 'nml', 'application/vnd.epson.esf' => 'esf', 'application/vnd.epson.msf' => 'msf', 'application/vnd.epson.quickanime' => 'qam', 'application/vnd.epson.salt' => 'slt', 'application/vnd.epson.ssf' => 'ssf', 'application/vnd.ezpix-album' => 'ez2', 'application/vnd.ezpix-package' => 'ez3', 'application/vnd.fdf' => 'fdf', 'application/vnd.fdsn.mseed' => 'mseed', 'application/vnd.fdsn.seed' => array('seed', 'dataless'), 'application/vnd.flographit' => 'gph', 'application/vnd.fluxtime.clip' => 'ftc', 'application/vnd.hal+xml' => 'hal', 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', 'application/vnd.ibm.minipay' => 'mpy', 'application/vnd.ibm.secure-container' => 'sc', 'application/vnd.iccprofile' => array('icc', 'icm'), 'application/vnd.igloader' => 'igl', 'application/vnd.immervision-ivp' => 'ivp', 'application/vnd.kde.karbon' => 'karbon', 'application/vnd.kde.kchart' => 'chrt', 'application/vnd.kde.kformula' => 'kfo', 'application/vnd.kde.kivio' => 'flw', 'application/vnd.kde.kontour' => 'kon', 'application/vnd.kde.kpresenter' => array('kpr', 'kpt'), 'application/vnd.kde.kspread' => 'ksp', 'application/vnd.kde.kword' => array('kwd', 'kwt'), 'application/vnd.kenameaapp' => 'htke', 'application/vnd.kidspiration' => 'kia', 'application/vnd.kinar' => array('kne', 'knp'), 'application/vnd.koan' => array('skp', 'skd', 'skt', 'skm'), 'application/vnd.kodak-descriptor' => 'sse', 'application/vnd.las.las+xml' => 'lasxml', 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', 'application/vnd.lotus-1-2-3' => '123', 'application/vnd.lotus-approach' => 'apr', 'application/vnd.lotus-freelance' => 'pre', 'application/vnd.lotus-notes' => 'nsf', 'application/vnd.lotus-organizer' => 'org', 'application/vnd.lotus-screencam' => 'scm', 'application/vnd.mozilla.xul+xml' => 'xul', 'application/vnd.ms-artgalry' => 'cil', 'application/vnd.ms-cab-compressed' => 'cab', 'application/vnd.ms-excel' => array( 'xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw' ), 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', 'application/vnd.ms-fontobject' => 'eot', 'application/vnd.ms-htmlhelp' => 'chm', 'application/vnd.ms-ims' => 'ims', 'application/vnd.ms-lrm' => 'lrm', 'application/vnd.ms-officetheme' => 'thmx', 'application/vnd.ms-pki.seccat' => 'cat', 'application/vnd.ms-pki.stl' => 'stl', 'application/vnd.ms-powerpoint' => array('ppt', 'pps', 'pot'), 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', 'application/vnd.ms-project' => array('mpp', 'mpt'), 'application/vnd.ms-word.document.macroenabled.12' => 'docm', 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', 'application/vnd.ms-works' => array('wps', 'wks', 'wcm', 'wdb'), 'application/vnd.ms-wpl' => 'wpl', 'application/vnd.ms-xpsdocument' => 'xps', 'application/vnd.mseq' => 'mseq', 'application/vnd.musician' => 'mus', 'application/vnd.oasis.opendocument.chart' => 'odc', 'application/vnd.oasis.opendocument.chart-template' => 'otc', 'application/vnd.oasis.opendocument.database' => 'odb', 'application/vnd.oasis.opendocument.formula' => 'odf', 'application/vnd.oasis.opendocument.formula-template' => 'odft', 'application/vnd.oasis.opendocument.graphics' => 'odg', 'application/vnd.oasis.opendocument.graphics-template' => 'otg', 'application/vnd.oasis.opendocument.image' => 'odi', 'application/vnd.oasis.opendocument.image-template' => 'oti', 'application/vnd.oasis.opendocument.presentation' => 'odp', 'application/vnd.oasis.opendocument.presentation-template' => 'otp', 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', 'application/vnd.oasis.opendocument.text' => 'odt', 'application/vnd.oasis.opendocument.text-master' => 'odm', 'application/vnd.oasis.opendocument.text-template' => 'ott', 'application/vnd.oasis.opendocument.text-web' => 'oth', 'application/vnd.olpc-sugar' => 'xo', 'application/vnd.oma.dd2+xml' => 'dd2', 'application/vnd.openofficeorg.extension' => 'oxt', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', 'application/vnd.pvi.ptid1' => 'ptid', 'application/vnd.quark.quarkxpress' => array( 'qxd', 'qxt', 'qwd', 'qwt', 'qxl', 'qxb' ), 'application/vnd.realvnc.bed' => 'bed', 'application/vnd.recordare.musicxml' => 'mxl', 'application/vnd.recordare.musicxml+xml' => 'musicxml', 'application/vnd.rig.cryptonote' => 'cryptonote', 'application/vnd.rim.cod' => 'cod', 'application/vnd.rn-realmedia' => 'rm', 'application/vnd.rn-realmedia-vbr' => 'rmvb', 'application/vnd.route66.link66+xml' => 'link66', 'application/vnd.sailingtracker.track' => 'st', 'application/vnd.seemail' => 'see', 'application/vnd.sema' => 'sema', 'application/vnd.semd' => 'semd', 'application/vnd.semf' => 'semf', 'application/vnd.shana.informed.formdata' => 'ifm', 'application/vnd.shana.informed.formtemplate' => 'itp', 'application/vnd.shana.informed.interchange' => 'iif', 'application/vnd.shana.informed.package' => 'ipk', 'application/vnd.simtech-mindmapper' => array('twd', 'twds'), 'application/vnd.smaf' => 'mmf', 'application/vnd.stepmania.stepchart' => 'sm', 'application/vnd.sun.xml.calc' => 'sxc', 'application/vnd.sun.xml.calc.template' => 'stc', 'application/vnd.sun.xml.draw' => 'sxd', 'application/vnd.sun.xml.draw.template' => 'std', 'application/vnd.sun.xml.impress' => 'sxi', 'application/vnd.sun.xml.impress.template' => 'sti', 'application/vnd.sun.xml.math' => 'sxm', 'application/vnd.sun.xml.writer' => 'sxw', 'application/vnd.sun.xml.writer.global' => 'sxg', 'application/vnd.sun.xml.writer.template' => 'stw', 'application/vnd.sus-calendar' => array('sus', 'susp'), 'application/vnd.svd' => 'svd', 'application/vnd.symbian.install' => array('sis', 'sisx'), 'application/vnd.syncml+xml' => 'xsm', 'application/vnd.syncml.dm+wbxml' => 'bdm', 'application/vnd.syncml.dm+xml' => 'xdm', 'application/vnd.tao.intent-module-archive' => 'tao', 'application/vnd.tcpdump.pcap' => array('pcap', 'cap', 'dmp'), 'application/vnd.tmobile-livetv' => 'tmo', 'application/vnd.trid.tpt' => 'tpt', 'application/vnd.triscape.mxs' => 'mxs', 'application/vnd.trueapp' => 'tra', 'application/vnd.ufdl' => array('ufd', 'ufdl'), 'application/vnd.uiq.theme' => 'utz', 'application/vnd.umajin' => 'umj', 'application/vnd.unity' => 'unityweb', 'application/vnd.uoml+xml' => 'uoml', 'application/vnd.vcx' => 'vcx', 'application/vnd.visio' => array('vsd', 'vst', 'vss', 'vsw'), 'application/vnd.visionary' => 'vis', 'application/vnd.vsf' => 'vsf', 'application/vnd.wap.wbxml' => 'wbxml', 'application/vnd.wap.wmlc' => 'wmlc', 'application/vnd.wap.wmlscriptc' => 'wmlsc', 'application/vnd.webturbo' => 'wtb', 'application/vnd.wolfram.player' => 'nbp', 'application/vnd.wordperfect' => 'wpd', 'application/vnd.wqd' => 'wqd', 'application/vnd.wt.stf' => 'stf', 'application/vnd.xara' => 'xar', 'application/vnd.xfdl' => 'xfdl', 'application/voicexml+xml' => 'vxml', 'application/widget' => 'wgt', 'application/winhlp' => 'hlp', 'application/wsdl+xml' => 'wsdl', 'application/wspolicy+xml' => 'wspolicy', 'application/x-7z-compressed' => '7z', 'application/x-bittorrent' => 'torrent', 'application/x-blorb' => array('blb', 'blorb'), 'application/x-bzip' => 'bz', 'application/x-cdlink' => 'vcd', 'application/x-cfs-compressed' => 'cfs', 'application/x-chat' => 'chat', 'application/x-chess-pgn' => 'pgn', 'application/x-conference' => 'nsc', 'application/x-cpio' => 'cpio', 'application/x-csh' => 'csh', 'application/x-debian-package' => array('deb', 'udeb'), 'application/x-dgc-compressed' => 'dgc', 'application/x-director' => array( 'dir', 'dcr', 'dxr', 'cst', 'cct', 'cxt', 'w3d', 'fgd', 'swa' ), 'application/x-font-ttf' => array('ttf', 'ttc'), 'application/x-font-type1' => array('pfa', 'pfb', 'pfm', 'afm'), 'application/x-font-woff' => 'woff', 'application/x-freearc' => 'arc', 'application/x-futuresplash' => 'spl', 'application/x-gca-compressed' => 'gca', 'application/x-glulx' => 'ulx', 'application/x-gnumeric' => 'gnumeric', 'application/x-gramps-xml' => 'gramps', 'application/x-gtar' => 'gtar', 'application/x-hdf' => 'hdf', 'application/x-install-instructions' => 'install', 'application/x-iso9660-image' => 'iso', 'application/x-java-jnlp-file' => 'jnlp', 'application/x-latex' => 'latex', 'application/x-lzh-compressed' => array('lzh', 'lha'), 'application/x-mie' => 'mie', 'application/x-mobipocket-ebook' => array('prc', 'mobi'), 'application/x-ms-application' => 'application', 'application/x-ms-shortcut' => 'lnk', 'application/x-ms-wmd' => 'wmd', 'application/x-ms-wmz' => 'wmz', 'application/x-ms-xbap' => 'xbap', 'application/x-msaccess' => 'mdb', 'application/x-msbinder' => 'obd', 'application/x-mscardfile' => 'crd', 'application/x-msclip' => 'clp', 'application/x-msdownload' => array('exe', 'dll', 'com', 'bat', 'msi'), 'application/x-msmediaview' => array( 'mvb', 'm13', 'm14' ), 'application/x-msmetafile' => array('wmf', 'wmz', 'emf', 'emz'), 'application/x-rar-compressed' => 'rar', 'application/x-research-info-systems' => 'ris', 'application/x-sh' => 'sh', 'application/x-shar' => 'shar', 'application/x-shockwave-flash' => 'swf', 'application/x-silverlight-app' => 'xap', 'application/x-sql' => 'sql', 'application/x-stuffit' => 'sit', 'application/x-stuffitx' => 'sitx', 'application/x-subrip' => 'srt', 'application/x-sv4cpio' => 'sv4cpio', 'application/x-sv4crc' => 'sv4crc', 'application/x-t3vm-image' => 't3', 'application/x-tads' => 'gam', 'application/x-tar' => 'tar', 'application/x-tcl' => 'tcl', 'application/x-tex' => 'tex', 'application/x-tex-tfm' => 'tfm', 'application/x-texinfo' => array('texinfo', 'texi'), 'application/x-tgif' => 'obj', 'application/x-ustar' => 'ustar', 'application/x-wais-source' => 'src', 'application/x-x509-ca-cert' => array('der', 'crt'), 'application/x-xfig' => 'fig', 'application/x-xliff+xml' => 'xlf', 'application/x-xpinstall' => 'xpi', 'application/x-xz' => 'xz', 'application/x-zmachine' => 'z1', 'application/xaml+xml' => 'xaml', 'application/xcap-diff+xml' => 'xdf', 'application/xenc+xml' => 'xenc', 'application/xhtml+xml' => array('xhtml', 'xht'), 'application/xml' => array('xml', 'xsl'), 'application/xml-dtd' => 'dtd', 'application/xop+xml' => 'xop', 'application/xproc+xml' => 'xpl', 'application/xslt+xml' => 'xslt', 'application/xspf+xml' => 'xspf', 'application/xv+xml' => array('mxml', 'xhvml', 'xvml', 'xvm'), 'application/yang' => 'yang', 'application/yin+xml' => 'yin', 'application/zip' => 'zip', 'audio/adpcm' => 'adp', 'audio/basic' => array('au', 'snd'), 'audio/midi' => array('mid', 'midi', 'kar', 'rmi'), 'audio/mp4' => 'mp4a', 'audio/mpeg' => array( 'mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a' ), 'audio/ogg' => array('oga', 'ogg', 'spx'), 'audio/vnd.dece.audio' => array('uva', 'uvva'), 'audio/vnd.rip' => 'rip', 'audio/webm' => 'weba', 'audio/x-aac' => 'aac', 'audio/x-aiff' => array('aif', 'aiff', 'aifc'), 'audio/x-caf' => 'caf', 'audio/x-flac' => 'flac', 'audio/x-matroska' => 'mka', 'audio/x-mpegurl' => 'm3u', 'audio/x-ms-wax' => 'wax', 'audio/x-ms-wma' => 'wma', 'audio/x-pn-realaudio' => array('ram', 'ra'), 'audio/x-pn-realaudio-plugin' => 'rmp', 'audio/x-wav' => 'wav', 'audio/xm' => 'xm', 'image/bmp' => 'bmp', 'image/cgm' => 'cgm', 'image/g3fax' => 'g3', 'image/gif' => 'gif', 'image/ief' => 'ief', 'image/jpeg' => array('jpeg', 'jpg', 'jpe'), 'image/ktx' => 'ktx', 'image/png' => 'png', 'image/prs.btif' => 'btif', 'image/sgi' => 'sgi', 'image/svg+xml' => array('svg', 'svgz'), 'image/tiff' => array('tiff', 'tif'), 'image/vnd.adobe.photoshop' => 'psd', 'image/vnd.dece.graphic' => array('uvi', 'uvvi', 'uvg', 'uvvg'), 'image/vnd.dvb.subtitle' => 'sub', 'image/vnd.djvu' => array('djvu', 'djv'), 'image/vnd.dwg' => 'dwg', 'image/vnd.dxf' => 'dxf', 'image/vnd.fastbidsheet' => 'fbs', 'image/vnd.fpx' => 'fpx', 'image/vnd.fst' => 'fst', 'image/vnd.fujixerox.edmics-mmr' => 'mmr', 'image/vnd.fujixerox.edmics-rlc' => 'rlc', 'image/vnd.ms-modi' => 'mdi', 'image/vnd.ms-photo' => 'wdp', 'image/vnd.net-fpx' => 'npx', 'image/vnd.wap.wbmp' => 'wbmp', 'image/vnd.xiff' => 'xif', 'image/webp' => 'webp', 'image/x-3ds' => '3ds', 'image/x-cmu-raster' => 'ras', 'image/x-cmx' => 'cmx', 'image/x-freehand' => array('fh', 'fhc', 'fh4', 'fh5', 'fh7'), 'image/x-icon' => 'ico', 'image/x-mrsid-image' => 'sid', 'image/x-pcx' => 'pcx', 'image/x-pict' => array('pic', 'pct'), 'image/x-portable-anymap' => 'pnm', 'image/x-portable-bitmap' => 'pbm', 'image/x-portable-graymap' => 'pgm', 'image/x-portable-pixmap' => 'ppm', 'image/x-rgb' => 'rgb', 'image/x-tga' => 'tga', 'image/x-xbitmap' => 'xbm', 'image/x-xpixmap' => 'xpm', 'image/x-xwindowdump' => 'xwd', 'message/rfc822' => array('eml', 'mime'), 'model/iges' => array('igs', 'iges'), 'model/mesh' => array('msh', 'mesh', 'silo'), 'model/vnd.collada+xml' => 'dae', 'model/vnd.dwf' => 'dwf', 'model/vnd.gdl' => 'gdl', 'model/vnd.gtw' => 'gtw', 'model/vnd.mts' => 'mts', 'model/vnd.vtu' => 'vtu', 'model/vrml' => array('wrl', 'vrml'), 'model/x3d+binary' => 'x3db', 'model/x3d+vrml' => 'x3dv', 'model/x3d+xml' => 'x3d', 'text/cache-manifest' => 'appcache', 'text/calendar' => array('ics', 'ifb'), 'text/css' => 'css', 'text/csv' => 'csv', 'text/html' => array('html', 'htm'), 'text/n3' => 'n3', 'text/plain' => array( 'txt', 'text', 'conf', 'def', 'list', 'log', 'in' ), 'text/prs.lines.tag' => 'dsc', 'text/richtext' => 'rtx', 'text/sgml' => array('sgml', 'sgm'), 'text/tab-separated-values' => 'tsv', 'text/troff' => array( 't', 'tr', 'roff', 'man', 'me', 'ms' ), 'text/turtle' => 'ttl', 'text/uri-list' => array('uri', 'uris', 'urls'), 'text/vcard' => 'vcard', 'text/vnd.curl' => 'curl', 'text/vnd.curl.dcurl' => 'dcurl', 'text/vnd.curl.scurl' => 'scurl', 'text/vnd.curl.mcurl' => 'mcurl', 'text/vnd.dvb.subtitle' => 'sub', 'text/vnd.fly' => 'fly', 'text/vnd.fmi.flexstor' => 'flx', 'text/vnd.graphviz' => 'gv', 'text/vnd.in3d.3dml' => '3dml', 'text/vnd.in3d.spot' => 'spot', 'text/vnd.sun.j2me.app-descriptor' => 'jad', 'text/vnd.wap.wml' => 'wml', 'text/vnd.wap.wmlscript' => 'wmls', 'text/x-asm' => array('s', 'asm'), 'text/x-fortran' => array('f', 'for', 'f77', 'f90'), 'text/x-java-source' => 'java', 'text/x-opml' => 'opml', 'text/x-pascal' => array('p', 'pas'), 'text/x-nfo' => 'nfo', 'text/x-setext' => 'etx', 'text/x-sfv' => 'sfv', 'text/x-uuencode' => 'uu', 'text/x-vcalendar' => 'vcs', 'text/x-vcard' => 'vcf', 'video/3gpp' => '3gp', 'video/3gpp2' => '3g2', 'video/h261' => 'h261', 'video/h263' => 'h263', 'video/h264' => 'h264', 'video/jpeg' => 'jpgv', 'video/jpm' => array('jpm', 'jpgm'), 'video/mj2' => 'mj2', 'video/mp4' => 'mp4', 'video/mpeg' => array('mpeg', 'mpg', 'mpe', 'm1v', 'm2v'), 'video/ogg' => 'ogv', 'video/quicktime' => array('qt', 'mov'), 'video/vnd.dece.hd' => array('uvh', 'uvvh'), 'video/vnd.dece.mobile' => array('uvm', 'uvvm'), 'video/vnd.dece.pd' => array('uvp', 'uvvp'), 'video/vnd.dece.sd' => array('uvs', 'uvvs'), 'video/vnd.dece.video' => array('uvv', 'uvvv'), 'video/vnd.dvb.file' => 'dvb', 'video/vnd.fvt' => 'fvt', 'video/vnd.mpegurl' => array('mxu', 'm4u'), 'video/vnd.ms-playready.media.pyv' => 'pyv', 'video/vnd.uvvu.mp4' => array('uvu', 'uvvu'), 'video/vnd.vivo' => 'viv', 'video/webm' => 'webm', 'video/x-f4v' => 'f4v', 'video/x-fli' => 'fli', 'video/x-flv' => 'flv', 'video/x-m4v' => 'm4v', 'video/x-matroska' => array('mkv', 'mk3d', 'mks'), 'video/x-mng' => 'mng', 'video/x-ms-asf' => array('asf', 'asx'), 'video/x-ms-vob' => 'vob', 'video/x-ms-wm' => 'wm', 'video/x-ms-wmv' => 'wmv', 'video/x-ms-wmx' => 'wmx', 'video/x-ms-wvx' => 'wvx', 'video/x-msvideo' => 'avi', 'video/x-sgi-movie' => 'movie', ); public static function mimeType() { return static::randomElement(array_keys(static::$mimeTypes)); } public static function fileExtension() { $random_extension = static::randomElement(array_values(static::$mimeTypes)); return is_array($random_extension) ? static::randomElement($random_extension) : $random_extension; } public static function file($sourceDirectory = '/tmp', $targetDirectory = '/tmp', $fullPath = true) { if (!is_dir($sourceDirectory)) { throw new \InvalidArgumentException(sprintf('Source directory %s does not exist or is not a directory.', $sourceDirectory)); } if (!is_dir($targetDirectory)) { throw new \InvalidArgumentException(sprintf('Target directory %s does not exist or is not a directory.', $targetDirectory)); } if ($sourceDirectory == $targetDirectory) { throw new \InvalidArgumentException('Source and target directories must differ.'); } $files = array_filter(array_values(array_diff(scandir($sourceDirectory), array('.', '..'))), function ($file) use ($sourceDirectory) { return is_file($sourceDirectory . DIRECTORY_SEPARATOR . $file) && is_readable($sourceDirectory . DIRECTORY_SEPARATOR . $file); }); if (empty($files)) { throw new \InvalidArgumentException(sprintf('Source directory %s is empty.', $sourceDirectory)); } $sourceFullPath = $sourceDirectory . DIRECTORY_SEPARATOR . static::randomElement($files); $destinationFile = Uuid::uuid() . '.' . pathinfo($sourceFullPath, PATHINFO_EXTENSION); $destinationFullPath = $targetDirectory . DIRECTORY_SEPARATOR . $destinationFile; if (false === copy($sourceFullPath, $destinationFullPath)) { return false; } return $fullPath ? $destinationFullPath : $destinationFile; } } generator->parse($format); } public static function companySuffix() { return static::randomElement(static::$companySuffix); } } = 0; $i -= 2) { $sum += $number{$i}; } for ($i = $length - 2; $i >= 0; $i -= 2) { $sum += array_sum(str_split($number{$i} * 2)); } return $sum % 10; } public static function computeCheckDigit($partialNumber) { $checkDigit = self::checksum($partialNumber . '0'); if ($checkDigit === 0) { return 0; } return (string) (10 - $checkDigit); } public static function isValid($number) { return self::checksum($number) === 0; } } 1 && $arg[1] == '-' && !$long_options)) { $non_opts[] = $args[$i]; continue; } elseif (\strlen($arg) > 1 && $arg[1] == '-') { self::parseLongOption( \substr($arg, 2), $long_options, $opts, $args ); } else { self::parseShortOption( \substr($arg, 1), $short_options, $opts, $args ); } } return [$opts, $non_opts]; } protected static function parseShortOption($arg, $short_options, &$opts, &$args) { $argLen = \strlen($arg); for ($i = 0; $i < $argLen; $i++) { $opt = $arg[$i]; $opt_arg = null; if (($spec = \strstr($short_options, $opt)) === false || $arg[$i] == ':') { throw new Exception( "unrecognized option -- $opt" ); } if (\strlen($spec) > 1 && $spec[1] == ':') { if ($i + 1 < $argLen) { $opts[] = [$opt, \substr($arg, $i + 1)]; break; } if (!(\strlen($spec) > 2 && $spec[2] == ':')) { if (false === $opt_arg = \current($args)) { throw new Exception( "option requires an argument -- $opt" ); } \next($args); } } $opts[] = [$opt, $opt_arg]; } } protected static function parseLongOption($arg, $long_options, &$opts, &$args) { $count = \count($long_options); $list = \explode('=', $arg); $opt = $list[0]; $opt_arg = null; if (\count($list) > 1) { $opt_arg = $list[1]; } $opt_len = \strlen($opt); for ($i = 0; $i < $count; $i++) { $long_opt = $long_options[$i]; $opt_start = \substr($long_opt, 0, $opt_len); if ($opt_start != $opt) { continue; } $opt_rest = \substr($long_opt, $opt_len); if ($opt_rest != '' && $opt[0] != '=' && $i + 1 < $count && $opt == \substr($long_options[$i + 1], 0, $opt_len)) { throw new Exception( "option --$opt is ambiguous" ); } if (\substr($long_opt, -1) == '=') { if (\substr($long_opt, -2) != '==') { if (!\strlen($opt_arg)) { if (false === $opt_arg = \current($args)) { throw new Exception( "option --$opt requires an argument" ); } \next($args); } } } elseif ($opt_arg) { throw new Exception( "option --$opt doesn't allow an argument" ); } $full_option = '--' . \preg_replace('/={1,2}$/', '', $long_opt); $opts[] = [$full_option, $opt_arg]; return; } throw new Exception("unrecognized option --$opt"); } } $stdout_handle ]; } public function getCommand(array $settings, $file = null) { return '"' . parent::getCommand($settings, $file) . '"'; } } setCodeCoverage( new CodeCoverage( null, unserialize('{codeCoverageFilter}') ) ); } $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); $result->enforceTimeLimit({enforcesTimeLimit}); $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); $test = new {className}('{methodName}', unserialize('{data}'), '{dataName}'); $test->setDependencyInput(unserialize('{dependencyInput}')); $test->setInIsolation(TRUE); ob_end_clean(); $test->run($result); $output = ''; if (!$test->hasExpectationOnOutput()) { $output = $test->getActualOutput(); } @rewind(STDOUT); if ($stdout = stream_get_contents(STDOUT)) { $output = $stdout . $output; $streamMetaData = stream_get_meta_data(STDOUT); if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { @ftruncate(STDOUT, 0); @rewind(STDOUT); } } print serialize( array( 'testResult' => $test->getResult(), 'numAssertions' => $test->getNumAssertions(), 'result' => $result, 'output' => $output ) ); } $configurationFilePath = '{configurationFilePath}'; if ('' !== $configurationFilePath) { $configuration = PHPUnit\Util\Configuration::getInstance($configurationFilePath); $configuration->handlePHPConfiguration(); unset($configuration); } function __phpunit_error_handler($errno, $errstr, $errfile, $errline, $errcontext) { return true; } set_error_handler("__phpunit_error_handler"); {constants} {included_files} {globals} restore_error_handler(); if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); } __phpunit_run_isolated_test(); useTempFile || $this->stdin) { if (!($this->tempFile = \tempnam(\sys_get_temp_dir(), 'PHPUnit')) || \file_put_contents($this->tempFile, $job) === false) { throw new Exception( 'Unable to write temporary file' ); } $job = $this->stdin; } return $this->runProcess($job, $settings); } protected function getHandles() { return []; } protected function runProcess($job, $settings) { $handles = $this->getHandles(); $env = null; if ($this->env) { $env = $_SERVER ?? []; unset($env['argv'], $env['argc']); $env = \array_merge($env, $this->env); foreach ($env as $envKey => $envVar) { if (\is_array($envVar)) { unset($env[$envKey]); } } } $pipeSpec = [ 0 => $handles[0] ?? ['pipe', 'r'], 1 => $handles[1] ?? ['pipe', 'w'], 2 => $handles[2] ?? ['pipe', 'w'], ]; $process = \proc_open( $this->getCommand($settings, $this->tempFile), $pipeSpec, $pipes, null, $env ); if (!\is_resource($process)) { throw new Exception( 'Unable to spawn worker process' ); } if ($job) { $this->process($pipes[0], $job); } \fclose($pipes[0]); if ($this->timeout) { $stderr = $stdout = ''; unset($pipes[0]); while (true) { $r = $pipes; $w = null; $e = null; $n = @\stream_select($r, $w, $e, $this->timeout); if ($n === false) { break; } elseif ($n === 0) { \proc_terminate($process, 9); throw new Exception(\sprintf('Job execution aborted after %d seconds', $this->timeout)); } elseif ($n > 0) { foreach ($r as $pipe) { $pipeOffset = 0; foreach ($pipes as $i => $origPipe) { if ($pipe == $origPipe) { $pipeOffset = $i; break; } } if (!$pipeOffset) { break; } $line = \fread($pipe, 8192); if (\strlen($line) == 0) { \fclose($pipes[$pipeOffset]); unset($pipes[$pipeOffset]); } else { if ($pipeOffset == 1) { $stdout .= $line; } else { $stderr .= $line; } } } if (empty($pipes)) { break; } } } } else { if (isset($pipes[1])) { $stdout = \stream_get_contents($pipes[1]); \fclose($pipes[1]); } if (isset($pipes[2])) { $stderr = \stream_get_contents($pipes[2]); \fclose($pipes[2]); } } if (isset($handles[1])) { \rewind($handles[1]); $stdout = \stream_get_contents($handles[1]); \fclose($handles[1]); } if (isset($handles[2])) { \rewind($handles[2]); $stderr = \stream_get_contents($handles[2]); \fclose($handles[2]); } \proc_close($process); $this->cleanup(); return ['stdout' => $stdout, 'stderr' => $stderr]; } protected function process($pipe, $job) { \fwrite($pipe, $job); } protected function cleanup() { if ($this->tempFile) { \unlink($this->tempFile); } } } ' . \file_get_contents('php://stdin')); runtime = new Runtime(); } public function setUseStderrRedirection($stderrRedirection) { if (!\is_bool($stderrRedirection)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->stderrRedirection = $stderrRedirection; } public function useStderrRedirection() { return $this->stderrRedirection; } public function setStdin($stdin) { $this->stdin = (string) $stdin; } public function getStdin() { return $this->stdin; } public function setArgs($args) { $this->args = (string) $args; } public function getArgs() { return $this->args; } public function setEnv(array $env) { $this->env = $env; } public function getEnv() { return $this->env; } public function setTimeout($timeout) { $this->timeout = (int) $timeout; } public function getTimeout() { return $this->timeout; } public static function factory() { if (DIRECTORY_SEPARATOR == '\\') { return new WindowsPhpProcess; } return new DefaultPhpProcess; } public function runTestJob($job, Test $test, TestResult $result) { $result->startTest($test); $_result = $this->runJob($job); $this->processChildResult( $test, $result, $_result['stdout'], $_result['stderr'] ); } public function getCommand(array $settings, $file = null) { $command = $this->runtime->getBinary(); $command .= $this->settingsToParameters($settings); if ('phpdbg' === PHP_SAPI) { $command .= ' -qrr '; if ($file) { $command .= '-e ' . \escapeshellarg($file); } else { $command .= \escapeshellarg(__DIR__ . '/eval-stdin.php'); } } elseif ($file) { $command .= ' -f ' . \escapeshellarg($file); } if ($this->args) { $command .= ' -- ' . $this->args; } if (true === $this->stderrRedirection) { $command .= ' 2>&1'; } return $command; } abstract public function runJob($job, array $settings = []); protected function settingsToParameters(array $settings) { $buffer = ''; foreach ($settings as $setting) { $buffer .= ' -d ' . $setting; } return $buffer; } private function processChildResult(Test $test, TestResult $result, $stdout, $stderr) { $time = 0; if (!empty($stderr)) { $result->addError( $test, new Exception(\trim($stderr)), $time ); } else { \set_error_handler(function ($errno, $errstr, $errfile, $errline) { throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); }); try { if (\strpos($stdout, "#!/usr/bin/env php\n") === 0) { $stdout = \substr($stdout, 19); } $childResult = \unserialize(\str_replace("#!/usr/bin/env php\n", '', $stdout)); \restore_error_handler(); } catch (ErrorException $e) { \restore_error_handler(); $childResult = false; $result->addError( $test, new Exception(\trim($stdout), 0, $e), $time ); } if ($childResult !== false) { if (!empty($childResult['output'])) { $output = $childResult['output']; } $test->setResult($childResult['testResult']); $test->addToAssertionCount($childResult['numAssertions']); $childResult = $childResult['result']; if ($result->getCollectCodeCoverageInformation()) { $result->getCodeCoverage()->merge( $childResult->getCodeCoverage() ); } $time = $childResult->time(); $notImplemented = $childResult->notImplemented(); $risky = $childResult->risky(); $skipped = $childResult->skipped(); $errors = $childResult->errors(); $warnings = $childResult->warnings(); $failures = $childResult->failures(); if (!empty($notImplemented)) { $result->addError( $test, $this->getException($notImplemented[0]), $time ); } elseif (!empty($risky)) { $result->addError( $test, $this->getException($risky[0]), $time ); } elseif (!empty($skipped)) { $result->addError( $test, $this->getException($skipped[0]), $time ); } elseif (!empty($errors)) { $result->addError( $test, $this->getException($errors[0]), $time ); } elseif (!empty($warnings)) { $result->addWarning( $test, $this->getException($warnings[0]), $time ); } elseif (!empty($failures)) { $result->addFailure( $test, $this->getException($failures[0]), $time ); } } } $result->endTest($test, $time); if (!empty($output)) { print $output; } } private function getException(TestFailure $error) { $exception = $error->thrownException(); if ($exception instanceof __PHP_Incomplete_Class) { $exceptionArray = []; foreach ((array) $exception as $key => $value) { $key = \substr($key, \strrpos($key, "\0") + 1); $exceptionArray[$key] = $value; } $exception = new SyntheticError( \sprintf( '%s: %s', $exceptionArray['_PHP_Incomplete_Class_Name'], $exceptionArray['message'] ), $exceptionArray['code'], $exceptionArray['file'], $exceptionArray['line'], $exceptionArray['trace'] ); } return $exception; } } PHP(?:Unit)?)\s+(?P[<>=!]{0,2})\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m'; const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[\d\t -.|~^]+)[ \t]*\r?$/m'; const REGEX_REQUIRES_OS = '/@requires\s+OS\s+(?P.+?)[ \t]*\r?$/m'; const REGEX_REQUIRES = '/@requires\s+(?Pfunction|extension)\s+(?P([^ ]+?))\s*(?P[<>=!]{0,2})\s*(?P[\d\.-]+[\d\.]?)?[ \t]*\r?$/m'; const UNKNOWN = -1; const SMALL = 0; const MEDIUM = 1; const LARGE = 2; private static $annotationCache = []; private static $hookMethods = []; public static function describe(\PHPUnit\Framework\Test $test, $asString = true) { if ($asString) { if ($test instanceof SelfDescribing) { return $test->toString(); } return \get_class($test); } if ($test instanceof TestCase) { return [ \get_class($test), $test->getName() ]; } if ($test instanceof SelfDescribing) { return ['', $test->toString()]; } return ['', \get_class($test)]; } public static function getLinesToBeCovered($className, $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) { return false; } return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); } public static function getLinesToBeUsed($className, $methodName) { return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); } private static function getLinesToBeCoveredOrUsed($className, $methodName, $mode) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $classShortcut = null; if (!empty($annotations['class'][$mode . 'DefaultClass'])) { if (\count($annotations['class'][$mode . 'DefaultClass']) > 1) { throw new CodeCoverageException( \sprintf( 'More than one @%sClass annotation in class or interface "%s".', $mode, $className ) ); } $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; } $list = []; if (isset($annotations['class'][$mode])) { $list = $annotations['class'][$mode]; } if (isset($annotations['method'][$mode])) { $list = \array_merge($list, $annotations['method'][$mode]); } $codeList = []; foreach (\array_unique($list) as $element) { if ($classShortcut && \strncmp($element, '::', 2) === 0) { $element = $classShortcut . $element; } $element = \preg_replace('/[\s()]+$/', '', $element); $element = \explode(' ', $element); $element = $element[0]; $codeList = \array_merge( $codeList, self::resolveElementToReflectionObjects($element) ); } return self::resolveReflectionObjectsToLines($codeList); } public static function getRequirements($className, $methodName) { $reflector = new ReflectionClass($className); $docComment = $reflector->getDocComment(); $reflector = new ReflectionMethod($className, $methodName); $docComment .= "\n" . $reflector->getDocComment(); $requires = []; if ($count = \preg_match_all(self::REGEX_REQUIRES_OS, $docComment, $matches)) { $requires['OS'] = \sprintf( '/%s/i', \addcslashes($matches['value'][$count - 1], '/') ); } if ($count = \preg_match_all(self::REGEX_REQUIRES_VERSION, $docComment, $matches)) { foreach (\range(0, $count - 1) as $i) { $requires[$matches['name'][$i]] = [ 'version' => $matches['version'][$i], 'operator' => $matches['operator'][$i] ]; } } if ($count = \preg_match_all(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $docComment, $matches)) { foreach (\range(0, $count - 1) as $i) { if (!empty($requires[$matches['name'][$i]])) { continue; } try { $versionConstraintParser = new VersionConstraintParser; $requires[$matches['name'][$i] . '_constraint'] = [ 'constraint' => $versionConstraintParser->parse(\trim($matches['constraint'][$i])) ]; } catch (\PharIo\Version\Exception $e) { throw new Warning($e->getMessage(), $e->getCode(), $e); } } } if ($count = \preg_match_all(self::REGEX_REQUIRES, $docComment, $matches)) { foreach (\range(0, $count - 1) as $i) { $name = $matches['name'][$i] . 's'; if (!isset($requires[$name])) { $requires[$name] = []; } $requires[$name][] = $matches['value'][$i]; if (empty($matches['version'][$i]) || $name != 'extensions') { continue; } $requires['extension_versions'][$matches['value'][$i]] = [ 'version' => $matches['version'][$i], 'operator' => $matches['operator'][$i] ]; } } return $requires; } public static function getMissingRequirements($className, $methodName) { $required = static::getRequirements($className, $methodName); $missing = []; if (!empty($required['PHP'])) { $operator = empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']; if (!\version_compare(PHP_VERSION, $required['PHP']['version'], $operator)) { $missing[] = \sprintf('PHP %s %s is required.', $operator, $required['PHP']['version']); } } elseif (!empty($required['PHP_constraint'])) { $version = new \PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION)); if (!$required['PHP_constraint']['constraint']->complies($version)) { $missing[] = \sprintf( 'PHP version does not match the required constraint %s.', $required['PHP_constraint']['constraint']->asString() ); } } if (!empty($required['PHPUnit'])) { $phpunitVersion = Version::id(); $operator = empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']; if (!\version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator)) { $missing[] = \sprintf('PHPUnit %s %s is required.', $operator, $required['PHPUnit']['version']); } } elseif (!empty($required['PHPUnit_constraint'])) { $phpunitVersion = new \PharIo\Version\Version(self::sanitizeVersionNumber(Version::id())); if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) { $missing[] = \sprintf( 'PHPUnit version does not match the required constraint %s.', $required['PHPUnit_constraint']['constraint']->asString() ); } } if (!empty($required['OS']) && !\preg_match($required['OS'], PHP_OS)) { $missing[] = \sprintf('Operating system matching %s is required.', $required['OS']); } if (!empty($required['functions'])) { foreach ($required['functions'] as $function) { $pieces = \explode('::', $function); if (2 === \count($pieces) && \method_exists($pieces[0], $pieces[1])) { continue; } if (\function_exists($function)) { continue; } $missing[] = \sprintf('Function %s is required.', $function); } } if (!empty($required['extensions'])) { foreach ($required['extensions'] as $extension) { if (isset($required['extension_versions'][$extension])) { continue; } if (!\extension_loaded($extension)) { $missing[] = \sprintf('Extension %s is required.', $extension); } } } if (!empty($required['extension_versions'])) { foreach ($required['extension_versions'] as $extension => $required) { $actualVersion = \phpversion($extension); $operator = empty($required['operator']) ? '>=' : $required['operator']; if (false === $actualVersion || !\version_compare($actualVersion, $required['version'], $operator)) { $missing[] = \sprintf('Extension %s %s %s is required.', $extension, $operator, $required['version']); } } } return $missing; } public static function getExpectedException($className, $methodName) { $reflector = new ReflectionMethod($className, $methodName); $docComment = $reflector->getDocComment(); $docComment = \substr($docComment, 3, -2); if (\preg_match(self::REGEX_EXPECTED_EXCEPTION, $docComment, $matches)) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $class = $matches[1]; $code = null; $message = ''; $messageRegExp = ''; if (isset($matches[2])) { $message = \trim($matches[2]); } elseif (isset($annotations['method']['expectedExceptionMessage'])) { $message = self::parseAnnotationContent( $annotations['method']['expectedExceptionMessage'][0] ); } if (isset($annotations['method']['expectedExceptionMessageRegExp'])) { $messageRegExp = self::parseAnnotationContent( $annotations['method']['expectedExceptionMessageRegExp'][0] ); } if (isset($matches[3])) { $code = $matches[3]; } elseif (isset($annotations['method']['expectedExceptionCode'])) { $code = self::parseAnnotationContent( $annotations['method']['expectedExceptionCode'][0] ); } if (\is_numeric($code)) { $code = (int) $code; } elseif (\is_string($code) && \defined($code)) { $code = (int) \constant($code); } return [ 'class' => $class, 'code' => $code, 'message' => $message, 'message_regex' => $messageRegExp ]; } return false; } private static function parseAnnotationContent($message) { if (\strpos($message, '::') !== false && \count(\explode('::', $message)) == 2) { if (\defined($message)) { $message = \constant($message); } } return $message; } public static function getProvidedData($className, $methodName) { $reflector = new ReflectionMethod($className, $methodName); $docComment = $reflector->getDocComment(); $data = self::getDataFromDataProviderAnnotation($docComment, $className, $methodName); if ($data === null) { $data = self::getDataFromTestWithAnnotation($docComment); } if (\is_array($data) && empty($data)) { throw new SkippedTestError; } if ($data !== null) { foreach ($data as $key => $value) { if (!\is_array($value)) { throw new Exception( \sprintf( 'Data set %s is invalid.', \is_int($key) ? '#' . $key : '"' . $key . '"' ) ); } } } return $data; } private static function getDataFromDataProviderAnnotation($docComment, $className, $methodName) { if (\preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { $result = []; foreach ($matches[1] as $match) { $dataProviderMethodNameNamespace = \explode('\\', $match); $leaf = \explode('::', \array_pop($dataProviderMethodNameNamespace)); $dataProviderMethodName = \array_pop($leaf); if (!empty($dataProviderMethodNameNamespace)) { $dataProviderMethodNameNamespace = \implode('\\', $dataProviderMethodNameNamespace) . '\\'; } else { $dataProviderMethodNameNamespace = ''; } if (!empty($leaf)) { $dataProviderClassName = $dataProviderMethodNameNamespace . \array_pop($leaf); } else { $dataProviderClassName = $className; } $dataProviderClass = new ReflectionClass($dataProviderClassName); $dataProviderMethod = $dataProviderClass->getMethod( $dataProviderMethodName ); if ($dataProviderMethod->isStatic()) { $object = null; } else { $object = $dataProviderClass->newInstance(); } if ($dataProviderMethod->getNumberOfParameters() == 0) { $data = $dataProviderMethod->invoke($object); } else { $data = $dataProviderMethod->invoke($object, $methodName); } if ($data instanceof Iterator) { $data = \iterator_to_array($data); } if (\is_array($data)) { $result = \array_merge($result, $data); } } return $result; } } public static function getDataFromTestWithAnnotation($docComment) { $docComment = self::cleanUpMultiLineAnnotation($docComment); if (\preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { $offset = \strlen($matches[0][0]) + $matches[0][1]; $annotationContent = \substr($docComment, $offset); $data = []; foreach (\explode("\n", $annotationContent) as $candidateRow) { $candidateRow = \trim($candidateRow); if ($candidateRow[0] !== '[') { break; } $dataSet = \json_decode($candidateRow, true); if (\json_last_error() != JSON_ERROR_NONE) { throw new Exception( 'The dataset for the @testWith annotation cannot be parsed: ' . \json_last_error_msg() ); } $data[] = $dataSet; } if (!$data) { throw new Exception('The dataset for the @testWith annotation cannot be parsed.'); } return $data; } } private static function cleanUpMultiLineAnnotation($docComment) { $docComment = \str_replace("\r\n", "\n", $docComment); $docComment = \preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment); $docComment = \substr($docComment, 0, -1); $docComment = \rtrim($docComment, "\n"); return $docComment; } public static function parseTestMethodAnnotations($className, $methodName = '') { if (!isset(self::$annotationCache[$className])) { $class = new ReflectionClass($className); $traits = $class->getTraits(); $annotations = []; foreach ($traits as $trait) { $annotations = \array_merge( $annotations, self::parseAnnotations($trait->getDocComment()) ); } self::$annotationCache[$className] = \array_merge( $annotations, self::parseAnnotations($class->getDocComment()) ); } if (!empty($methodName) && !isset(self::$annotationCache[$className . '::' . $methodName])) { try { $method = new ReflectionMethod($className, $methodName); $annotations = self::parseAnnotations($method->getDocComment()); } catch (ReflectionException $e) { $annotations = []; } self::$annotationCache[$className . '::' . $methodName] = $annotations; } return [ 'class' => self::$annotationCache[$className], 'method' => !empty($methodName) ? self::$annotationCache[$className . '::' . $methodName] : [] ]; } public static function getInlineAnnotations($className, $methodName) { $method = new ReflectionMethod($className, $methodName); $code = \file($method->getFileName()); $lineNumber = $method->getStartLine(); $startLine = $method->getStartLine() - 1; $endLine = $method->getEndLine() - 1; $methodLines = \array_slice($code, $startLine, $endLine - $startLine + 1); $annotations = []; foreach ($methodLines as $line) { if (\preg_match('#/\*\*?\s*@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?\*/$#m', $line, $matches)) { $annotations[\strtolower($matches['name'])] = [ 'line' => $lineNumber, 'value' => $matches['value'] ]; } $lineNumber++; } return $annotations; } private static function parseAnnotations($docblock) { $annotations = []; $docblock = \substr($docblock, 3, -2); if (\preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docblock, $matches)) { $numMatches = \count($matches[0]); for ($i = 0; $i < $numMatches; ++$i) { $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; } } return $annotations; } public static function getBackupSettings($className, $methodName) { return [ 'backupGlobals' => self::getBooleanAnnotationSetting( $className, $methodName, 'backupGlobals' ), 'backupStaticAttributes' => self::getBooleanAnnotationSetting( $className, $methodName, 'backupStaticAttributes' ) ]; } public static function getDependencies($className, $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $dependencies = []; if (isset($annotations['class']['depends'])) { $dependencies = $annotations['class']['depends']; } if (isset($annotations['method']['depends'])) { $dependencies = \array_merge( $dependencies, $annotations['method']['depends'] ); } return \array_unique($dependencies); } public static function getErrorHandlerSettings($className, $methodName) { return self::getBooleanAnnotationSetting( $className, $methodName, 'errorHandler' ); } public static function getGroups($className, $methodName = '') { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $groups = []; if (isset($annotations['method']['author'])) { $groups = $annotations['method']['author']; } elseif (isset($annotations['class']['author'])) { $groups = $annotations['class']['author']; } if (isset($annotations['class']['group'])) { $groups = \array_merge($groups, $annotations['class']['group']); } if (isset($annotations['method']['group'])) { $groups = \array_merge($groups, $annotations['method']['group']); } if (isset($annotations['class']['ticket'])) { $groups = \array_merge($groups, $annotations['class']['ticket']); } if (isset($annotations['method']['ticket'])) { $groups = \array_merge($groups, $annotations['method']['ticket']); } foreach (['method', 'class'] as $element) { foreach (['small', 'medium', 'large'] as $size) { if (isset($annotations[$element][$size])) { $groups[] = $size; break 2; } } } return \array_unique($groups); } public static function getSize($className, $methodName) { $groups = \array_flip(self::getGroups($className, $methodName)); $class = new ReflectionClass($className); if (isset($groups['large']) || (\class_exists('PHPUnit\DbUnit\TestCase', false) && $class->isSubclassOf('PHPUnit\DbUnit\TestCase'))) { return self::LARGE; } if (isset($groups['medium'])) { return self::MEDIUM; } if (isset($groups['small'])) { return self::SMALL; } return self::UNKNOWN; } public static function getProcessIsolationSettings($className, $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); if (isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess'])) { return true; } return false; } public static function getPreserveGlobalStateSettings($className, $methodName) { return self::getBooleanAnnotationSetting( $className, $methodName, 'preserveGlobalState' ); } public static function getHookMethods($className) { if (!\class_exists($className, false)) { return self::emptyHookMethodsArray(); } if (!isset(self::$hookMethods[$className])) { self::$hookMethods[$className] = self::emptyHookMethodsArray(); try { $class = new ReflectionClass($className); foreach ($class->getMethods() as $method) { if (self::isBeforeClassMethod($method)) { \array_unshift( self::$hookMethods[$className]['beforeClass'], $method->getName() ); } if (self::isBeforeMethod($method)) { \array_unshift( self::$hookMethods[$className]['before'], $method->getName() ); } if (self::isAfterMethod($method)) { self::$hookMethods[$className]['after'][] = $method->getName(); } if (self::isAfterClassMethod($method)) { self::$hookMethods[$className]['afterClass'][] = $method->getName(); } } } catch (ReflectionException $e) { } } return self::$hookMethods[$className]; } private static function emptyHookMethodsArray() { return [ 'beforeClass' => ['setUpBeforeClass'], 'before' => ['setUp'], 'after' => ['tearDown'], 'afterClass' => ['tearDownAfterClass'] ]; } private static function getBooleanAnnotationSetting($className, $methodName, $settingName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); if (isset($annotations['class'][$settingName])) { if ($annotations['class'][$settingName][0] == 'enabled') { return true; } if ($annotations['class'][$settingName][0] == 'disabled') { return false; } } if (isset($annotations['method'][$settingName])) { if ($annotations['method'][$settingName][0] == 'enabled') { return true; } if ($annotations['method'][$settingName][0] == 'disabled') { return false; } } return; } private static function resolveElementToReflectionObjects($element) { $codeToCoverList = []; if (\strpos($element, '\\') !== false && \function_exists($element)) { $codeToCoverList[] = new ReflectionFunction($element); } elseif (\strpos($element, '::') !== false) { list($className, $methodName) = \explode('::', $element); if (isset($methodName[0]) && $methodName[0] == '<') { $classes = [$className]; foreach ($classes as $className) { if (!\class_exists($className) && !\interface_exists($className) && !\trait_exists($className)) { throw new InvalidCoversTargetException( \sprintf( 'Trying to @cover or @use not existing class or ' . 'interface "%s".', $className ) ); } $class = new ReflectionClass($className); $methods = $class->getMethods(); $inverse = isset($methodName[1]) && $methodName[1] == '!'; if (\strpos($methodName, 'protected')) { $visibility = 'isProtected'; } elseif (\strpos($methodName, 'private')) { $visibility = 'isPrivate'; } elseif (\strpos($methodName, 'public')) { $visibility = 'isPublic'; } foreach ($methods as $method) { if ($inverse && !$method->$visibility()) { $codeToCoverList[] = $method; } elseif (!$inverse && $method->$visibility()) { $codeToCoverList[] = $method; } } } } else { $classes = [$className]; foreach ($classes as $className) { if ($className == '' && \function_exists($methodName)) { $codeToCoverList[] = new ReflectionFunction( $methodName ); } else { if (!((\class_exists($className) || \interface_exists($className) || \trait_exists($className)) && \method_exists($className, $methodName))) { throw new InvalidCoversTargetException( \sprintf( 'Trying to @cover or @use not existing method "%s::%s".', $className, $methodName ) ); } $codeToCoverList[] = new ReflectionMethod( $className, $methodName ); } } } } else { $extended = false; if (\strpos($element, '') !== false) { $element = \str_replace('', '', $element); $extended = true; } $classes = [$element]; if ($extended) { $classes = \array_merge( $classes, \class_implements($element), \class_parents($element) ); } foreach ($classes as $className) { if (!\class_exists($className) && !\interface_exists($className) && !\trait_exists($className)) { throw new InvalidCoversTargetException( \sprintf( 'Trying to @cover or @use not existing class or ' . 'interface "%s".', $className ) ); } $codeToCoverList[] = new ReflectionClass($className); } } return $codeToCoverList; } private static function resolveReflectionObjectsToLines(array $reflectors) { $result = []; foreach ($reflectors as $reflector) { $filename = $reflector->getFileName(); if (!isset($result[$filename])) { $result[$filename] = []; } $result[$filename] = \array_unique( \array_merge( $result[$filename], \range($reflector->getStartLine(), $reflector->getEndLine()) ) ); } return $result; } private static function isBeforeClassMethod(ReflectionMethod $method) { return $method->isStatic() && \strpos($method->getDocComment(), '@beforeClass') !== false; } private static function isBeforeMethod(ReflectionMethod $method) { return \preg_match('/@before\b/', $method->getDocComment()); } private static function isAfterClassMethod(ReflectionMethod $method) { return $method->isStatic() && \strpos($method->getDocComment(), '@afterClass') !== false; } private static function isAfterMethod(ReflectionMethod $method) { return \preg_match('/@after\b/', $method->getDocComment()); } private static function sanitizeVersionNumber($version) { return \preg_replace( '/^(\d+\.\d+(?:.\d+)?).*$/', '$1', $version ); } } 0; $i--) { $file = $files[$i]; if ($prefix !== false && \strpos($file, $prefix) === 0) { continue; } if (\preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) { continue; } if (!$blacklist->isBlacklisted($file) && \is_file($file)) { $result = 'require_once \'' . $file . "';\n" . $result; } } return $result; } public static function getIniSettingsAsString() { $result = ''; $iniSettings = \ini_get_all(null, false); foreach ($iniSettings as $key => $value) { $result .= \sprintf( '@ini_set(%s, %s);' . "\n", self::exportVariable($key), self::exportVariable($value) ); } return $result; } public static function getConstantsAsString() { $constants = \get_defined_constants(true); $result = ''; if (isset($constants['user'])) { foreach ($constants['user'] as $name => $value) { $result .= \sprintf( 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, self::exportVariable($value) ); } } return $result; } public static function getGlobalsAsString() { $result = ''; $superGlobalArrays = self::getSuperGlobalArrays(); foreach ($superGlobalArrays as $superGlobalArray) { if (isset($GLOBALS[$superGlobalArray]) && \is_array($GLOBALS[$superGlobalArray])) { foreach (\array_keys($GLOBALS[$superGlobalArray]) as $key) { if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { continue; } $result .= \sprintf( '$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n", $superGlobalArray, $key, self::exportVariable($GLOBALS[$superGlobalArray][$key]) ); } } } $blacklist = $superGlobalArrays; $blacklist[] = 'GLOBALS'; foreach (\array_keys($GLOBALS) as $key) { if (!\in_array($key, $blacklist) && !$GLOBALS[$key] instanceof Closure) { $result .= \sprintf( '$GLOBALS[\'%s\'] = %s;' . "\n", $key, self::exportVariable($GLOBALS[$key]) ); } } return $result; } protected static function getSuperGlobalArrays() { return self::$superGlobalArrays; } protected static function exportVariable($variable) { if (\is_scalar($variable) || \is_null($variable) || (\is_array($variable) && self::arrayOnlyContainsScalars($variable))) { return \var_export($variable, true); } return 'unserialize(' . \var_export(\serialize($variable), true) . ')'; } protected static function arrayOnlyContainsScalars(array $array) { $result = true; foreach ($array as $element) { if (\is_array($element)) { $result = self::arrayOnlyContainsScalars($element); } elseif (!\is_scalar($element) && !\is_null($element)) { $result = false; } if ($result === false) { break; } } return $result; } } printHeader(); $this->printFooter($result); } public function addError(Test $test, \Exception $e, $time) { $this->printEvent( 'testFailed', [ 'name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e), ] ); } public function addWarning(Test $test, Warning $e, $time) { $this->printEvent( 'testFailed', [ 'name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e) ] ); } public function addFailure(Test $test, AssertionFailedError $e, $time) { $parameters = [ 'name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e), ]; if ($e instanceof ExpectationFailedException) { $comparisonFailure = $e->getComparisonFailure(); if ($comparisonFailure instanceof ComparisonFailure) { $expectedString = $comparisonFailure->getExpectedAsString(); if (\is_null($expectedString) || empty($expectedString)) { $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); } $actualString = $comparisonFailure->getActualAsString(); if (\is_null($actualString) || empty($actualString)) { $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); } if (!\is_null($actualString) && !\is_null($expectedString)) { $parameters['type'] = 'comparisonFailure'; $parameters['actual'] = $actualString; $parameters['expected'] = $expectedString; } } } $this->printEvent('testFailed', $parameters); } public function addIncompleteTest(Test $test, \Exception $e, $time) { $this->printIgnoredTest($test->getName(), $e); } public function addRiskyTest(Test $test, \Exception $e, $time) { $this->addError($test, $e, $time); } public function addSkippedTest(Test $test, \Exception $e, $time) { $testName = $test->getName(); if ($this->startedTestName != $testName) { $this->startTest($test); $this->printIgnoredTest($testName, $e); $this->endTest($test, $time); } else { $this->printIgnoredTest($testName, $e); } } public function printIgnoredTest($testName, \Exception $e) { $this->printEvent( 'testIgnored', [ 'name' => $testName, 'message' => self::getMessage($e), 'details' => self::getDetails($e), ] ); } public function startTestSuite(TestSuite $suite) { if (\stripos(\ini_get('disable_functions'), 'getmypid') === false) { $this->flowId = \getmypid(); } else { $this->flowId = false; } if (!$this->isSummaryTestCountPrinted) { $this->isSummaryTestCountPrinted = true; $this->printEvent( 'testCount', ['count' => \count($suite)] ); } $suiteName = $suite->getName(); if (empty($suiteName)) { return; } $parameters = ['name' => $suiteName]; if (\class_exists($suiteName, false)) { $fileName = self::getFileName($suiteName); $parameters['locationHint'] = "php_qn://$fileName::\\$suiteName"; } else { $split = \preg_split('/::/', $suiteName); if (\count($split) == 2 && \method_exists($split[0], $split[1])) { $fileName = self::getFileName($split[0]); $parameters['locationHint'] = "php_qn://$fileName::\\$suiteName"; $parameters['name'] = $split[1]; } } $this->printEvent('testSuiteStarted', $parameters); } public function endTestSuite(TestSuite $suite) { $suiteName = $suite->getName(); if (empty($suiteName)) { return; } $parameters = ['name' => $suiteName]; if (!\class_exists($suiteName, false)) { $split = \preg_split('/::/', $suiteName); if (\count($split) == 2 && \method_exists($split[0], $split[1])) { $parameters['name'] = $split[1]; } } $this->printEvent('testSuiteFinished', $parameters); } public function startTest(Test $test) { $testName = $test->getName(); $this->startedTestName = $testName; $params = ['name' => $testName]; if ($test instanceof TestCase) { $className = \get_class($test); $fileName = self::getFileName($className); $params['locationHint'] = "php_qn://$fileName::\\$className::$testName"; } $this->printEvent('testStarted', $params); } public function endTest(Test $test, $time) { parent::endTest($test, $time); $this->printEvent( 'testFinished', [ 'name' => $test->getName(), 'duration' => (int) (\round($time, 2) * 1000) ] ); } private function printEvent($eventName, $params = []) { $this->write("\n##teamcity[$eventName"); if ($this->flowId) { $params['flowId'] = $this->flowId; } foreach ($params as $key => $value) { $escapedValue = self::escapeValue($value); $this->write(" $key='$escapedValue'"); } $this->write("]\n"); } private static function getMessage(\Exception $e) { $message = ''; if (!$e instanceof Exception) { if (\strlen(\get_class($e)) != 0) { $message .= \get_class($e); } if (\strlen($message) != 0 && \strlen($e->getMessage()) != 0) { $message .= ' : '; } } return $message . $e->getMessage(); } private static function getDetails(\Exception $e) { $stackTrace = Filter::getFilteredStacktrace($e); $previous = $e instanceof ExceptionWrapper ? $e->getPreviousWrapped() : $e->getPrevious(); while ($previous) { $stackTrace .= "\nCaused by\n" . TestFailure::exceptionToString($previous) . "\n" . Filter::getFilteredStacktrace($previous); $previous = $previous instanceof ExceptionWrapper ? $previous->getPreviousWrapped() : $previous->getPrevious(); } return ' ' . \str_replace("\n", "\n ", $stackTrace); } private static function getPrimitiveValueAsString($value) { if (\is_null($value)) { return 'null'; } if (\is_bool($value)) { return $value == true ? 'true' : 'false'; } if (\is_scalar($value)) { return \print_r($value, true); } } private static function escapeValue($text) { $text = \str_replace('|', '||', $text); $text = \str_replace("'", "|'", $text); $text = \str_replace("\n", '|n', $text); $text = \str_replace("\r", '|r', $text); $text = \str_replace(']', '|]', $text); $text = \str_replace('[', '|[', $text); return $text; } private static function getFileName($className) { $reflectionClass = new ReflectionClass($className); $fileName = $reflectionClass->getFileName(); return $fileName; } } document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->root = $this->document->createElement('testsuites'); $this->document->appendChild($this->root); parent::__construct($out); $this->reportUselessTests = $reportUselessTests; } public function flush() { if ($this->writeDocument === true) { $this->write($this->getXML()); } parent::flush(); } public function addError(Test $test, \Exception $e, $time) { $this->doAddFault($test, $e, $time, 'error'); $this->testSuiteErrors[$this->testSuiteLevel]++; } public function addWarning(Test $test, Warning $e, $time) { $this->doAddFault($test, $e, $time, 'warning'); $this->testSuiteFailures[$this->testSuiteLevel]++; } public function addFailure(Test $test, AssertionFailedError $e, $time) { $this->doAddFault($test, $e, $time, 'failure'); $this->testSuiteFailures[$this->testSuiteLevel]++; } public function addIncompleteTest(Test $test, \Exception $e, $time) { $this->doAddSkipped($test); } public function addRiskyTest(Test $test, \Exception $e, $time) { if (!$this->reportUselessTests || $this->currentTestCase === null) { return; } $error = $this->document->createElement( 'error', Xml::prepareString( "Risky Test\n" . Filter::getFilteredStacktrace($e) ) ); $error->setAttribute('type', \get_class($e)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } public function addSkippedTest(Test $test, \Exception $e, $time) { $this->doAddSkipped($test); } public function startTestSuite(TestSuite $suite) { $testSuite = $this->document->createElement('testsuite'); $testSuite->setAttribute('name', $suite->getName()); if (\class_exists($suite->getName(), false)) { try { $class = new ReflectionClass($suite->getName()); $testSuite->setAttribute('file', $class->getFileName()); } catch (ReflectionException $e) { } } if ($this->testSuiteLevel > 0) { $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); } else { $this->root->appendChild($testSuite); } $this->testSuiteLevel++; $this->testSuites[$this->testSuiteLevel] = $testSuite; $this->testSuiteTests[$this->testSuiteLevel] = 0; $this->testSuiteAssertions[$this->testSuiteLevel] = 0; $this->testSuiteErrors[$this->testSuiteLevel] = 0; $this->testSuiteFailures[$this->testSuiteLevel] = 0; $this->testSuiteSkipped[$this->testSuiteLevel] = 0; $this->testSuiteTimes[$this->testSuiteLevel] = 0; } public function endTestSuite(TestSuite $suite) { $this->testSuites[$this->testSuiteLevel]->setAttribute( 'tests', $this->testSuiteTests[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'assertions', $this->testSuiteAssertions[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'errors', $this->testSuiteErrors[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'failures', $this->testSuiteFailures[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'skipped', $this->testSuiteSkipped[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'time', \sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]) ); if ($this->testSuiteLevel > 1) { $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; } $this->testSuiteLevel--; } public function startTest(Test $test) { $testCase = $this->document->createElement('testcase'); $testCase->setAttribute('name', $test->getName()); if ($test instanceof TestCase) { $class = new ReflectionClass($test); $methodName = $test->getName(!$test->usesDataProvider()); if ($class->hasMethod($methodName)) { $method = $class->getMethod($methodName); $testCase->setAttribute('class', $class->getName()); $testCase->setAttribute('classname', \str_replace('\\', '.', $class->getName())); $testCase->setAttribute('file', $class->getFileName()); $testCase->setAttribute('line', $method->getStartLine()); } } $this->currentTestCase = $testCase; } public function endTest(Test $test, $time) { if ($test instanceof TestCase) { $numAssertions = $test->getNumAssertions(); $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', $numAssertions ); } $this->currentTestCase->setAttribute( 'time', \sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; if (\method_exists($test, 'hasOutput') && $test->hasOutput()) { $systemOut = $this->document->createElement('system-out'); $systemOut->appendChild( $this->document->createTextNode($test->getActualOutput()) ); $this->currentTestCase->appendChild($systemOut); } $this->currentTestCase = null; } public function getXML() { return $this->document->saveXML(); } public function setWriteDocument($flag) { if (\is_bool($flag)) { $this->writeDocument = $flag; } } private function doAddFault(Test $test, \Exception $e, $time, $type) { if ($this->currentTestCase === null) { return; } if ($test instanceof SelfDescribing) { $buffer = $test->toString() . "\n"; } else { $buffer = ''; } $buffer .= TestFailure::exceptionToString($e) . "\n" . Filter::getFilteredStacktrace($e); $fault = $this->document->createElement( $type, Xml::prepareString($buffer) ); if ($e instanceof ExceptionWrapper) { $fault->setAttribute('type', $e->getClassName()); } else { $fault->setAttribute('type', \get_class($e)); } $this->currentTestCase->appendChild($fault); } private function doAddSkipped(Test $test) { if ($this->currentTestCase === null) { return; } $skipped = $this->document->createElement('skipped'); $this->currentTestCase->appendChild($skipped); $this->testSuiteSkipped[$this->testSuiteLevel]++; } } getSyntheticTrace(); $eFile = $e->getSyntheticFile(); $eLine = $e->getSyntheticLine(); } elseif ($e instanceof Exception) { $eTrace = $e->getSerializableTrace(); $eFile = $e->getFile(); $eLine = $e->getLine(); } else { if ($e->getPrevious()) { $e = $e->getPrevious(); } $eTrace = $e->getTrace(); $eFile = $e->getFile(); $eLine = $e->getLine(); } if (!self::frameExists($eTrace, $eFile, $eLine)) { \array_unshift( $eTrace, ['file' => $eFile, 'line' => $eLine] ); } $blacklist = new Blacklist; foreach ($eTrace as $frame) { if (isset($frame['file']) && \is_file($frame['file']) && !$blacklist->isBlacklisted($frame['file']) && ($prefix === false || \strpos($frame['file'], $prefix) !== 0) && $frame['file'] !== $script) { if ($asString === true) { $filteredStacktrace .= \sprintf( "%s:%s\n", $frame['file'], $frame['line'] ?? '?' ); } else { $filteredStacktrace[] = $frame; } } } return $filteredStacktrace; } private static function frameExists(array $trace, $file, $line) { foreach ($trace as $frame) { if (isset($frame['file']) && $frame['file'] == $file && isset($frame['line']) && $frame['line'] == $line) { return true; } } return false; } } preserveWhiteSpace = false; $internal = \libxml_use_internal_errors(true); $message = ''; $reporting = \error_reporting(0); if ('' !== $filename) { $document->documentURI = $filename; } if ($isHtml) { $loaded = $document->loadHTML($actual); } else { $loaded = $document->loadXML($actual); } if (!$isHtml && $xinclude) { $document->xinclude(); } foreach (\libxml_get_errors() as $error) { $message .= "\n" . $error->message; } \libxml_use_internal_errors($internal); \error_reporting($reporting); if ($xinclude) { @\chdir($cwd); } if ($loaded === false || ($strict && $message !== '')) { if ($filename !== '') { throw new Exception( \sprintf( 'Could not load "%s".%s', $filename, $message != '' ? "\n" . $message : '' ) ); } if ($message === '') { $message = 'Could not load XML for unknown reason'; } throw new Exception($message); } return $document; } public static function loadFile($filename, $isHtml = false, $xinclude = false, $strict = false) { $reporting = \error_reporting(0); $contents = \file_get_contents($filename); \error_reporting($reporting); if ($contents === false) { throw new Exception( \sprintf( 'Could not read "%s".', $filename ) ); } return self::load($contents, $isHtml, $filename, $xinclude, $strict); } public static function removeCharacterDataNodes(DOMNode $node) { if ($node->hasChildNodes()) { for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { $node->removeChild($child); } } } } public static function prepareString($string) { return \preg_replace( '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', '', \htmlspecialchars( self::convertToUtf8($string), ENT_QUOTES, 'UTF-8' ) ); } public static function xmlToVariable(DOMElement $element) { $variable = null; switch ($element->tagName) { case 'array': $variable = []; foreach ($element->childNodes as $entry) { if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { continue; } $item = $entry->childNodes->item(0); if ($item instanceof DOMText) { $item = $entry->childNodes->item(1); } $value = self::xmlToVariable($item); if ($entry->hasAttribute('key')) { $variable[(string) $entry->getAttribute('key')] = $value; } else { $variable[] = $value; } } break; case 'object': $className = $element->getAttribute('class'); if ($element->hasChildNodes()) { $arguments = $element->childNodes->item(1)->childNodes; $constructorArgs = []; foreach ($arguments as $argument) { if ($argument instanceof DOMElement) { $constructorArgs[] = self::xmlToVariable($argument); } } $class = new ReflectionClass($className); $variable = $class->newInstanceArgs($constructorArgs); } else { $variable = new $className; } break; case 'boolean': $variable = $element->textContent == 'true' ? true : false; break; case 'integer': case 'double': case 'string': $variable = $element->textContent; \settype($variable, $element->tagName); break; } return $variable; } private static function convertToUtf8($string) { if (!self::isUtf8($string)) { if (\function_exists('mb_convert_encoding')) { return \mb_convert_encoding($string, 'UTF-8'); } return \utf8_encode($string); } return $string; } private static function isUtf8($string) { $length = \strlen($string); for ($i = 0; $i < $length; $i++) { if (\ord($string[$i]) < 0x80) { $n = 0; } elseif ((\ord($string[$i]) & 0xE0) == 0xC0) { $n = 1; } elseif ((\ord($string[$i]) & 0xF0) == 0xE0) { $n = 2; } elseif ((\ord($string[$i]) & 0xF0) == 0xF0) { $n = 3; } else { return false; } for ($j = 0; $j < $n; $j++) { if ((++$i == $length) || ((\ord($string[$i]) & 0xC0) != 0x80)) { return false; } } } return true; } } out = \fsockopen($out[0], $out[1]); } else { if (\strpos($out, 'php://') === false && !\is_dir(\dirname($out))) { \mkdir(\dirname($out), 0777, true); } $this->out = \fopen($out, 'wt'); } $this->outTarget = $out; } else { $this->out = $out; } } } public function flush() { if ($this->out && \strncmp($this->outTarget, 'php://', 6) !== 0) { \fclose($this->out); } } public function incrementalFlush() { if ($this->out) { \fflush($this->out); } else { \flush(); } } public function write($buffer) { if ($this->out) { \fwrite($this->out, $buffer); if ($this->autoFlush) { $this->incrementalFlush(); } } else { if (PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg') { $buffer = \htmlspecialchars($buffer, ENT_SUBSTITUTE); } print $buffer; if ($this->autoFlush) { $this->incrementalFlush(); } } } public function getAutoFlush() { return $this->autoFlush; } public function setAutoFlush($autoFlush) { if (\is_bool($autoFlush)) { $this->autoFlush = $autoFlush; } else { throw InvalidArgumentHelper::factory(1, 'boolean'); } } } Test Documentation EOT; private $classHeader = <<%s
      EOT; private $classFooter = << EOT; private $pageFooter = << EOT; protected function startRun() { $this->write($this->pageHeader); } protected function startClass($name) { $this->write( \sprintf( $this->classHeader, $name, $this->currentTestClassPrettified ) ); } protected function onTest($name, $success = true) { $this->write( \sprintf( "
    • %s %s
    • \n", $success ? '#555753' : '#ef2929', $success ? '✓' : '❌', $name ) ); } protected function endClass($name) { $this->write($this->classFooter); } protected function endRun() { $this->write($this->pageFooter); } } groups = $groups; $this->excludeGroups = $excludeGroups; $this->prettifier = new NamePrettifier; $this->startRun(); } public function flush() { $this->doEndClass(); $this->endRun(); parent::flush(); } public function addError(Test $test, \Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_ERROR; $this->failed++; } public function addWarning(Test $test, Warning $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_WARNING; $this->warned++; } public function addFailure(Test $test, AssertionFailedError $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_FAILURE; $this->failed++; } public function addIncompleteTest(Test $test, \Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_INCOMPLETE; $this->incomplete++; } public function addRiskyTest(Test $test, \Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_RISKY; $this->risky++; } public function addSkippedTest(Test $test, \Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = BaseTestRunner::STATUS_SKIPPED; $this->skipped++; } public function startTestSuite(TestSuite $suite) { } public function endTestSuite(TestSuite $suite) { } public function startTest(Test $test) { if (!$this->isOfInterest($test)) { return; } $class = \get_class($test); if ($this->testClass != $class) { if ($this->testClass != '') { $this->doEndClass(); } $classAnnotations = \PHPUnit\Util\Test::parseTestMethodAnnotations($class); if (isset($classAnnotations['class']['testdox'][0])) { $this->currentTestClassPrettified = $classAnnotations['class']['testdox'][0]; } else { $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); } $this->startClass($class); $this->testClass = $class; $this->tests = []; } $annotations = $test->getAnnotations(); if (isset($annotations['method']['testdox'][0])) { $this->currentTestMethodPrettified = $annotations['method']['testdox'][0]; } else { $this->currentTestMethodPrettified = $this->prettifier->prettifyTestMethod($test->getName(false)); } if ($test instanceof TestCase && $test->usesDataProvider()) { $this->currentTestMethodPrettified .= ' ' . $test->dataDescription(); } $this->testStatus = BaseTestRunner::STATUS_PASSED; } public function endTest(Test $test, $time) { if (!$this->isOfInterest($test)) { return; } if (!isset($this->tests[$this->currentTestMethodPrettified])) { if ($this->testStatus == BaseTestRunner::STATUS_PASSED) { $this->tests[$this->currentTestMethodPrettified]['success'] = 1; $this->tests[$this->currentTestMethodPrettified]['failure'] = 0; } else { $this->tests[$this->currentTestMethodPrettified]['success'] = 0; $this->tests[$this->currentTestMethodPrettified]['failure'] = 1; } } else { if ($this->testStatus == BaseTestRunner::STATUS_PASSED) { $this->tests[$this->currentTestMethodPrettified]['success']++; } else { $this->tests[$this->currentTestMethodPrettified]['failure']++; } } $this->currentTestClassPrettified = null; $this->currentTestMethodPrettified = null; } protected function doEndClass() { foreach ($this->tests as $name => $data) { $this->onTest($name, $data['failure'] == 0); } $this->endClass($this->testClass); } protected function startRun() { } protected function startClass($name) { } protected function onTest($name, $success = true) { } protected function endClass($name) { } protected function endRun() { } private function isOfInterest(Test $test) { if (!$test instanceof TestCase) { return false; } if ($test instanceof WarningTestCase) { return false; } if (!empty($this->groups)) { foreach ($test->getGroups() as $group) { if (\in_array($group, $this->groups)) { return true; } } return false; } if (!empty($this->excludeGroups)) { foreach ($test->getGroups() as $group) { if (\in_array($group, $this->excludeGroups)) { return false; } } return true; } return true; } } document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->root = $this->document->createElement('tests'); $this->document->appendChild($this->root); $this->prettifier = new NamePrettifier; parent::__construct($out); } public function flush() { $this->write($this->document->saveXML()); parent::flush(); } public function addError(Test $test, \Exception $e, $time) { $this->exception = $e; } public function addWarning(Test $test, Warning $e, $time) { } public function addFailure(Test $test, AssertionFailedError $e, $time) { $this->exception = $e; } public function addIncompleteTest(Test $test, \Exception $e, $time) { } public function addRiskyTest(Test $test, \Exception $e, $time) { } public function addSkippedTest(Test $test, \Exception $e, $time) { } public function startTestSuite(TestSuite $suite) { } public function endTestSuite(TestSuite $suite) { } public function startTest(Test $test) { $this->exception = null; } public function endTest(Test $test, $time) { if (!$test instanceof TestCase) { return; } $groups = \array_filter( $test->getGroups(), function ($group) { if ($group == 'small' || $group == 'medium' || $group == 'large') { return false; } return true; } ); $node = $this->document->createElement('test'); $node->setAttribute('className', \get_class($test)); $node->setAttribute('methodName', $test->getName()); $node->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(\get_class($test))); $node->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestMethod($test->getName())); $node->setAttribute('status', $test->getStatus()); $node->setAttribute('time', $time); $node->setAttribute('size', $test->getSize()); $node->setAttribute('groups', \implode(',', $groups)); $inlineAnnotations = \PHPUnit\Util\Test::getInlineAnnotations(\get_class($test), $test->getName()); if (isset($inlineAnnotations['given']) && isset($inlineAnnotations['when']) && isset($inlineAnnotations['then'])) { $node->setAttribute('given', $inlineAnnotations['given']['value']); $node->setAttribute('givenStartLine', $inlineAnnotations['given']['line']); $node->setAttribute('when', $inlineAnnotations['when']['value']); $node->setAttribute('whenStartLine', $inlineAnnotations['when']['line']); $node->setAttribute('then', $inlineAnnotations['then']['value']); $node->setAttribute('thenStartLine', $inlineAnnotations['then']['line']); } if ($this->exception !== null) { if ($this->exception instanceof Exception) { $steps = $this->exception->getSerializableTrace(); } else { $steps = $this->exception->getTrace(); } $class = new ReflectionClass($test); $file = $class->getFileName(); foreach ($steps as $step) { if (isset($step['file']) && $step['file'] == $file) { $node->setAttribute('exceptionLine', $step['line']); break; } } $node->setAttribute('exceptionMessage', $this->exception->getMessage()); } $this->root->appendChild($node); } } write($this->currentTestClassPrettified . "\n"); } protected function onTest($name, $success = true) { if ($success) { $this->write(' [x] '); } else { $this->write(' [ ] '); } $this->write($name . "\n"); } protected function endClass($name) { $this->write("\n"); } } suffix !== null && $this->suffix == \substr($name, -1 * \strlen($this->suffix))) { $title = \substr($title, 0, \strripos($title, $this->suffix)); } if ($this->prefix !== null && $this->prefix == \substr($name, 0, \strlen($this->prefix))) { $title = \substr($title, \strlen($this->prefix)); } if (\substr($title, 0, 1) == '\\') { $title = \substr($title, 1); } return $title; } public function prettifyTestMethod($name) { $buffer = ''; if (!\is_string($name) || \strlen($name) == 0) { return $buffer; } $string = \preg_replace('#\d+$#', '', $name, -1, $count); if (\in_array($string, $this->strings)) { $name = $string; } elseif ($count == 0) { $this->strings[] = $string; } if (\substr($name, 0, 4) == 'test') { $name = \substr($name, 4); } if (\strlen($name) == 0) { return $buffer; } $name[0] = \strtoupper($name[0]); if (\strpos($name, '_') !== false) { return \trim(\str_replace('_', ' ', $name)); } $max = \strlen($name); $wasNumeric = false; for ($i = 0; $i < $max; $i++) { if ($i > 0 && \ord($name[$i]) >= 65 && \ord($name[$i]) <= 90) { $buffer .= ' ' . \strtolower($name[$i]); } else { $isNumeric = \is_numeric($name[$i]); if (!$wasNumeric && $isNumeric) { $buffer .= ' '; $wasNumeric = true; } if ($wasNumeric && !$isNumeric) { $wasNumeric = false; } $buffer .= $name[$i]; } } return $buffer; } public function setPrefix($prefix) { $this->prefix = $prefix; } public function setSuffix($suffix) { $this->suffix = $suffix; } } filename = $filename; $this->document = Xml::loadFile($filename, false, true, true); $this->xpath = new DOMXPath($this->document); } final private function __clone() { } public static function getInstance($filename) { $realpath = \realpath($filename); if ($realpath === false) { throw new Exception( \sprintf( 'Could not read "%s".', $filename ) ); } if (!isset(self::$instances[$realpath])) { self::$instances[$realpath] = new self($realpath); } return self::$instances[$realpath]; } public function getFilename() { return $this->filename; } public function getFilterConfiguration() { $addUncoveredFilesFromWhitelist = true; $processUncoveredFilesFromWhitelist = false; $tmp = $this->xpath->query('filter/whitelist'); if ($tmp->length == 0) { return [ 'whitelist' => [] ]; } if ($tmp->length == 1) { if ($tmp->item(0)->hasAttribute('addUncoveredFilesFromWhitelist')) { $addUncoveredFilesFromWhitelist = $this->getBoolean( (string) $tmp->item(0)->getAttribute( 'addUncoveredFilesFromWhitelist' ), true ); } if ($tmp->item(0)->hasAttribute('processUncoveredFilesFromWhitelist')) { $processUncoveredFilesFromWhitelist = $this->getBoolean( (string) $tmp->item(0)->getAttribute( 'processUncoveredFilesFromWhitelist' ), false ); } } return [ 'whitelist' => [ 'addUncoveredFilesFromWhitelist' => $addUncoveredFilesFromWhitelist, 'processUncoveredFilesFromWhitelist' => $processUncoveredFilesFromWhitelist, 'include' => [ 'directory' => $this->readFilterDirectories( 'filter/whitelist/directory' ), 'file' => $this->readFilterFiles( 'filter/whitelist/file' ) ], 'exclude' => [ 'directory' => $this->readFilterDirectories( 'filter/whitelist/exclude/directory' ), 'file' => $this->readFilterFiles( 'filter/whitelist/exclude/file' ) ] ] ]; } public function getGroupConfiguration() { return $this->parseGroupConfiguration('groups'); } public function getTestdoxGroupConfiguration() { return $this->parseGroupConfiguration('testdoxGroups'); } private function parseGroupConfiguration($root) { $groups = [ 'include' => [], 'exclude' => [] ]; foreach ($this->xpath->query($root . '/include/group') as $group) { $groups['include'][] = (string) $group->textContent; } foreach ($this->xpath->query($root . '/exclude/group') as $group) { $groups['exclude'][] = (string) $group->textContent; } return $groups; } public function getListenerConfiguration() { $result = []; foreach ($this->xpath->query('listeners/listener') as $listener) { $class = (string) $listener->getAttribute('class'); $file = ''; $arguments = []; if ($listener->getAttribute('file')) { $file = $this->toAbsolutePath( (string) $listener->getAttribute('file'), true ); } foreach ($listener->childNodes as $node) { if ($node instanceof DOMElement && $node->tagName == 'arguments') { foreach ($node->childNodes as $argument) { if ($argument instanceof DOMElement) { if ($argument->tagName == 'file' || $argument->tagName == 'directory') { $arguments[] = $this->toAbsolutePath((string) $argument->textContent); } else { $arguments[] = Xml::xmlToVariable($argument); } } } } } $result[] = [ 'class' => $class, 'file' => $file, 'arguments' => $arguments ]; } return $result; } public function getLoggingConfiguration() { $result = []; foreach ($this->xpath->query('logging/log') as $log) { $type = (string) $log->getAttribute('type'); $target = (string) $log->getAttribute('target'); if (!$target) { continue; } $target = $this->toAbsolutePath($target); if ($type == 'coverage-html') { if ($log->hasAttribute('lowUpperBound')) { $result['lowUpperBound'] = $this->getInteger( (string) $log->getAttribute('lowUpperBound'), 50 ); } if ($log->hasAttribute('highLowerBound')) { $result['highLowerBound'] = $this->getInteger( (string) $log->getAttribute('highLowerBound'), 90 ); } } elseif ($type == 'coverage-crap4j') { if ($log->hasAttribute('threshold')) { $result['crap4jThreshold'] = $this->getInteger( (string) $log->getAttribute('threshold'), 30 ); } } elseif ($type == 'coverage-text') { if ($log->hasAttribute('showUncoveredFiles')) { $result['coverageTextShowUncoveredFiles'] = $this->getBoolean( (string) $log->getAttribute('showUncoveredFiles'), false ); } if ($log->hasAttribute('showOnlySummary')) { $result['coverageTextShowOnlySummary'] = $this->getBoolean( (string) $log->getAttribute('showOnlySummary'), false ); } } $result[$type] = $target; } return $result; } public function getPHPConfiguration() { $result = [ 'include_path' => [], 'ini' => [], 'const' => [], 'var' => [], 'env' => [], 'post' => [], 'get' => [], 'cookie' => [], 'server' => [], 'files' => [], 'request' => [] ]; foreach ($this->xpath->query('php/includePath') as $includePath) { $path = (string) $includePath->textContent; if ($path) { $result['include_path'][] = $this->toAbsolutePath($path); } } foreach ($this->xpath->query('php/ini') as $ini) { $name = (string) $ini->getAttribute('name'); $value = (string) $ini->getAttribute('value'); $result['ini'][$name] = $value; } foreach ($this->xpath->query('php/const') as $const) { $name = (string) $const->getAttribute('name'); $value = (string) $const->getAttribute('value'); $result['const'][$name] = $this->getBoolean($value, $value); } foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { foreach ($this->xpath->query('php/' . $array) as $var) { $name = (string) $var->getAttribute('name'); $value = (string) $var->getAttribute('value'); $verbatim = false; if ($var->hasAttribute('verbatim')) { $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false); } if (!$verbatim) { $value = $this->getBoolean($value, $value); } $result[$array][$name] = $value; } } return $result; } public function handlePHPConfiguration() { $configuration = $this->getPHPConfiguration(); if (!empty($configuration['include_path'])) { \ini_set( 'include_path', \implode(PATH_SEPARATOR, $configuration['include_path']) . PATH_SEPARATOR . \ini_get('include_path') ); } foreach ($configuration['ini'] as $name => $value) { if (\defined($value)) { $value = \constant($value); } \ini_set($name, $value); } foreach ($configuration['const'] as $name => $value) { if (!\defined($name)) { \define($name, $value); } } foreach (['var', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { switch ($array) { case 'var': $target = &$GLOBALS; break; case 'server': $target = &$_SERVER; break; default: $target = &$GLOBALS['_' . \strtoupper($array)]; break; } foreach ($configuration[$array] as $name => $value) { $target[$name] = $value; } } foreach ($configuration['env'] as $name => $value) { if (false === \getenv($name)) { \putenv("{$name}={$value}"); } if (!isset($_ENV[$name])) { $_ENV[$name] = $value; } } } public function getPHPUnitConfiguration() { $result = []; $root = $this->document->documentElement; if ($root->hasAttribute('cacheTokens')) { $result['cacheTokens'] = $this->getBoolean( (string) $root->getAttribute('cacheTokens'), false ); } if ($root->hasAttribute('columns')) { $columns = (string) $root->getAttribute('columns'); if ($columns == 'max') { $result['columns'] = 'max'; } else { $result['columns'] = $this->getInteger($columns, 80); } } if ($root->hasAttribute('colors')) { if ($this->getBoolean($root->getAttribute('colors'), false)) { $result['colors'] = ResultPrinter::COLOR_AUTO; } else { $result['colors'] = ResultPrinter::COLOR_NEVER; } } if ($root->hasAttribute('stderr')) { $result['stderr'] = $this->getBoolean( (string) $root->getAttribute('stderr'), false ); } if ($root->hasAttribute('backupGlobals')) { $result['backupGlobals'] = $this->getBoolean( (string) $root->getAttribute('backupGlobals'), false ); } if ($root->hasAttribute('backupStaticAttributes')) { $result['backupStaticAttributes'] = $this->getBoolean( (string) $root->getAttribute('backupStaticAttributes'), false ); } if ($root->getAttribute('bootstrap')) { $result['bootstrap'] = $this->toAbsolutePath( (string) $root->getAttribute('bootstrap') ); } if ($root->hasAttribute('convertErrorsToExceptions')) { $result['convertErrorsToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertErrorsToExceptions'), true ); } if ($root->hasAttribute('convertNoticesToExceptions')) { $result['convertNoticesToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertNoticesToExceptions'), true ); } if ($root->hasAttribute('convertWarningsToExceptions')) { $result['convertWarningsToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertWarningsToExceptions'), true ); } if ($root->hasAttribute('forceCoversAnnotation')) { $result['forceCoversAnnotation'] = $this->getBoolean( (string) $root->getAttribute('forceCoversAnnotation'), false ); } if ($root->hasAttribute('disableCodeCoverageIgnore')) { $result['disableCodeCoverageIgnore'] = $this->getBoolean( (string) $root->getAttribute('disableCodeCoverageIgnore'), false ); } if ($root->hasAttribute('processIsolation')) { $result['processIsolation'] = $this->getBoolean( (string) $root->getAttribute('processIsolation'), false ); } if ($root->hasAttribute('stopOnError')) { $result['stopOnError'] = $this->getBoolean( (string) $root->getAttribute('stopOnError'), false ); } if ($root->hasAttribute('stopOnFailure')) { $result['stopOnFailure'] = $this->getBoolean( (string) $root->getAttribute('stopOnFailure'), false ); } if ($root->hasAttribute('stopOnWarning')) { $result['stopOnWarning'] = $this->getBoolean( (string) $root->getAttribute('stopOnWarning'), false ); } if ($root->hasAttribute('stopOnIncomplete')) { $result['stopOnIncomplete'] = $this->getBoolean( (string) $root->getAttribute('stopOnIncomplete'), false ); } if ($root->hasAttribute('stopOnRisky')) { $result['stopOnRisky'] = $this->getBoolean( (string) $root->getAttribute('stopOnRisky'), false ); } if ($root->hasAttribute('stopOnSkipped')) { $result['stopOnSkipped'] = $this->getBoolean( (string) $root->getAttribute('stopOnSkipped'), false ); } if ($root->hasAttribute('failOnWarning')) { $result['failOnWarning'] = $this->getBoolean( (string) $root->getAttribute('failOnWarning'), false ); } if ($root->hasAttribute('failOnRisky')) { $result['failOnRisky'] = $this->getBoolean( (string) $root->getAttribute('failOnRisky'), false ); } if ($root->hasAttribute('testSuiteLoaderClass')) { $result['testSuiteLoaderClass'] = (string) $root->getAttribute( 'testSuiteLoaderClass' ); } if ($root->hasAttribute('defaultTestSuite')) { $result['defaultTestSuite'] = (string) $root->getAttribute( 'defaultTestSuite' ); } if ($root->getAttribute('testSuiteLoaderFile')) { $result['testSuiteLoaderFile'] = $this->toAbsolutePath( (string) $root->getAttribute('testSuiteLoaderFile') ); } if ($root->hasAttribute('printerClass')) { $result['printerClass'] = (string) $root->getAttribute( 'printerClass' ); } if ($root->getAttribute('printerFile')) { $result['printerFile'] = $this->toAbsolutePath( (string) $root->getAttribute('printerFile') ); } if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) { $result['beStrictAboutChangesToGlobalState'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutChangesToGlobalState'), false ); } if ($root->hasAttribute('beStrictAboutOutputDuringTests')) { $result['disallowTestOutput'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutOutputDuringTests'), false ); } if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) { $result['beStrictAboutResourceUsageDuringSmallTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutResourceUsageDuringSmallTests'), false ); } if ($root->hasAttribute('beStrictAboutTestsThatDoNotTestAnything')) { $result['reportUselessTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutTestsThatDoNotTestAnything'), true ); } if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) { $result['disallowTodoAnnotatedTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutTodoAnnotatedTests'), false ); } if ($root->hasAttribute('beStrictAboutCoversAnnotation')) { $result['strictCoverage'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutCoversAnnotation'), false ); } if ($root->hasAttribute('enforceTimeLimit')) { $result['enforceTimeLimit'] = $this->getBoolean( (string) $root->getAttribute('enforceTimeLimit'), false ); } if ($root->hasAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage')) { $result['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $this->getBoolean( (string) $root->getAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage'), false ); } if ($root->hasAttribute('timeoutForSmallTests')) { $result['timeoutForSmallTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForSmallTests'), 1 ); } if ($root->hasAttribute('timeoutForMediumTests')) { $result['timeoutForMediumTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForMediumTests'), 10 ); } if ($root->hasAttribute('timeoutForLargeTests')) { $result['timeoutForLargeTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForLargeTests'), 60 ); } if ($root->hasAttribute('reverseDefectList')) { $result['reverseDefectList'] = $this->getBoolean( (string) $root->getAttribute('reverseDefectList'), false ); } if ($root->hasAttribute('verbose')) { $result['verbose'] = $this->getBoolean( (string) $root->getAttribute('verbose'), false ); } if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) { $result['registerMockObjectsFromTestArgumentsRecursively'] = $this->getBoolean( (string) $root->getAttribute('registerMockObjectsFromTestArgumentsRecursively'), false ); } if ($root->hasAttribute('extensionsDirectory')) { $result['extensionsDirectory'] = $this->toAbsolutePath( (string) $root->getAttribute( 'extensionsDirectory' ) ); } return $result; } public function getTestSuiteConfiguration($testSuiteFilter = null) { $testSuiteNodes = $this->xpath->query('testsuites/testsuite'); if ($testSuiteNodes->length == 0) { $testSuiteNodes = $this->xpath->query('testsuite'); } if ($testSuiteNodes->length == 1) { return $this->getTestSuite($testSuiteNodes->item(0), $testSuiteFilter); } if ($testSuiteNodes->length > 1) { $suite = new TestSuite; foreach ($testSuiteNodes as $testSuiteNode) { $suite->addTestSuite( $this->getTestSuite($testSuiteNode, $testSuiteFilter) ); } return $suite; } } public function getTestSuiteNames() { $names = []; $nodes = $this->xpath->query('*/testsuite'); foreach ($nodes as $node) { $names[] = $node->getAttribute('name'); } return $names; } protected function getTestSuite(DOMElement $testSuiteNode, $testSuiteFilter = null) { if ($testSuiteNode->hasAttribute('name')) { $suite = new TestSuite( (string) $testSuiteNode->getAttribute('name') ); } else { $suite = new TestSuite; } $exclude = []; foreach ($testSuiteNode->getElementsByTagName('exclude') as $excludeNode) { $excludeFile = (string) $excludeNode->textContent; if ($excludeFile) { $exclude[] = $this->toAbsolutePath($excludeFile); } } $fileIteratorFacade = new File_Iterator_Facade; $testSuiteFilter = $testSuiteFilter ? \explode(self::TEST_SUITE_FILTER_SEPARATOR, $testSuiteFilter) : []; foreach ($testSuiteNode->getElementsByTagName('directory') as $directoryNode) { if (!empty($testSuiteFilter) && !\in_array($directoryNode->parentNode->getAttribute('name'), $testSuiteFilter)) { continue; } $directory = (string) $directoryNode->textContent; if (empty($directory)) { continue; } if ($directoryNode->hasAttribute('phpVersion')) { $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); } else { $phpVersion = PHP_VERSION; } if ($directoryNode->hasAttribute('phpVersionOperator')) { $phpVersionOperator = (string) $directoryNode->getAttribute('phpVersionOperator'); } else { $phpVersionOperator = '>='; } if (!\version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) { continue; } if ($directoryNode->hasAttribute('prefix')) { $prefix = (string) $directoryNode->getAttribute('prefix'); } else { $prefix = ''; } if ($directoryNode->hasAttribute('suffix')) { $suffix = (string) $directoryNode->getAttribute('suffix'); } else { $suffix = 'Test.php'; } $files = $fileIteratorFacade->getFilesAsArray( $this->toAbsolutePath($directory), $suffix, $prefix, $exclude ); $suite->addTestFiles($files); } foreach ($testSuiteNode->getElementsByTagName('file') as $fileNode) { if (!empty($testSuiteFilter) && !\in_array($fileNode->parentNode->getAttribute('name'), $testSuiteFilter)) { continue; } $file = (string) $fileNode->textContent; if (empty($file)) { continue; } $file = $fileIteratorFacade->getFilesAsArray( $this->toAbsolutePath($file) ); if (!isset($file[0])) { continue; } $file = $file[0]; if ($fileNode->hasAttribute('phpVersion')) { $phpVersion = (string) $fileNode->getAttribute('phpVersion'); } else { $phpVersion = PHP_VERSION; } if ($fileNode->hasAttribute('phpVersionOperator')) { $phpVersionOperator = (string) $fileNode->getAttribute('phpVersionOperator'); } else { $phpVersionOperator = '>='; } if (!\version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) { continue; } $suite->addTestFile($file); } return $suite; } protected function getBoolean($value, $default) { if (\strtolower($value) == 'false') { return false; } if (\strtolower($value) == 'true') { return true; } return $default; } protected function getInteger($value, $default) { if (\is_numeric($value)) { return (int) $value; } return $default; } protected function readFilterDirectories($query) { $directories = []; foreach ($this->xpath->query($query) as $directory) { $directoryPath = (string) $directory->textContent; if (!$directoryPath) { continue; } if ($directory->hasAttribute('prefix')) { $prefix = (string) $directory->getAttribute('prefix'); } else { $prefix = ''; } if ($directory->hasAttribute('suffix')) { $suffix = (string) $directory->getAttribute('suffix'); } else { $suffix = '.php'; } if ($directory->hasAttribute('group')) { $group = (string) $directory->getAttribute('group'); } else { $group = 'DEFAULT'; } $directories[] = [ 'path' => $this->toAbsolutePath($directoryPath), 'prefix' => $prefix, 'suffix' => $suffix, 'group' => $group ]; } return $directories; } protected function readFilterFiles($query) { $files = []; foreach ($this->xpath->query($query) as $file) { $filePath = (string) $file->textContent; if ($filePath) { $files[] = $this->toAbsolutePath($filePath); } } return $files; } protected function toAbsolutePath($path, $useIncludePath = false) { $path = \trim($path); if ($path[0] === '/') { return $path; } if (\defined('PHP_WINDOWS_VERSION_BUILD') && ($path[0] === '\\' || (\strlen($path) >= 3 && \preg_match('#^[A-Z]\:[/\\\]#i', \substr($path, 0, 3))))) { return $path; } if (\strpos($path, '://') !== false) { return $path; } $file = \dirname($this->filename) . DIRECTORY_SEPARATOR . $path; if ($useIncludePath && !\file_exists($file)) { $includePathFile = \stream_resolve_include_path($path); if ($includePathFile) { $file = $includePathFile; } } return $file; } } 1, 'PHP_Invoker' => 1, 'PHP_Timer' => 1, 'PHP_Token' => 1, 'PHPUnit\Framework\TestCase' => 2, 'PHPUnit\DbUnit\TestCase' => 2, 'PHPUnit_Framework_MockObject_Generator' => 1, 'Text_Template' => 1, 'Symfony\Component\Yaml\Yaml' => 1, 'SebastianBergmann\CodeCoverage\CodeCoverage' => 1, 'SebastianBergmann\Diff\Diff' => 1, 'SebastianBergmann\Environment\Runtime' => 1, 'SebastianBergmann\Comparator\Comparator' => 1, 'SebastianBergmann\Exporter\Exporter' => 1, 'SebastianBergmann\GlobalState\Snapshot' => 1, 'SebastianBergmann\RecursionContext\Context' => 1, 'SebastianBergmann\Version' => 1, 'Composer\Autoload\ClassLoader' => 1, 'Doctrine\Instantiator\Instantiator' => 1, 'phpDocumentor\Reflection\DocBlock' => 1, 'Prophecy\Prophet' => 1, 'DeepCopy\DeepCopy' => 1 ]; private static $directories; public function getBlacklistedDirectories() { $this->initialize(); return self::$directories; } public function isBlacklisted($file) { if (\defined('PHPUNIT_TESTSUITE')) { return false; } $this->initialize(); foreach (self::$directories as $directory) { if (\strpos($file, $directory) === 0) { return true; } } return false; } private function initialize() { if (self::$directories === null) { self::$directories = []; foreach (self::$blacklistedClassNames as $className => $parent) { if (!\class_exists($className)) { continue; } $reflector = new ReflectionClass($className); $directory = $reflector->getFileName(); for ($i = 0; $i < $parent; $i++) { $directory = \dirname($directory); } self::$directories[] = $directory; } if (DIRECTORY_SEPARATOR === '\\') { self::$directories[] = \sys_get_temp_dir() . '\\PHP'; } } } } {tests_directory} {src_directory} EOT; public function generateDefaultConfiguration($phpunitVersion, $bootstrapScript, $testsDirectory, $srcDirectory) { return \str_replace( [ '{phpunit_version}', '{bootstrap_script}', '{tests_directory}', '{src_directory}' ], [ $phpunitVersion, $bootstrapScript, $testsDirectory, $srcDirectory ], $this->defaultTemplate ); } } filename = $filename; $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); } public function count() { return 1; } private function assertPhptExpectation(array $sections, $output) { $assertions = [ 'EXPECT' => 'assertEquals', 'EXPECTF' => 'assertStringMatchesFormat', 'EXPECTREGEX' => 'assertRegExp', ]; $actual = \preg_replace('/\r\n/', "\n", \trim($output)); foreach ($assertions as $sectionName => $sectionAssertion) { if (isset($sections[$sectionName])) { $sectionContent = \preg_replace('/\r\n/', "\n", \trim($sections[$sectionName])); $assertion = $sectionAssertion; $expected = $sectionName == 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; break; } } Assert::$assertion($expected, $actual); } public function run(TestResult $result = null) { $sections = $this->parse(); $code = $this->render($sections['FILE']); if ($result === null) { $result = new TestResult; } $skip = false; $xfail = false; $time = 0; $settings = $this->settings; $result->startTest($this); if (isset($sections['INI'])) { $settings = \array_merge($settings, $this->parseIniSection($sections['INI'])); } if (isset($sections['ENV'])) { $env = $this->parseEnvSection($sections['ENV']); $this->phpUtil->setEnv($env); } $this->phpUtil->setUseStderrRedirection(true); if ($result->enforcesTimeLimit()) { $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); } if (isset($sections['SKIPIF'])) { $skipif = $this->render($sections['SKIPIF']); $jobResult = $this->phpUtil->runJob($skipif, $settings); if (!\strncasecmp('skip', \ltrim($jobResult['stdout']), 4)) { if (\preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $message)) { $message = \substr($message[1], 2); } else { $message = ''; } $result->addFailure($this, new SkippedTestError($message), 0); $skip = true; } } if (isset($sections['XFAIL'])) { $xfail = \trim($sections['XFAIL']); } if (!$skip) { if (isset($sections['STDIN'])) { $this->phpUtil->setStdin($sections['STDIN']); } if (isset($sections['ARGS'])) { $this->phpUtil->setArgs($sections['ARGS']); } PHP_Timer::start(); $jobResult = $this->phpUtil->runJob($code, $settings); $time = PHP_Timer::stop(); try { $this->assertPhptExpectation($sections, $jobResult['stdout']); } catch (AssertionFailedError $e) { if ($xfail !== false) { $result->addFailure( $this, new IncompleteTestError( $xfail, 0, $e ), $time ); } else { $result->addFailure($this, $e, $time); } } catch (Throwable $t) { $result->addError($this, $t, $time); } if ($result->allCompletelyImplemented() && $xfail !== false) { $result->addFailure( $this, new IncompleteTestError( 'XFAIL section but test passes' ), $time ); } $this->phpUtil->setStdin(''); $this->phpUtil->setArgs(''); if (isset($sections['CLEAN'])) { $cleanCode = $this->render($sections['CLEAN']); $this->phpUtil->runJob($cleanCode, $this->settings); } } $result->endTest($this, $time); return $result; } public function getName() { return $this->toString(); } public function toString() { return $this->filename; } private function parse() { $sections = []; $section = ''; $allowExternalSections = [ 'FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX' ]; $requiredSections = [ 'FILE', [ 'EXPECT', 'EXPECTF', 'EXPECTREGEX' ] ]; $unsupportedSections = [ 'REDIRECTTEST', 'REQUEST', 'POST', 'PUT', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'GET', 'COOKIE', 'HEADERS', 'CGI', 'EXPECTHEADERS', 'EXTENSIONS', 'PHPDBG' ]; foreach (\file($this->filename) as $line) { if (\preg_match('/^--([_A-Z]+)--/', $line, $result)) { $section = $result[1]; $sections[$section] = ''; continue; } elseif (empty($section)) { throw new Exception('Invalid PHPT file'); } $sections[$section] .= $line; } if (isset($sections['FILEEOF'])) { $sections['FILE'] = \rtrim($sections['FILEEOF'], "\r\n"); unset($sections['FILEEOF']); } $testDirectory = \dirname($this->filename) . DIRECTORY_SEPARATOR; foreach ($allowExternalSections as $section) { if (isset($sections[$section . '_EXTERNAL'])) { $externalFilename = \trim($sections[$section . '_EXTERNAL']); if (!\is_file($testDirectory . $externalFilename) || !\is_readable($testDirectory . $externalFilename)) { throw new Exception( \sprintf( 'Could not load --%s-- %s for PHPT file', $section . '_EXTERNAL', $testDirectory . $externalFilename ) ); } $sections[$section] = \file_get_contents($testDirectory . $externalFilename); unset($sections[$section . '_EXTERNAL']); } } $isValid = true; foreach ($requiredSections as $section) { if (\is_array($section)) { $foundSection = false; foreach ($section as $anySection) { if (isset($sections[$anySection])) { $foundSection = true; break; } } if (!$foundSection) { $isValid = false; break; } } else { if (!isset($sections[$section])) { $isValid = false; break; } } } if (!$isValid) { throw new Exception('Invalid PHPT file'); } foreach ($unsupportedSections as $section) { if (isset($sections[$section])) { throw new Exception( 'PHPUnit does not support this PHPT file' ); } } return $sections; } private function render($code) { return \str_replace( [ '__DIR__', '__FILE__' ], [ "'" . \dirname($this->filename) . "'", "'" . $this->filename . "'" ], $code ); } protected function parseIniSection($content) { return \preg_split('/\n|\r/', $content, -1, PREG_SPLIT_NO_EMPTY); } protected function parseEnvSection($content) { $env = []; foreach (\explode("\n", \trim($content)) as $e) { $e = \explode('=', \trim($e), 2); if (!empty($e[0]) && isset($e[1])) { $env[$e[0]] = $e[1]; } } return $env; } } getFileName() == $filename) { $suiteClassName = $loadedClass; break; } } } if (!\class_exists($suiteClassName, false) && !empty($loadedClasses)) { $testCaseClass = TestCase::class; foreach ($loadedClasses as $loadedClass) { $class = new ReflectionClass($loadedClass); $classFile = $class->getFileName(); if ($class->isSubclassOf($testCaseClass) && !$class->isAbstract()) { $suiteClassName = $loadedClass; $testCaseClass = $loadedClass; if ($classFile == \realpath($suiteClassFile)) { break; } } if ($class->hasMethod('suite')) { $method = $class->getMethod('suite'); if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { $suiteClassName = $loadedClass; if ($classFile == \realpath($suiteClassFile)) { break; } } } } } if (\class_exists($suiteClassName, false)) { $class = new ReflectionClass($suiteClassName); if ($class->getFileName() == \realpath($suiteClassFile)) { return $class; } } throw new Exception( \sprintf( "Class '%s' could not be found in '%s'.", $suiteClassName, $suiteClassFile ) ); } public function reload(ReflectionClass $aClass) { return $aClass; } } groupTests); } } isSubclassOf(\RecursiveFilterIterator::class)) { throw new InvalidArgumentException( \sprintf( 'Class "%s" does not extend RecursiveFilterIterator', $filter->name ) ); } $this->filters[] = [$filter, $args]; } public function factory(Iterator $iterator, TestSuite $suite) { foreach ($this->filters as $filter) { list($class, $args) = $filter; $iterator = $class->newInstance($iterator, $args, $suite); } return $iterator; } } groupTests); } } setFilter($filter); } protected function setFilter($filter) { if (RegularExpression::safeMatch($filter, '') === false) { if (\preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) { if (isset($matches[3]) && $matches[2] < $matches[3]) { $filter = \sprintf( '%s.*with data set #(\d+)$', $matches[1] ); $this->filterMin = $matches[2]; $this->filterMax = $matches[3]; } else { $filter = \sprintf( '%s.*with data set #%s$', $matches[1], $matches[2] ); } } elseif (\preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { $filter = \sprintf( '%s.*with data set "%s"$', $matches[1], $matches[2] ); } $filter = \sprintf('/%s/', \str_replace( '/', '\\/', $filter )); } $this->filter = $filter; } public function accept() { $test = $this->getInnerIterator()->current(); if ($test instanceof TestSuite) { return true; } $tmp = \PHPUnit\Util\Test::describe($test, false); if ($test instanceof WarningTestCase) { $name = $test->getMessage(); } else { if ($tmp[0] != '') { $name = \implode('::', $tmp); } else { $name = $tmp[1]; } } $accepted = @\preg_match($this->filter, $name, $matches); if ($accepted && isset($this->filterMax)) { $set = \end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return $accepted; } } getGroupDetails() as $group => $tests) { if (\in_array($group, $groups)) { $testHashes = \array_map( function ($test) { return \spl_object_hash($test); }, $tests ); $this->groupTests = \array_merge($this->groupTests, $testHashes); } } } public function accept() { $test = $this->getInnerIterator()->current(); if ($test instanceof TestSuite) { return true; } return $this->doAccept(\spl_object_hash($test)); } abstract protected function doAccept($hash); } getFilesAsArray( $suiteClassName, $suffixes ); $suite = new TestSuite($suiteClassName); $suite->addTestFiles($files); return $suite; } try { $testClass = $this->loadSuiteClass( $suiteClassName, $suiteClassFile ); } catch (Exception $e) { $this->runFailed($e->getMessage()); return; } try { $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); if (!$suiteMethod->isStatic()) { $this->runFailed( 'suite() method must be static.' ); return; } try { $test = $suiteMethod->invoke(null, $testClass->getName()); } catch (ReflectionException $e) { $this->runFailed( \sprintf( "Failed to invoke suite() method.\n%s", $e->getMessage() ) ); return; } } catch (ReflectionException $e) { try { $test = new TestSuite($testClass); } catch (Exception $e) { $test = new TestSuite; $test->setName($suiteClassName); } } $this->clearStatus(); return $test; } protected function loadSuiteClass($suiteClassName, $suiteClassFile = '') { $loader = $this->getLoader(); return $loader->load($suiteClassName, $suiteClassFile); } protected function clearStatus() { } abstract protected function runFailed($message); } getVersion(); } return self::$version; } public static function series() { if (\strpos(self::id(), '-')) { $version = \explode('-', self::id())[0]; } else { $version = self::id(); } return \implode('.', \array_slice(\explode('.', $version), 0, 2)); } public static function getVersionString() { return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.'; } public static function getReleaseChannel() { if (\strpos(self::$pharVersion, '-') !== false) { return '-nightly'; } return ''; } } setName($theClass); return; } if (!$argumentsValid) { throw new Exception; } if (!$theClass->isSubclassOf(TestCase::class)) { throw new Exception( 'Class "' . $theClass->name . '" does not extend PHPUnit\Framework\TestCase.' ); } if ($name != '') { $this->setName($name); } else { $this->setName($theClass->getName()); } $constructor = $theClass->getConstructor(); if ($constructor !== null && !$constructor->isPublic()) { $this->addTest( self::warning( \sprintf( 'Class "%s" has no public constructor.', $theClass->getName() ) ) ); return; } foreach ($theClass->getMethods() as $method) { $this->addTestMethod($theClass, $method); } if (empty($this->tests)) { $this->addTest( self::warning( \sprintf( 'No tests found in class "%s".', $theClass->getName() ) ) ); } $this->testCase = true; } public function toString() { return $this->getName(); } public function addTest(Test $test, $groups = []) { $class = new ReflectionClass($test); if (!$class->isAbstract()) { $this->tests[] = $test; $this->numTests = -1; if ($test instanceof self && empty($groups)) { $groups = $test->getGroups(); } if (empty($groups)) { $groups = ['default']; } foreach ($groups as $group) { if (!isset($this->groups[$group])) { $this->groups[$group] = [$test]; } else { $this->groups[$group][] = $test; } } if ($test instanceof TestCase) { $test->setGroups($groups); } } } public function addTestSuite($testClass) { if (\is_string($testClass) && \class_exists($testClass)) { $testClass = new ReflectionClass($testClass); } if (!\is_object($testClass)) { throw InvalidArgumentHelper::factory( 1, 'class name or object' ); } if ($testClass instanceof self) { $this->addTest($testClass); } elseif ($testClass instanceof ReflectionClass) { $suiteMethod = false; if (!$testClass->isAbstract()) { if ($testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { $method = $testClass->getMethod( BaseTestRunner::SUITE_METHODNAME ); if ($method->isStatic()) { $this->addTest( $method->invoke(null, $testClass->getName()) ); $suiteMethod = true; } } } if (!$suiteMethod && !$testClass->isAbstract()) { $this->addTest(new self($testClass)); } } else { throw new Exception; } } public function addTestFile($filename) { if (!\is_string($filename)) { throw InvalidArgumentHelper::factory(1, 'string'); } if (\file_exists($filename) && \substr($filename, -5) == '.phpt') { $this->addTest( new PhptTestCase($filename) ); return; } $classes = \get_declared_classes(); $filename = Fileloader::checkAndLoad($filename); $newClasses = \array_diff(\get_declared_classes(), $classes); if (!empty($newClasses)) { $this->foundClasses = \array_merge($newClasses, $this->foundClasses); } $shortname = \basename($filename, '.php'); $shortnameRegEx = '/(?:^|_|\\\\)' . \preg_quote($shortname, '/') . '$/'; foreach ($this->foundClasses as $i => $className) { if (\preg_match($shortnameRegEx, $className)) { $class = new ReflectionClass($className); if ($class->getFileName() == $filename) { $newClasses = [$className]; unset($this->foundClasses[$i]); break; } } } foreach ($newClasses as $className) { $class = new ReflectionClass($className); if (!$class->isAbstract()) { if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { $method = $class->getMethod( BaseTestRunner::SUITE_METHODNAME ); if ($method->isStatic()) { $this->addTest($method->invoke(null, $className)); } } elseif ($class->implementsInterface(Test::class)) { $this->addTestSuite($class); } } } $this->numTests = -1; } public function addTestFiles($filenames) { if (!(\is_array($filenames) || (\is_object($filenames) && $filenames instanceof Iterator))) { throw InvalidArgumentHelper::factory( 1, 'array or iterator' ); } foreach ($filenames as $filename) { $this->addTestFile((string) $filename); } } public function count($preferCache = false) { if ($preferCache && $this->cachedNumTests !== null) { return $this->cachedNumTests; } $numTests = 0; foreach ($this as $test) { $numTests += \count($test); } $this->cachedNumTests = $numTests; return $numTests; } public static function createTest(ReflectionClass $theClass, $name) { $className = $theClass->getName(); if (!$theClass->isInstantiable()) { return self::warning( \sprintf('Cannot instantiate class "%s".', $className) ); } $backupSettings = \PHPUnit\Util\Test::getBackupSettings( $className, $name ); $preserveGlobalState = \PHPUnit\Util\Test::getPreserveGlobalStateSettings( $className, $name ); $runTestInSeparateProcess = \PHPUnit\Util\Test::getProcessIsolationSettings( $className, $name ); $constructor = $theClass->getConstructor(); if ($constructor !== null) { $parameters = $constructor->getParameters(); if (\count($parameters) < 2) { $test = new $className; } else { try { $data = \PHPUnit\Util\Test::getProvidedData( $className, $name ); } catch (IncompleteTestError $e) { $message = \sprintf( 'Test for %s::%s marked incomplete by data provider', $className, $name ); $_message = $e->getMessage(); if (!empty($_message)) { $message .= "\n" . $_message; } $data = self::incompleteTest($className, $name, $message); } catch (SkippedTestError $e) { $message = \sprintf( 'Test for %s::%s skipped by data provider', $className, $name ); $_message = $e->getMessage(); if (!empty($_message)) { $message .= "\n" . $_message; } $data = self::skipTest($className, $name, $message); } catch (Throwable $_t) { $t = $_t; } catch (Exception $_t) { $t = $_t; } if (isset($t)) { $message = \sprintf( 'The data provider specified for %s::%s is invalid.', $className, $name ); $_message = $t->getMessage(); if (!empty($_message)) { $message .= "\n" . $_message; } $data = self::warning($message); } if (isset($data)) { $test = new DataProviderTestSuite( $className . '::' . $name ); if (empty($data)) { $data = self::warning( \sprintf( 'No tests found in suite "%s".', $test->getName() ) ); } $groups = \PHPUnit\Util\Test::getGroups($className, $name); if ($data instanceof WarningTestCase || $data instanceof SkippedTestCase || $data instanceof IncompleteTestCase) { $test->addTest($data, $groups); } else { foreach ($data as $_dataName => $_data) { $_test = new $className($name, $_data, $_dataName); if ($runTestInSeparateProcess) { $_test->setRunTestInSeparateProcess(true); if ($preserveGlobalState !== null) { $_test->setPreserveGlobalState($preserveGlobalState); } } if ($backupSettings['backupGlobals'] !== null) { $_test->setBackupGlobals( $backupSettings['backupGlobals'] ); } if ($backupSettings['backupStaticAttributes'] !== null) { $_test->setBackupStaticAttributes( $backupSettings['backupStaticAttributes'] ); } $test->addTest($_test, $groups); } } } else { $test = new $className; } } } if (!isset($test)) { throw new Exception('No valid test provided.'); } if ($test instanceof TestCase) { $test->setName($name); if ($runTestInSeparateProcess) { $test->setRunTestInSeparateProcess(true); if ($preserveGlobalState !== null) { $test->setPreserveGlobalState($preserveGlobalState); } } if ($backupSettings['backupGlobals'] !== null) { $test->setBackupGlobals($backupSettings['backupGlobals']); } if ($backupSettings['backupStaticAttributes'] !== null) { $test->setBackupStaticAttributes( $backupSettings['backupStaticAttributes'] ); } } return $test; } protected function createResult() { return new TestResult; } public function getName() { return $this->name; } public function getGroups() { return \array_keys($this->groups); } public function getGroupDetails() { return $this->groups; } public function setGroupDetails(array $groups) { $this->groups = $groups; } public function run(TestResult $result = null) { if ($result === null) { $result = $this->createResult(); } if (\count($this) == 0) { return $result; } $hookMethods = \PHPUnit\Util\Test::getHookMethods($this->name); $result->startTestSuite($this); try { $this->setUp(); foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { if ($this->testCase === true && \class_exists($this->name, false) && \method_exists($this->name, $beforeClassMethod)) { if ($missingRequirements = \PHPUnit\Util\Test::getMissingRequirements($this->name, $beforeClassMethod)) { $this->markTestSuiteSkipped(\implode(PHP_EOL, $missingRequirements)); } \call_user_func([$this->name, $beforeClassMethod]); } } } catch (SkippedTestSuiteError $e) { $numTests = \count($this); for ($i = 0; $i < $numTests; $i++) { $result->startTest($this); $result->addFailure($this, $e, 0); $result->endTest($this, 0); } $this->tearDown(); $result->endTestSuite($this); return $result; } catch (Throwable $_t) { $t = $_t; } catch (Exception $_t) { $t = $_t; } if (isset($t)) { $numTests = \count($this); for ($i = 0; $i < $numTests; $i++) { if ($result->shouldStop()) { break; } $result->startTest($this); $result->addError($this, $t, 0); $result->endTest($this, 0); } $this->tearDown(); $result->endTestSuite($this); return $result; } foreach ($this as $test) { if ($result->shouldStop()) { break; } if ($test instanceof TestCase || $test instanceof self) { $test->setbeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState); $test->setBackupGlobals($this->backupGlobals); $test->setBackupStaticAttributes($this->backupStaticAttributes); $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); } $test->run($result); } foreach ($hookMethods['afterClass'] as $afterClassMethod) { if ($this->testCase === true && \class_exists($this->name, false) && \method_exists($this->name, $afterClassMethod)) { \call_user_func([$this->name, $afterClassMethod]); } } $this->tearDown(); $result->endTestSuite($this); return $result; } public function setRunTestInSeparateProcess($runTestInSeparateProcess) { if (\is_bool($runTestInSeparateProcess)) { $this->runTestInSeparateProcess = $runTestInSeparateProcess; } else { throw InvalidArgumentHelper::factory(1, 'boolean'); } } public function runTest(Test $test, TestResult $result) { $test->run($result); } public function setName($name) { $this->name = $name; } public function testAt($index) { if (isset($this->tests[$index])) { return $this->tests[$index]; } return false; } public function tests() { return $this->tests; } public function setTests(array $tests) { $this->tests = $tests; } public function markTestSuiteSkipped($message = '') { throw new SkippedTestSuiteError($message); } protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method) { if (!$this->isTestMethod($method)) { return; } $name = $method->getName(); if (!$method->isPublic()) { $this->addTest( self::warning( \sprintf( 'Test method "%s" in test class "%s" is not public.', $name, $class->getName() ) ) ); return; } $test = self::createTest($class, $name); if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) { $test->setDependencies( \PHPUnit\Util\Test::getDependencies($class->getName(), $name) ); } $this->addTest( $test, \PHPUnit\Util\Test::getGroups($class->getName(), $name) ); } public static function isTestMethod(ReflectionMethod $method) { if (\strpos($method->name, 'test') === 0) { return true; } $docComment = $method->getDocComment(); return \strpos($docComment, '@test') !== false || \strpos($docComment, '@scenario') !== false; } protected static function warning($message) { return new WarningTestCase($message); } protected static function skipTest($class, $methodName, $message) { return new SkippedTestCase($class, $methodName, $message); } protected static function incompleteTest($class, $methodName, $message) { return new IncompleteTestCase($class, $methodName, $message); } public function setbeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState) { if (\is_null($this->beStrictAboutChangesToGlobalState) && \is_bool($beStrictAboutChangesToGlobalState)) { $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; } } public function setBackupGlobals($backupGlobals) { if (\is_null($this->backupGlobals) && \is_bool($backupGlobals)) { $this->backupGlobals = $backupGlobals; } } public function setBackupStaticAttributes($backupStaticAttributes) { if (\is_null($this->backupStaticAttributes) && \is_bool($backupStaticAttributes)) { $this->backupStaticAttributes = $backupStaticAttributes; } } public function getIterator() { $iterator = new TestSuiteIterator($this); if ($this->iteratorFilter !== null) { $iterator = $this->iteratorFilter->factory($iterator, $this); } return $iterator; } public function injectFilter(Factory $filter) { $this->iteratorFilter = $filter; foreach ($this as $test) { if ($test instanceof self) { $test->injectFilter($filter); } } } protected function setUp() { } protected function tearDown() { } } tests as $test) { $test->setDependencies($dependencies); } } } syntheticFile = $file; $this->syntheticLine = $line; $this->syntheticTrace = $trace; } public function getSyntheticFile() { return $this->syntheticFile; } public function getSyntheticLine() { return $this->syntheticLine; } public function getSyntheticTrace() { return $this->syntheticTrace; } } getMessage(), (int) $t->getCode()); $this->className = \get_class($t); $this->file = $t->getFile(); $this->line = $t->getLine(); $this->serializableTrace = $t->getTrace(); foreach ($this->serializableTrace as $i => $call) { unset($this->serializableTrace[$i]['args']); } if ($t->getPrevious()) { $this->previous = new self($t->getPrevious()); } } public function getClassName() { return $this->className; } public function getPreviousWrapped() { return $this->previous; } public function __toString() { $string = TestFailure::exceptionToString($this); if ($trace = Filter::getFilteredStacktrace($this)) { $string .= "\n" . $trace; } if ($this->previous) { $string .= "\nCaused by\n" . $this->previous; } return $string; } } importNode($expectedElement, true); $tmp = new DOMDocument; $actualElement = $tmp->importNode($actualElement, true); unset($tmp); static::assertEquals( $expectedElement->tagName, $actualElement->tagName, $message ); if ($checkAttributes) { static::assertEquals( $expectedElement->attributes->length, $actualElement->attributes->length, \sprintf( '%s%sNumber of attributes on node "%s" does not match', $message, !empty($message) ? "\n" : '', $expectedElement->tagName ) ); for ($i = 0; $i < $expectedElement->attributes->length; $i++) { $expectedAttribute = $expectedElement->attributes->item($i); $actualAttribute = $actualElement->attributes->getNamedItem( $expectedAttribute->name ); if (!$actualAttribute) { static::fail( \sprintf( '%s%sCould not find attribute "%s" on node "%s"', $message, !empty($message) ? "\n" : '', $expectedAttribute->name, $expectedElement->tagName ) ); } } } Xml::removeCharacterDataNodes($expectedElement); Xml::removeCharacterDataNodes($actualElement); static::assertEquals( $expectedElement->childNodes->length, $actualElement->childNodes->length, \sprintf( '%s%sNumber of child nodes of "%s" differs', $message, !empty($message) ? "\n" : '', $expectedElement->tagName ) ); for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { static::assertEqualXMLStructure( $expectedElement->childNodes->item($i), $actualElement->childNodes->item($i), $checkAttributes, $message ); } } public static function assertThat($value, Constraint $constraint, $message = '') { self::$count += \count($constraint); $constraint->evaluate($value, $message); } public static function assertJson($actualJson, $message = '') { if (!\is_string($actualJson)) { throw InvalidArgumentHelper::factory(1, 'string'); } static::assertThat($actualJson, static::isJson(), $message); } public static function assertJsonStringEqualsJsonString($expectedJson, $actualJson, $message = '') { static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraint = new JsonMatches( $expectedJson ); static::assertThat($actualJson, $constraint, $message); } public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, $message = '') { static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraint = new JsonMatches( $expectedJson ); static::assertThat($actualJson, new LogicalNot($constraint), $message); } public static function assertJsonStringEqualsJsonFile($expectedFile, $actualJson, $message = '') { static::assertFileExists($expectedFile, $message); $expectedJson = \file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraint = new JsonMatches( $expectedJson ); static::assertThat($actualJson, $constraint, $message); } public static function assertJsonStringNotEqualsJsonFile($expectedFile, $actualJson, $message = '') { static::assertFileExists($expectedFile, $message); $expectedJson = \file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraint = new JsonMatches( $expectedJson ); static::assertThat($actualJson, new LogicalNot($constraint), $message); } public static function assertJsonFileEqualsJsonFile($expectedFile, $actualFile, $message = '') { static::assertFileExists($expectedFile, $message); static::assertFileExists($actualFile, $message); $actualJson = \file_get_contents($actualFile); $expectedJson = \file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraintExpected = new JsonMatches( $expectedJson ); $constraintActual = new JsonMatches($actualJson); static::assertThat($expectedJson, $constraintActual, $message); static::assertThat($actualJson, $constraintExpected, $message); } public static function assertJsonFileNotEqualsJsonFile($expectedFile, $actualFile, $message = '') { static::assertFileExists($expectedFile, $message); static::assertFileExists($actualFile, $message); $actualJson = \file_get_contents($actualFile); $expectedJson = \file_get_contents($expectedFile); static::assertJson($expectedJson, $message); static::assertJson($actualJson, $message); $constraintExpected = new JsonMatches( $expectedJson ); $constraintActual = new JsonMatches($actualJson); static::assertThat($expectedJson, new LogicalNot($constraintActual), $message); static::assertThat($actualJson, new LogicalNot($constraintExpected), $message); } public static function logicalAnd() { $constraints = \func_get_args(); $constraint = new LogicalAnd; $constraint->setConstraints($constraints); return $constraint; } public static function logicalOr() { $constraints = \func_get_args(); $constraint = new LogicalOr; $constraint->setConstraints($constraints); return $constraint; } public static function logicalNot(Constraint $constraint) { return new LogicalNot($constraint); } public static function logicalXor() { $constraints = \func_get_args(); $constraint = new LogicalXor; $constraint->setConstraints($constraints); return $constraint; } public static function anything() { return new IsAnything; } public static function isTrue() { return new IsTrue; } public static function callback($callback) { return new Callback($callback); } public static function isFalse() { return new IsFalse; } public static function isJson() { return new IsJson; } public static function isNull() { return new IsNull; } public static function isFinite() { return new IsFinite; } public static function isInfinite() { return new IsInfinite; } public static function isNan() { return new IsNan; } public static function attribute(Constraint $constraint, $attributeName) { return new Attribute( $constraint, $attributeName ); } public static function contains($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { return new TraversableContains($value, $checkForObjectIdentity, $checkForNonObjectIdentity); } public static function containsOnly($type) { return new TraversableContainsOnly($type); } public static function containsOnlyInstancesOf($classname) { return new TraversableContainsOnly($classname, false); } public static function arrayHasKey($key) { return new ArrayHasKey($key); } public static function equalTo($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return new IsEqual( $value, $delta, $maxDepth, $canonicalize, $ignoreCase ); } public static function attributeEqualTo($attributeName, $value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return static::attribute( static::equalTo( $value, $delta, $maxDepth, $canonicalize, $ignoreCase ), $attributeName ); } public static function isEmpty() { return new IsEmpty; } public static function isWritable() { return new IsWritable; } public static function isReadable() { return new IsReadable; } public static function directoryExists() { return new DirectoryExists; } public static function fileExists() { return new FileExists; } public static function greaterThan($value) { return new GreaterThan($value); } public static function greaterThanOrEqual($value) { return static::logicalOr( new IsEqual($value), new GreaterThan($value) ); } public static function classHasAttribute($attributeName) { return new ClassHasAttribute( $attributeName ); } public static function classHasStaticAttribute($attributeName) { return new ClassHasStaticAttribute( $attributeName ); } public static function objectHasAttribute($attributeName) { return new ObjectHasAttribute( $attributeName ); } public static function identicalTo($value) { return new IsIdentical($value); } public static function isInstanceOf($className) { return new IsInstanceOf($className); } public static function isType($type) { return new IsType($type); } public static function lessThan($value) { return new LessThan($value); } public static function lessThanOrEqual($value) { return static::logicalOr( new IsEqual($value), new LessThan($value) ); } public static function matchesRegularExpression($pattern) { return new RegularExpression($pattern); } public static function matches($string) { return new StringMatchesFormatDescription($string); } public static function stringStartsWith($prefix) { return new StringStartsWith($prefix); } public static function stringContains($string, $case = true) { return new StringContains($string, $case); } public static function stringEndsWith($suffix) { return new StringEndsWith($suffix); } public static function countOf($count) { return new Count($count); } public static function fail($message = '') { self::$count++; throw new AssertionFailedError($message); } public static function readAttribute($classOrObject, $attributeName) { if (!\is_string($attributeName)) { throw InvalidArgumentHelper::factory(2, 'string'); } if (!\preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw InvalidArgumentHelper::factory(2, 'valid attribute name'); } if (\is_string($classOrObject)) { if (!\class_exists($classOrObject)) { throw InvalidArgumentHelper::factory( 1, 'class name' ); } return static::getStaticAttribute( $classOrObject, $attributeName ); } if (\is_object($classOrObject)) { return static::getObjectAttribute( $classOrObject, $attributeName ); } throw InvalidArgumentHelper::factory( 1, 'class name or object' ); } public static function getStaticAttribute($className, $attributeName) { if (!\is_string($className)) { throw InvalidArgumentHelper::factory(1, 'string'); } if (!\class_exists($className)) { throw InvalidArgumentHelper::factory(1, 'class name'); } if (!\is_string($attributeName)) { throw InvalidArgumentHelper::factory(2, 'string'); } if (!\preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw InvalidArgumentHelper::factory(2, 'valid attribute name'); } $class = new ReflectionClass($className); while ($class) { $attributes = $class->getStaticProperties(); if (\array_key_exists($attributeName, $attributes)) { return $attributes[$attributeName]; } $class = $class->getParentClass(); } throw new Exception( \sprintf( 'Attribute "%s" not found in class.', $attributeName ) ); } public static function getObjectAttribute($object, $attributeName) { if (!\is_object($object)) { throw InvalidArgumentHelper::factory(1, 'object'); } if (!\is_string($attributeName)) { throw InvalidArgumentHelper::factory(2, 'string'); } if (!\preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw InvalidArgumentHelper::factory(2, 'valid attribute name'); } try { $attribute = new ReflectionProperty($object, $attributeName); } catch (ReflectionException $e) { $reflector = new ReflectionObject($object); while ($reflector = $reflector->getParentClass()) { try { $attribute = $reflector->getProperty($attributeName); break; } catch (ReflectionException $e) { } } } if (isset($attribute)) { if (!$attribute || $attribute->isPublic()) { return $object->$attributeName; } $attribute->setAccessible(true); $value = $attribute->getValue($object); $attribute->setAccessible(false); return $value; } throw new Exception( \sprintf( 'Attribute "%s" not found in object.', $attributeName ) ); } public static function markTestIncomplete($message = '') { throw new IncompleteTestError($message); } public static function markTestSkipped($message = '') { throw new SkippedTestError($message); } public static function getCount() { return self::$count; } public static function resetCount() { self::$count = 0; } } setName($name); } $this->data = $data; $this->dataName = $dataName; } public function toString() { $class = new ReflectionClass($this); $buffer = \sprintf( '%s::%s', $class->name, $this->getName(false) ); return $buffer . $this->getDataSetAsString(); } public function count() { return 1; } public function getGroups() { return $this->groups; } public function setGroups(array $groups) { $this->groups = $groups; } public function getAnnotations() { return \PHPUnit\Util\Test::parseTestMethodAnnotations( \get_class($this), $this->name ); } public function getName($withDataSet = true) { if ($withDataSet) { return $this->name . $this->getDataSetAsString(false); } return $this->name; } public function getSize() { return \PHPUnit\Util\Test::getSize( \get_class($this), $this->getName(false) ); } public function hasSize() { return $this->getSize() !== \PHPUnit\Util\Test::UNKNOWN; } public function isSmall() { return $this->getSize() === \PHPUnit\Util\Test::SMALL; } public function isMedium() { return $this->getSize() === \PHPUnit\Util\Test::MEDIUM; } public function isLarge() { return $this->getSize() === \PHPUnit\Util\Test::LARGE; } public function getActualOutput() { if (!$this->outputBufferingActive) { return $this->output; } return \ob_get_contents(); } public function hasOutput() { if (\strlen($this->output) === 0) { return false; } if ($this->hasExpectationOnOutput()) { return false; } return true; } public function doesNotPerformAssertions() { return $this->doesNotPerformAssertions; } public function expectOutputRegex($expectedRegex) { if ($this->outputExpectedString !== null) { throw new Exception; } if (\is_string($expectedRegex) || \is_null($expectedRegex)) { $this->outputExpectedRegex = $expectedRegex; } } public function expectOutputString($expectedString) { if ($this->outputExpectedRegex !== null) { throw new Exception; } if (\is_string($expectedString) || \is_null($expectedString)) { $this->outputExpectedString = $expectedString; } } public function hasExpectationOnOutput() { return \is_string($this->outputExpectedString) || \is_string($this->outputExpectedRegex); } public function getExpectedException() { return $this->expectedException; } public function getExpectedExceptionCode() { return $this->expectedExceptionCode; } public function getExpectedExceptionMessage() { return $this->expectedExceptionMessage; } public function expectException($exception) { if (!\is_string($exception)) { throw InvalidArgumentHelper::factory(1, 'string'); } $this->expectedException = $exception; } public function expectExceptionCode($code) { if (!$this->expectedException) { $this->expectedException = \Exception::class; } if (!\is_int($code) && !\is_string($code)) { throw InvalidArgumentHelper::factory(1, 'integer or string'); } $this->expectedExceptionCode = $code; } public function expectExceptionMessage($message) { if (!$this->expectedException) { $this->expectedException = \Exception::class; } if (!\is_string($message)) { throw InvalidArgumentHelper::factory(1, 'string'); } $this->expectedExceptionMessage = $message; } public function expectExceptionMessageRegExp($messageRegExp) { if (!\is_string($messageRegExp)) { throw InvalidArgumentHelper::factory(1, 'string'); } $this->expectedExceptionMessageRegExp = $messageRegExp; } public function setRegisterMockObjectsFromTestArgumentsRecursively($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->registerMockObjectsFromTestArgumentsRecursively = $flag; } protected function setExpectedExceptionFromAnnotation() { try { $expectedException = \PHPUnit\Util\Test::getExpectedException( \get_class($this), $this->name ); if ($expectedException !== false) { $this->expectException($expectedException['class']); if ($expectedException['code'] !== null) { $this->expectExceptionCode($expectedException['code']); } if ($expectedException['message'] !== '') { $this->expectExceptionMessage($expectedException['message']); } elseif ($expectedException['message_regex'] !== '') { $this->expectExceptionMessageRegExp($expectedException['message_regex']); } } } catch (ReflectionException $e) { } } public function setUseErrorHandler($useErrorHandler) { $this->useErrorHandler = $useErrorHandler; } protected function setUseErrorHandlerFromAnnotation() { try { $useErrorHandler = \PHPUnit\Util\Test::getErrorHandlerSettings( \get_class($this), $this->name ); if ($useErrorHandler !== null) { $this->setUseErrorHandler($useErrorHandler); } } catch (ReflectionException $e) { } } protected function checkRequirements() { if (!$this->name || !\method_exists($this, $this->name)) { return; } $missingRequirements = \PHPUnit\Util\Test::getMissingRequirements( \get_class($this), $this->name ); if (!empty($missingRequirements)) { $this->markTestSkipped(\implode(PHP_EOL, $missingRequirements)); } } public function getStatus() { return $this->status; } public function markAsRisky() { $this->status = BaseTestRunner::STATUS_RISKY; } public function getStatusMessage() { return $this->statusMessage; } public function hasFailed() { $status = $this->getStatus(); return $status == BaseTestRunner::STATUS_FAILURE || $status == BaseTestRunner::STATUS_ERROR; } public function run(TestResult $result = null) { if ($result === null) { $result = $this->createResult(); } if (!$this instanceof WarningTestCase) { $this->setTestResultObject($result); $this->setUseErrorHandlerFromAnnotation(); } if ($this->useErrorHandler !== null) { $oldErrorHandlerSetting = $result->getConvertErrorsToExceptions(); $result->convertErrorsToExceptions($this->useErrorHandler); } if (!$this instanceof WarningTestCase && !$this instanceof SkippedTestCase && !$this->handleDependencies()) { return; } if ($this->runTestInSeparateProcess === true && $this->inIsolation !== true && !$this instanceof PhptTestCase) { $class = new ReflectionClass($this); $template = new Text_Template( __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl' ); if ($this->preserveGlobalState) { $constants = GlobalState::getConstantsAsString(); $globals = GlobalState::getGlobalsAsString(); $includedFiles = GlobalState::getIncludedFilesAsString(); $iniSettings = GlobalState::getIniSettingsAsString(); } else { $constants = ''; if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . \var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n"; } else { $globals = ''; } $includedFiles = ''; $iniSettings = ''; } $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; $enforcesTimeLimit = $result->enforcesTimeLimit() ? 'true' : 'false'; $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false'; if (\defined('PHPUNIT_COMPOSER_INSTALL')) { $composerAutoload = \var_export(PHPUNIT_COMPOSER_INSTALL, true); } else { $composerAutoload = '\'\''; } if (\defined('__PHPUNIT_PHAR__')) { $phar = \var_export(__PHPUNIT_PHAR__, true); } else { $phar = '\'\''; } if ($result->getCodeCoverage()) { $codeCoverageFilter = $result->getCodeCoverage()->filter(); } else { $codeCoverageFilter = null; } $data = \var_export(\serialize($this->data), true); $dataName = \var_export($this->dataName, true); $dependencyInput = \var_export(\serialize($this->dependencyInput), true); $includePath = \var_export(\get_include_path(), true); $codeCoverageFilter = \var_export(\serialize($codeCoverageFilter), true); $data = "'." . $data . ".'"; $dataName = "'.(" . $dataName . ").'"; $dependencyInput = "'." . $dependencyInput . ".'"; $includePath = "'." . $includePath . ".'"; $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? ''; $template->setVar( [ 'composerAutoload' => $composerAutoload, 'phar' => $phar, 'filename' => $class->getFileName(), 'className' => $class->getName(), 'methodName' => $this->name, 'collectCodeCoverageInformation' => $coverage, 'data' => $data, 'dataName' => $dataName, 'dependencyInput' => $dependencyInput, 'constants' => $constants, 'globals' => $globals, 'include_path' => $includePath, 'included_files' => $includedFiles, 'iniSettings' => $iniSettings, 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, 'enforcesTimeLimit' => $enforcesTimeLimit, 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, 'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests, 'codeCoverageFilter' => $codeCoverageFilter, 'configurationFilePath' => $configurationFilePath ] ); $this->prepareTemplate($template); $php = AbstractPhpProcess::factory(); $php->runTestJob($template->render(), $this, $result); } else { $result->run($this); } if ($this->useErrorHandler !== null) { $result->convertErrorsToExceptions($oldErrorHandlerSetting); } $this->result = null; return $result; } public function runBare() { $this->numAssertions = 0; $this->snapshotGlobalState(); $this->startOutputBuffering(); \clearstatcache(); $currentWorkingDirectory = \getcwd(); $hookMethods = \PHPUnit\Util\Test::getHookMethods(\get_class($this)); try { $hasMetRequirements = false; $this->checkRequirements(); $hasMetRequirements = true; if ($this->inIsolation) { foreach ($hookMethods['beforeClass'] as $method) { $this->$method(); } } $this->setExpectedExceptionFromAnnotation(); $this->setDoesNotPerformAssertionsFromAnnotation(); foreach ($hookMethods['before'] as $method) { $this->$method(); } $this->assertPreConditions(); $this->testResult = $this->runTest(); $this->verifyMockObjects(); $this->assertPostConditions(); if (!empty($this->warnings)) { throw new Warning( \implode( "\n", \array_unique($this->warnings) ) ); } $this->status = BaseTestRunner::STATUS_PASSED; } catch (IncompleteTest $e) { $this->status = BaseTestRunner::STATUS_INCOMPLETE; $this->statusMessage = $e->getMessage(); } catch (SkippedTest $e) { $this->status = BaseTestRunner::STATUS_SKIPPED; $this->statusMessage = $e->getMessage(); } catch (Warning $e) { $this->status = BaseTestRunner::STATUS_WARNING; $this->statusMessage = $e->getMessage(); } catch (AssertionFailedError $e) { $this->status = BaseTestRunner::STATUS_FAILURE; $this->statusMessage = $e->getMessage(); } catch (PredictionException $e) { $this->status = BaseTestRunner::STATUS_FAILURE; $this->statusMessage = $e->getMessage(); } catch (Throwable $_e) { $e = $_e; } if (isset($_e)) { $this->status = BaseTestRunner::STATUS_ERROR; $this->statusMessage = $_e->getMessage(); } $this->mockObjects = []; $this->prophet = null; try { if ($hasMetRequirements) { foreach ($hookMethods['after'] as $method) { $this->$method(); } if ($this->inIsolation) { foreach ($hookMethods['afterClass'] as $method) { $this->$method(); } } } } catch (Throwable $_e) { if (!isset($e)) { $e = $_e; } } try { $this->stopOutputBuffering(); } catch (RiskyTestError $_e) { if (!isset($e)) { $e = $_e; } } \clearstatcache(); if ($currentWorkingDirectory != \getcwd()) { \chdir($currentWorkingDirectory); } $this->restoreGlobalState(); foreach ($this->iniSettings as $varName => $oldValue) { \ini_set($varName, $oldValue); } $this->iniSettings = []; foreach ($this->locale as $category => $locale) { \setlocale($category, $locale); } if (!isset($e)) { try { if ($this->outputExpectedRegex !== null) { $this->assertRegExp($this->outputExpectedRegex, $this->output); } elseif ($this->outputExpectedString !== null) { $this->assertEquals($this->outputExpectedString, $this->output); } } catch (Throwable $_e) { $e = $_e; } } if (isset($e)) { if ($e instanceof PredictionException) { $e = new AssertionFailedError($e->getMessage()); } $this->onNotSuccessfulTest($e); } } protected function runTest() { if ($this->name === null) { throw new Exception( 'PHPUnit\Framework\TestCase::$name must not be null.' ); } try { $class = new ReflectionClass($this); $method = $class->getMethod($this->name); } catch (ReflectionException $e) { $this->fail($e->getMessage()); } $testArguments = \array_merge($this->data, $this->dependencyInput); $this->registerMockObjectsFromTestArguments($testArguments); try { $testResult = $method->invokeArgs($this, $testArguments); } catch (Throwable $_e) { $e = $_e; } if (isset($e)) { $checkException = false; if (!($e instanceof SkippedTestError) && \is_string($this->expectedException)) { $checkException = true; if ($e instanceof Exception) { $checkException = false; } $reflector = new ReflectionClass($this->expectedException); if ($this->expectedException === 'PHPUnit\Framework\Exception' || $this->expectedException === '\PHPUnit\Framework\Exception' || $reflector->isSubclassOf('PHPUnit\Framework\Exception')) { $checkException = true; } } if ($checkException) { $this->assertThat( $e, new ExceptionConstraint( $this->expectedException ) ); if (\is_string($this->expectedExceptionMessage) && !empty($this->expectedExceptionMessage)) { $this->assertThat( $e, new ExceptionMessage( $this->expectedExceptionMessage ) ); } if (\is_string($this->expectedExceptionMessageRegExp) && !empty($this->expectedExceptionMessageRegExp)) { $this->assertThat( $e, new ExceptionMessageRegularExpression( $this->expectedExceptionMessageRegExp ) ); } if ($this->expectedExceptionCode !== null) { $this->assertThat( $e, new ExceptionCode( $this->expectedExceptionCode ) ); } return; } throw $e; } if ($this->expectedException !== null) { $this->assertThat( null, new ExceptionConstraint( $this->expectedException ) ); } return $testResult; } protected function verifyMockObjects() { foreach ($this->mockObjects as $mockObject) { if ($mockObject->__phpunit_hasMatchers()) { $this->numAssertions++; } $mockObject->__phpunit_verify( $this->shouldInvocationMockerBeReset($mockObject) ); } if ($this->prophet !== null) { try { $this->prophet->checkPredictions(); } catch (Throwable $t) { } foreach ($this->prophet->getProphecies() as $objectProphecy) { foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { foreach ($methodProphecies as $methodProphecy) { $this->numAssertions += \count($methodProphecy->getCheckedPredictions()); } } } if (isset($t)) { throw $t; } } } public function setName($name) { $this->name = $name; } public function setDependencies(array $dependencies) { $this->dependencies = $dependencies; } public function hasDependencies() { return \count($this->dependencies) > 0; } public function setDependencyInput(array $dependencyInput) { $this->dependencyInput = $dependencyInput; } public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState) { $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; } public function setBackupGlobals($backupGlobals) { if (\is_null($this->backupGlobals) && \is_bool($backupGlobals)) { $this->backupGlobals = $backupGlobals; } } public function setBackupStaticAttributes($backupStaticAttributes) { if (\is_null($this->backupStaticAttributes) && \is_bool($backupStaticAttributes)) { $this->backupStaticAttributes = $backupStaticAttributes; } } public function setRunTestInSeparateProcess($runTestInSeparateProcess) { if (\is_bool($runTestInSeparateProcess)) { if ($this->runTestInSeparateProcess === null) { $this->runTestInSeparateProcess = $runTestInSeparateProcess; } } else { throw InvalidArgumentHelper::factory(1, 'boolean'); } } public function setPreserveGlobalState($preserveGlobalState) { if (\is_bool($preserveGlobalState)) { $this->preserveGlobalState = $preserveGlobalState; } else { throw InvalidArgumentHelper::factory(1, 'boolean'); } } public function setInIsolation($inIsolation) { if (\is_bool($inIsolation)) { $this->inIsolation = $inIsolation; } else { throw InvalidArgumentHelper::factory(1, 'boolean'); } } public function isInIsolation() { return $this->inIsolation; } public function getResult() { return $this->testResult; } public function setResult($result) { $this->testResult = $result; } public function setOutputCallback($callback) { if (!\is_callable($callback)) { throw InvalidArgumentHelper::factory(1, 'callback'); } $this->outputCallback = $callback; } public function getTestResultObject() { return $this->result; } public function setTestResultObject(TestResult $result) { $this->result = $result; } public function registerMockObject(PHPUnit_Framework_MockObject_MockObject $mockObject) { $this->mockObjects[] = $mockObject; } protected function iniSet($varName, $newValue) { if (!\is_string($varName)) { throw InvalidArgumentHelper::factory(1, 'string'); } $currentValue = \ini_set($varName, $newValue); if ($currentValue !== false) { $this->iniSettings[$varName] = $currentValue; } else { throw new Exception( \sprintf( 'INI setting "%s" could not be set to "%s".', $varName, $newValue ) ); } } protected function setLocale() { $args = \func_get_args(); if (\count($args) < 2) { throw new Exception; } $category = $args[0]; $locale = $args[1]; $categories = [ LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME ]; if (\defined('LC_MESSAGES')) { $categories[] = LC_MESSAGES; } if (!\in_array($category, $categories)) { throw new Exception; } if (!\is_array($locale) && !\is_string($locale)) { throw new Exception; } $this->locale[$category] = \setlocale($category, 0); $result = \call_user_func_array('setlocale', $args); if ($result === false) { throw new Exception( 'The locale functionality is not implemented on your platform, ' . 'the specified locale does not exist or the category name is ' . 'invalid.' ); } } public function getMockBuilder($className) { return new PHPUnit_Framework_MockObject_MockBuilder($this, $className); } protected function createMock($originalClassName) { return $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() ->getMock(); } protected function createConfiguredMock($originalClassName, array $configuration) { $o = $this->createMock($originalClassName); foreach ($configuration as $method => $return) { $o->method($method)->willReturn($return); } return $o; } protected function createPartialMock($originalClassName, array $methods) { return $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() ->setMethods(empty($methods) ? null : $methods) ->getMock(); } protected function createTestProxy($originalClassName, array $constructorArguments = []) { return $this->getMockBuilder($originalClassName) ->setConstructorArgs($constructorArguments) ->enableProxyingToOriginalMethods() ->getMock(); } protected function getMockClass($originalClassName, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = false, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false) { $mock = $this->getMockObjectGenerator()->getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments ); return \get_class($mock); } protected function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false) { $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass( $originalClassName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments ); $this->registerMockObject($mockObject); return $mockObject; } protected function getMockFromWsdl($wsdlFile, $originalClassName = '', $mockClassName = '', array $methods = [], $callOriginalConstructor = true, array $options = []) { if ($originalClassName === '') { $originalClassName = \pathinfo(\basename(\parse_url($wsdlFile)['path']), PATHINFO_FILENAME); } if (!\class_exists($originalClassName)) { eval( $this->getMockObjectGenerator()->generateClassFromWsdl( $wsdlFile, $originalClassName, $methods, $options ) ); } $mockObject = $this->getMockObjectGenerator()->getMock( $originalClassName, $methods, ['', $options], $mockClassName, $callOriginalConstructor, false, false ); $this->registerMockObject($mockObject); return $mockObject; } protected function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false) { $mockObject = $this->getMockObjectGenerator()->getMockForTrait( $traitName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments ); $this->registerMockObject($mockObject); return $mockObject; } protected function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) { return $this->getMockObjectGenerator()->getObjectForTrait( $traitName, $arguments, $traitClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload ); } protected function prophesize($classOrInterface = null) { return $this->getProphet()->prophesize($classOrInterface); } public function addToAssertionCount($count) { $this->numAssertions += $count; } public function getNumAssertions() { return $this->numAssertions; } public static function any() { return new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount; } public static function never() { return new PHPUnit_Framework_MockObject_Matcher_InvokedCount(0); } public static function atLeast($requiredInvocations) { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount( $requiredInvocations ); } public static function atLeastOnce() { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce; } public static function once() { return new PHPUnit_Framework_MockObject_Matcher_InvokedCount(1); } public static function exactly($count) { return new PHPUnit_Framework_MockObject_Matcher_InvokedCount($count); } public static function atMost($allowedInvocations) { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount( $allowedInvocations ); } public static function at($index) { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex($index); } public static function returnValue($value) { return new PHPUnit_Framework_MockObject_Stub_Return($value); } public static function returnValueMap(array $valueMap) { return new PHPUnit_Framework_MockObject_Stub_ReturnValueMap($valueMap); } public static function returnArgument($argumentIndex) { return new PHPUnit_Framework_MockObject_Stub_ReturnArgument( $argumentIndex ); } public static function returnCallback($callback) { return new PHPUnit_Framework_MockObject_Stub_ReturnCallback($callback); } public static function returnSelf() { return new PHPUnit_Framework_MockObject_Stub_ReturnSelf(); } public static function throwException(Throwable $exception) { return new PHPUnit_Framework_MockObject_Stub_Exception($exception); } public static function onConsecutiveCalls() { $args = \func_get_args(); return new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($args); } public function usesDataProvider() { return !empty($this->data); } public function dataDescription() { return \is_string($this->dataName) ? $this->dataName : ''; } protected function getDataSetAsString($includeData = true) { $buffer = ''; if (!empty($this->data)) { if (\is_int($this->dataName)) { $buffer .= \sprintf(' with data set #%d', $this->dataName); } else { $buffer .= \sprintf(' with data set "%s"', $this->dataName); } $exporter = new Exporter; if ($includeData) { $buffer .= \sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); } } return $buffer; } protected function getProvidedData() { return $this->data; } protected function createResult() { return new TestResult; } protected function handleDependencies() { if (!empty($this->dependencies) && !$this->inIsolation) { $className = \get_class($this); $passed = $this->result->passed(); $passedKeys = \array_keys($passed); $numKeys = \count($passedKeys); for ($i = 0; $i < $numKeys; $i++) { $pos = \strpos($passedKeys[$i], ' with data set'); if ($pos !== false) { $passedKeys[$i] = \substr($passedKeys[$i], 0, $pos); } } $passedKeys = \array_flip(\array_unique($passedKeys)); foreach ($this->dependencies as $dependency) { $clone = false; if (\strpos($dependency, 'clone ') === 0) { $clone = true; $dependency = \substr($dependency, \strlen('clone ')); } elseif (\strpos($dependency, '!clone ') === 0) { $clone = false; $dependency = \substr($dependency, \strlen('!clone ')); } if (\strpos($dependency, '::') === false) { $dependency = $className . '::' . $dependency; } if (!isset($passedKeys[$dependency])) { $this->result->startTest($this); $this->result->addError( $this, new SkippedTestError( \sprintf( 'This test depends on "%s" to pass.', $dependency ) ), 0 ); $this->result->endTest($this, 0); return false; } if (isset($passed[$dependency])) { if ($passed[$dependency]['size'] != \PHPUnit\Util\Test::UNKNOWN && $this->getSize() != \PHPUnit\Util\Test::UNKNOWN && $passed[$dependency]['size'] > $this->getSize()) { $this->result->addError( $this, new SkippedTestError( 'This test depends on a test that is larger than itself.' ), 0 ); return false; } if ($clone) { $deepCopy = new DeepCopy; $deepCopy->skipUncloneable(false); $this->dependencyInput[$dependency] = $deepCopy->copy($passed[$dependency]['result']); } else { $this->dependencyInput[$dependency] = $passed[$dependency]['result']; } } else { $this->dependencyInput[$dependency] = null; } } } return true; } public static function setUpBeforeClass() { } protected function setUp() { } protected function assertPreConditions() { } protected function assertPostConditions() { } protected function tearDown() { } public static function tearDownAfterClass() { } protected function onNotSuccessfulTest(Throwable $t) { throw $t; } protected function prepareTemplate(Text_Template $template) { } private function getMockObjectGenerator() { if (null === $this->mockObjectGenerator) { $this->mockObjectGenerator = new PHPUnit_Framework_MockObject_Generator; } return $this->mockObjectGenerator; } private function startOutputBuffering() { \ob_start(); $this->outputBufferingActive = true; $this->outputBufferingLevel = \ob_get_level(); } private function stopOutputBuffering() { if (\ob_get_level() != $this->outputBufferingLevel) { while (\ob_get_level() >= $this->outputBufferingLevel) { \ob_end_clean(); } throw new RiskyTestError( 'Test code or tested code did not (only) close its own output buffers' ); } $output = \ob_get_contents(); if ($this->outputCallback === false) { $this->output = $output; } else { $this->output = \call_user_func_array( $this->outputCallback, [$output] ); } \ob_end_clean(); $this->outputBufferingActive = false; $this->outputBufferingLevel = \ob_get_level(); } private function snapshotGlobalState() { if ($this->runTestInSeparateProcess || $this->inIsolation || (!$this->backupGlobals === true && !$this->backupStaticAttributes)) { return; } $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true); } private function restoreGlobalState() { if (!$this->snapshot instanceof Snapshot) { return; } if ($this->beStrictAboutChangesToGlobalState) { try { $this->compareGlobalStateSnapshots( $this->snapshot, $this->createGlobalStateSnapshot($this->backupGlobals === true) ); } catch (RiskyTestError $rte) { } } $restorer = new Restorer; if ($this->backupGlobals === true) { $restorer->restoreGlobalVariables($this->snapshot); } if ($this->backupStaticAttributes) { $restorer->restoreStaticAttributes($this->snapshot); } $this->snapshot = null; if (isset($rte)) { throw $rte; } } private function createGlobalStateSnapshot($backupGlobals) { $blacklist = new Blacklist; foreach ($this->backupGlobalsBlacklist as $globalVariable) { $blacklist->addGlobalVariable($globalVariable); } if (!\defined('PHPUNIT_TESTSUITE')) { $blacklist->addClassNamePrefix('PHPUnit'); $blacklist->addClassNamePrefix('File_Iterator'); $blacklist->addClassNamePrefix('SebastianBergmann\CodeCoverage'); $blacklist->addClassNamePrefix('PHP_Invoker'); $blacklist->addClassNamePrefix('PHP_Timer'); $blacklist->addClassNamePrefix('PHP_Token'); $blacklist->addClassNamePrefix('Symfony'); $blacklist->addClassNamePrefix('Text_Template'); $blacklist->addClassNamePrefix('Doctrine\Instantiator'); $blacklist->addClassNamePrefix('Prophecy'); foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { foreach ($attributes as $attribute) { $blacklist->addStaticAttribute($class, $attribute); } } } return new Snapshot( $blacklist, $backupGlobals, (bool) $this->backupStaticAttributes, false, false, false, false, false, false, false ); } private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after) { $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true; if ($backupGlobals) { $this->compareGlobalStateSnapshotPart( $before->globalVariables(), $after->globalVariables(), "--- Global variables before the test\n+++ Global variables after the test\n" ); $this->compareGlobalStateSnapshotPart( $before->superGlobalVariables(), $after->superGlobalVariables(), "--- Super-global variables before the test\n+++ Super-global variables after the test\n" ); } if ($this->backupStaticAttributes) { $this->compareGlobalStateSnapshotPart( $before->staticAttributes(), $after->staticAttributes(), "--- Static attributes before the test\n+++ Static attributes after the test\n" ); } } private function compareGlobalStateSnapshotPart(array $before, array $after, $header) { if ($before != $after) { $differ = new Differ($header); $exporter = new Exporter; $diff = $differ->diff( $exporter->export($before), $exporter->export($after) ); throw new RiskyTestError( $diff ); } } private function getProphet() { if ($this->prophet === null) { $this->prophet = new Prophet; } return $this->prophet; } private function shouldInvocationMockerBeReset(PHPUnit_Framework_MockObject_MockObject $mock) { $enumerator = new Enumerator; foreach ($enumerator->enumerate($this->dependencyInput) as $object) { if ($mock === $object) { return false; } } if (!\is_array($this->testResult) && !\is_object($this->testResult)) { return true; } foreach ($enumerator->enumerate($this->testResult) as $object) { if ($mock === $object) { return false; } } return true; } private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []) { if ($this->registerMockObjectsFromTestArgumentsRecursively) { $enumerator = new Enumerator; foreach ($enumerator->enumerate($testArguments) as $object) { if ($object instanceof PHPUnit_Framework_MockObject_MockObject) { $this->registerMockObject($object); } } } else { foreach ($testArguments as $testArgument) { if ($testArgument instanceof PHPUnit_Framework_MockObject_MockObject) { if ($this->isCloneable($testArgument)) { $testArgument = clone $testArgument; } $this->registerMockObject($testArgument); } elseif (\is_array($testArgument) && !\in_array($testArgument, $visited, true)) { $visited[] = $testArgument; $this->registerMockObjectsFromTestArguments( $testArgument, $visited ); } } } } private function setDoesNotPerformAssertionsFromAnnotation() { $annotations = $this->getAnnotations(); if (isset($annotations['method']['doesNotPerformAssertions'])) { $this->doesNotPerformAssertions = true; } } private function isCloneable(PHPUnit_Framework_MockObject_MockObject $testArgument) { $reflector = new ReflectionObject($testArgument); if (!$reflector->isCloneable()) { return false; } if ($reflector->hasMethod('__clone') && $reflector->getMethod('__clone')->isPublic()) { return true; } return false; } } expectedMessageRegExp = $expected; } protected function matches($other) { $match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage()); if (false === $match) { throw new \PHPUnit\Framework\Exception( "Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'" ); } return 1 === $match; } protected function failureDescription($other) { return \sprintf( "exception message '%s' matches '%s'", $other->getMessage(), $this->expectedMessageRegExp ); } public function toString() { return 'exception message matches '; } } hasProperty($this->attributeName)) { $attribute = $class->getProperty($this->attributeName); return $attribute->isStatic(); } return false; } public function toString() { return \sprintf( 'has static attribute "%s"', $this->attributeName ); } } toString() ); } } string = $string; $this->ignoreCase = $ignoreCase; } protected function matches($other) { if ($this->ignoreCase) { return \mb_stripos($other, $this->string) !== false; } return \mb_strpos($other, $this->string) !== false; } public function toString() { if ($this->ignoreCase) { $string = \mb_strtolower($this->string); } else { $string = $this->string; } return \sprintf( 'contains "%s"', $string ); } } suffix = $suffix; } protected function matches($other) { return \substr($other, 0 - \strlen($this->suffix)) == $this->suffix; } public function toString() { return 'ends with "' . $this->suffix . '"'; } } constraints = []; foreach ($constraints as $constraint) { if (!($constraint instanceof Constraint)) { throw new \PHPUnit\Framework\Exception( 'All parameters to ' . __CLASS__ . ' must be a constraint object.' ); } $this->constraints[] = $constraint; } } public function evaluate($other, $description = '', $returnResult = false) { $success = true; $constraint = null; foreach ($this->constraints as $constraint) { if (!$constraint->evaluate($other, $description, true)) { $success = false; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString() { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' and '; } $text .= $constraint->toString(); } return $text; } public function count() { $count = 0; foreach ($this->constraints as $constraint) { $count += \count($constraint); } return $count; } } className = $className; } protected function matches($other) { return ($other instanceof $this->className); } protected function failureDescription($other) { return \sprintf( '%s is an instance of %s "%s"', $this->exporter->shortenedExport($other), $this->getType(), $this->className ); } public function toString() { return \sprintf( 'is instance of %s "%s"', $this->getType(), $this->className ); } private function getType() { try { $reflection = new ReflectionClass($this->className); if ($reflection->isInterface()) { return 'interface'; } } catch (ReflectionException $e) { } return 'class'; } } checkForObjectIdentity = $checkForObjectIdentity; $this->checkForNonObjectIdentity = $checkForNonObjectIdentity; $this->value = $value; } protected function matches($other) { if ($other instanceof SplObjectStorage) { return $other->contains($this->value); } if (\is_object($this->value)) { foreach ($other as $element) { if ($this->checkForObjectIdentity && $element === $this->value) { return true; } if (!$this->checkForObjectIdentity && $element == $this->value) { return true; } } } else { foreach ($other as $element) { if ($this->checkForNonObjectIdentity && $element === $this->value) { return true; } if (!$this->checkForNonObjectIdentity && $element == $this->value) { return true; } } } return false; } public function toString() { if (\is_string($this->value) && \strpos($this->value, "\n") !== false) { return 'contains "' . $this->value . '"'; } return 'contains ' . $this->exporter->export($this->value); } protected function failureDescription($other) { return \sprintf( '%s %s', \is_array($other) ? 'an array' : 'a traversable', $this->toString() ); } } constraint = $constraint; } public static function negate($string) { return \str_replace( [ 'contains ', 'exists', 'has ', 'is ', 'are ', 'matches ', 'starts with ', 'ends with ', 'reference ', 'not not ' ], [ 'does not contain ', 'does not exist', 'does not have ', 'is not ', 'are not ', 'does not match ', 'starts not with ', 'ends not with ', 'don\'t reference ', 'not ' ], $string ); } public function evaluate($other, $description = '', $returnResult = false) { $success = !$this->constraint->evaluate($other, $description, true); if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } protected function failureDescription($other) { switch (\get_class($this->constraint)) { case LogicalAnd::class: case self::class: case LogicalOr::class: return 'not( ' . $this->constraint->failureDescription($other) . ' )'; default: return self::negate( $this->constraint->failureDescription($other) ); } } public function toString() { switch (\get_class($this->constraint)) { case LogicalAnd::class: case self::class: case LogicalOr::class: return 'not( ' . $this->constraint->toString() . ' )'; default: return self::negate( $this->constraint->toString() ); } } public function count() { return \count($this->constraint); } } expectedCount = $expected; } protected function matches($other) { return $this->expectedCount === $this->getCountOf($other); } protected function getCountOf($other) { if ($other instanceof Countable || \is_array($other)) { return \count($other); } if ($other instanceof Traversable) { if ($other instanceof IteratorAggregate) { $iterator = $other->getIterator(); } else { $iterator = $other; } if ($iterator instanceof Generator) { return $this->getCountOfGenerator($iterator); } $key = $iterator->key(); $count = \iterator_count($iterator); if ($key !== null) { $iterator->rewind(); while ($iterator->valid() && $key !== $iterator->key()) { $iterator->next(); } } return $count; } } protected function getCountOfGenerator(Generator $generator) { for ($count = 0; $generator->valid(); $generator->next()) { $count += 1; } return $count; } protected function failureDescription($other) { return \sprintf( 'actual size %d matches expected size %d', $this->getCountOf($other), $this->expectedCount ); } public function toString() { return \sprintf( 'count matches %d', $this->expectedCount ); } } getCountOf($expected)); } } constraints = []; foreach ($constraints as $constraint) { if (!($constraint instanceof Constraint)) { $constraint = new IsEqual( $constraint ); } $this->constraints[] = $constraint; } } public function evaluate($other, $description = '', $returnResult = false) { $success = false; $constraint = null; foreach ($this->constraints as $constraint) { if ($constraint->evaluate($other, $description, true)) { $success = true; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString() { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' or '; } $text .= $constraint->toString(); } return $text; } public function count() { $count = 0; foreach ($this->constraints as $constraint) { $count += \count($constraint); } return $count; } } constraint = new IsType($type); } else { $this->constraint = new IsInstanceOf( $type ); } $this->type = $type; } public function evaluate($other, $description = '', $returnResult = false) { $success = true; foreach ($other as $item) { if (!$this->constraint->evaluate($item, '', true)) { $success = false; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString() { return 'contains only values of type "' . $this->type . '"'; } } value = $value; $this->delta = $delta; $this->maxDepth = $maxDepth; $this->canonicalize = $canonicalize; $this->ignoreCase = $ignoreCase; } public function evaluate($other, $description = '', $returnResult = false) { if ($this->value === $other) { return true; } $comparatorFactory = SebastianBergmann\Comparator\Factory::getInstance(); try { $comparator = $comparatorFactory->getComparatorFor( $this->value, $other ); $comparator->assertEquals( $this->value, $other, $this->delta, $this->canonicalize, $this->ignoreCase ); } catch (SebastianBergmann\Comparator\ComparisonFailure $f) { if ($returnResult) { return false; } throw new ExpectationFailedException( \trim($description . "\n" . $f->getMessage()), $f ); } return true; } public function toString() { $delta = ''; if (\is_string($this->value)) { if (\strpos($this->value, "\n") !== false) { return 'is equal to '; } return \sprintf( 'is equal to ', $this->value ); } if ($this->delta != 0) { $delta = \sprintf( ' with delta <%F>', $this->delta ); } return \sprintf( 'is equal to %s%s', $this->exporter->export($this->value), $delta ); } } key = $key; } protected function matches($other) { if (\is_array($other)) { return \array_key_exists($this->key, $other); } if ($other instanceof ArrayAccess) { return $other->offsetExists($this->key); } return false; } public function toString() { return 'has the key ' . $this->exporter->export($this->key); } protected function failureDescription($other) { return 'an array ' . $this->toString(); } } strict = $strict; $this->subset = $subset; } protected function matches($other) { $other = $this->toArray($other); $this->subset = $this->toArray($this->subset); $patched = \array_replace_recursive($other, $this->subset); if ($this->strict) { return $other === $patched; } return $other == $patched; } public function toString() { return 'has the subset ' . $this->exporter->export($this->subset); } protected function failureDescription($other) { return 'an array ' . $this->toString(); } private function toArray($other) { if (\is_array($other)) { return $other; } if ($other instanceof \ArrayObject) { return $other->getArrayCopy(); } if ($other instanceof \Traversable) { return \iterator_to_array($other); } return (array) $other; } } expectedCode = $expected; } protected function matches($other) { return (string) $other->getCode() == (string) $this->expectedCode; } protected function failureDescription($other) { return \sprintf( '%s is equal to expected exception code %s', $this->exporter->export($other->getCode()), $this->exporter->export($this->expectedCode) ); } public function toString() { return 'exception code is '; } } callback = $callback; } protected function matches($other) { return \call_user_func($this->callback, $other); } public function toString() { return 'is accepted by specified callback'; } } attributeName = $attributeName; } public function evaluate($other, $description = '', $returnResult = false) { return parent::evaluate( Assert::readAttribute( $other, $this->attributeName ), $description, $returnResult ); } public function toString() { return 'attribute "' . $this->attributeName . '" ' . $this->innerConstraint->toString(); } protected function failureDescription($other) { return $this->toString(); } } value = $value; } protected function matches($other) { list($error, $recodedOther) = $this->canonicalizeJson($other); if ($error) { return false; } list($error, $recodedValue) = $this->canonicalizeJson($this->value); if ($error) { return false; } return $recodedOther == $recodedValue; } protected function fail($other, $description, ComparisonFailure $comparisonFailure = null) { if ($comparisonFailure === null) { list($error) = $this->canonicalizeJson($other); if ($error) { parent::fail($other, $description); return; } list($error) = $this->canonicalizeJson($this->value); if ($error) { parent::fail($other, $description); return; } $comparisonFailure = new ComparisonFailure( \json_decode($this->value), \json_decode($other), $other, $this->value, false, 'Failed asserting that two json values are equal.' ); } parent::fail($other, $description, $comparisonFailure); } private function canonicalizeJson($json) { $decodedJson = \json_decode($json, true); if (\json_last_error()) { return [true, null]; } $this->recursiveSort($decodedJson); $reencodedJson = \json_encode($decodedJson); return [false, $reencodedJson]; } private function recursiveSort(&$json) { if (\is_array($json)) { \ksort($json); foreach ($json as $key => &$value) { $this->recursiveSort($value); } } } public function toString() { return \sprintf( 'matches JSON string "%s"', $this->value ); } } constraints = []; foreach ($constraints as $constraint) { if (!($constraint instanceof Constraint)) { $constraint = new IsEqual( $constraint ); } $this->constraints[] = $constraint; } } public function evaluate($other, $description = '', $returnResult = false) { $success = true; $lastResult = null; $constraint = null; foreach ($this->constraints as $constraint) { $result = $constraint->evaluate($other, $description, true); if ($result === $lastResult) { $success = false; break; } $lastResult = $result; } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } public function toString() { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' xor '; } $text .= $constraint->toString(); } return $text; } public function count() { $count = 0; foreach ($this->constraints as $constraint) { $count += \count($constraint); } return $count; } } expectedMessage = $expected; } protected function matches($other) { return \strpos($other->getMessage(), $this->expectedMessage) !== false; } protected function failureDescription($other) { return \sprintf( "exception message '%s' contains '%s'", $other->getMessage(), $this->expectedMessage ); } public function toString() { return 'exception message contains '; } } value = $value; } protected function matches($other) { return $this->value > $other; } public function toString() { return 'is less than ' . $this->exporter->export($this->value); } } value = $value; } protected function matches($other) { return $this->value < $other; } public function toString() { return 'is greater than ' . $this->exporter->export($this->value); } } pattern = $pattern; } protected function matches($other) { return \preg_match($this->pattern, $other) > 0; } public function toString() { return \sprintf( 'matches PCRE pattern "%s"', $this->pattern ); } } pattern = $this->createPatternFromFormat( \preg_replace('/\r\n/', "\n", $string) ); $this->string = $string; } protected function failureDescription($other) { return 'string matches format description'; } protected function additionalFailureDescription($other) { $from = \preg_split('(\r\n|\r|\n)', $this->string); $to = \preg_split('(\r\n|\r|\n)', $other); foreach ($from as $index => $line) { if (isset($to[$index]) && $line !== $to[$index]) { $line = $this->createPatternFromFormat($line); if (\preg_match($line, $to[$index]) > 0) { $from[$index] = $to[$index]; } } } $this->string = \implode("\n", $from); $other = \implode("\n", $to); $differ = new Differ("--- Expected\n+++ Actual\n"); return $differ->diff($this->string, $other); } protected function createPatternFromFormat($string) { $string = \str_replace( [ '%e', '%s', '%S', '%a', '%A', '%w', '%i', '%d', '%x', '%f', '%c' ], [ '\\' . DIRECTORY_SEPARATOR, '[^\r\n]+', '[^\r\n]*', '.+', '.*', '\s*', '[+-]?\d+', '\d+', '[0-9a-fA-F]+', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', '.' ], \preg_quote($string, '/') ); return '/^' . $string . '$/s'; } } value = $value; } public function evaluate($other, $description = '', $returnResult = false) { if (\is_float($this->value) && \is_float($other) && !\is_infinite($this->value) && !\is_infinite($other) && !\is_nan($this->value) && !\is_nan($other)) { $success = \abs($this->value - $other) < self::EPSILON; } else { $success = $this->value === $other; } if ($returnResult) { return $success; } if (!$success) { $f = null; if (\is_string($this->value) && \is_string($other)) { $f = new SebastianBergmann\Comparator\ComparisonFailure( $this->value, $other, \sprintf("'%s'", $this->value), \sprintf("'%s'", $other) ); } $this->fail($other, $description, $f); } } protected function failureDescription($other) { if (\is_object($this->value) && \is_object($other)) { return 'two variables reference the same object'; } if (\is_string($this->value) && \is_string($other)) { return 'two strings are identical'; } return parent::failureDescription($other); } public function toString() { if (\is_object($this->value)) { return 'is identical to an object of class "' . \get_class($this->value) . '"'; } return 'is identical to ' . $this->exporter->export($this->value); } } true, 'boolean' => true, 'bool' => true, 'double' => true, 'float' => true, 'integer' => true, 'int' => true, 'null' => true, 'numeric' => true, 'object' => true, 'real' => true, 'resource' => true, 'string' => true, 'scalar' => true, 'callable' => true ]; protected $type; public function __construct($type) { parent::__construct(); if (!isset($this->types[$type])) { throw new \PHPUnit\Framework\Exception( \sprintf( 'Type specified for PHPUnit\Framework\Constraint\IsType <%s> ' . 'is not a valid type.', $type ) ); } $this->type = $type; } protected function matches($other) { switch ($this->type) { case 'numeric': return \is_numeric($other); case 'integer': case 'int': return \is_int($other); case 'double': case 'float': case 'real': return \is_float($other); case 'string': return \is_string($other); case 'boolean': case 'bool': return \is_bool($other); case 'null': return \is_null($other); case 'array': return \is_array($other); case 'object': return \is_object($other); case 'resource': return \is_resource($other) || \is_string(@\get_resource_type($other)); case 'scalar': return \is_scalar($other); case 'callable': return \is_callable($other); } } public function toString() { return \sprintf( 'is of type "%s"', $this->type ); } } attributeName = $attributeName; } protected function matches($other) { $class = new ReflectionClass($other); return $class->hasProperty($this->attributeName); } public function toString() { return \sprintf( 'has attribute "%s"', $this->attributeName ); } protected function failureDescription($other) { return \sprintf( '%sclass "%s" %s', \is_object($other) ? 'object of ' : '', \is_object($other) ? \get_class($other) : $other, $this->toString() ); } } className = $className; } protected function matches($other) { return $other instanceof $this->className; } protected function failureDescription($other) { if ($other !== null) { $message = ''; if ($other instanceof Throwable) { $message = '. Message was: "' . $other->getMessage() . '" at' . "\n" . Filter::getFilteredStacktrace($other); } return \sprintf( 'exception of type "%s" matches expected exception "%s"%s', \get_class($other), $this->className, $message ); } return \sprintf( 'exception of type "%s" is thrown', $this->className ); } public function toString() { return \sprintf( 'exception of type "%s"', $this->className ); } } prefix = $prefix; } protected function matches($other) { return \strpos($other, $this->prefix) === 0; } public function toString() { return 'starts with "' . $this->prefix . '"'; } } exporter = new Exporter; } public function evaluate($other, $description = '', $returnResult = false) { $success = false; if ($this->matches($other)) { $success = true; } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } protected function matches($other) { return false; } public function count() { return 1; } protected function fail($other, $description, ComparisonFailure $comparisonFailure = null) { $failureDescription = \sprintf( 'Failed asserting that %s.', $this->failureDescription($other) ); $additionalFailureDescription = $this->additionalFailureDescription($other); if ($additionalFailureDescription) { $failureDescription .= "\n" . $additionalFailureDescription; } if (!empty($description)) { $failureDescription = $description . "\n" . $failureDescription; } throw new ExpectationFailedException( $failureDescription, $comparisonFailure ); } protected function additionalFailureDescription($other) { return ''; } protected function failureDescription($other) { return $this->exporter->export($other) . ' ' . $this->toString(); } } hasProperty($this->attributeName); } } innerConstraint = $innerConstraint; } public function evaluate($other, $description = '', $returnResult = false) { try { return $this->innerConstraint->evaluate( $other, $description, $returnResult ); } catch (ExpectationFailedException $e) { $this->fail($other, $description); } } public function count() { return \count($this->innerConstraint); } } exporter->shortenedExport($other), $error ); } public function toString() { return 'is valid JSON'; } } getMessage(); } } message = $message; parent::__construct('Warning'); } protected function runTest() { throw new Warning($this->message); } public function getMessage() { return $this->message; } public function toString() { return 'Warning'; } } tests = $testSuite->tests(); } public function rewind() { $this->position = 0; } public function valid() { return $this->position < \count($this->tests); } public function key() { return $this->position; } public function current() { return $this->valid() ? $this->tests[$this->position] : null; } public function next() { $this->position++; } public function getChildren() { return new self( $this->tests[$this->position] ); } public function hasChildren() { return $this->tests[$this->position] instanceof TestSuite; } } comparisonFailure = $comparisonFailure; parent::__construct($message, 0, $previous); } public function getComparisonFailure() { return $this->comparisonFailure; } } getMessage(); } } file = $file; $this->line = $line; } } testName = $failedTest->toString(); } else { $this->testName = \get_class($failedTest); } if (!$failedTest instanceof TestCase || !$failedTest->isInIsolation()) { $this->failedTest = $failedTest; } $this->thrownException = $t; } public function toString() { return \sprintf( '%s: %s', $this->testName, $this->thrownException->getMessage() ); } public function getExceptionAsString() { return self::exceptionToString($this->thrownException); } public static function exceptionToString(Throwable $e) { if ($e instanceof SelfDescribing) { $buffer = $e->toString(); if ($e instanceof ExpectationFailedException && $e->getComparisonFailure()) { $buffer .= $e->getComparisonFailure()->getDiff(); } if (!empty($buffer)) { $buffer = \trim($buffer) . "\n"; } return $buffer; } if ($e instanceof Error) { return $e->getMessage() . "\n"; } if ($e instanceof ExceptionWrapper) { return $e->getClassName() . ': ' . $e->getMessage() . "\n"; } return \get_class($e) . ': ' . $e->getMessage() . "\n"; } public function getTestName() { return $this->testName; } public function failedTest() { return $this->failedTest; } public function thrownException() { return $this->thrownException; } public function exceptionMessage() { return $this->thrownException()->getMessage(); } public function isFailure() { return ($this->thrownException() instanceof AssertionFailedError); } } message = $message; parent::__construct($className . '::' . $methodName); } protected function runTest() { $this->markTestSkipped($this->message); } public function getMessage() { return $this->message; } public function toString() { return $this->getName(); } } serializableTrace = $this->getTrace(); foreach ($this->serializableTrace as $i => $call) { unset($this->serializableTrace[$i]['args']); } } public function getSerializableTrace() { return $this->serializableTrace; } public function __toString() { $string = TestFailure::exceptionToString($this); if ($trace = Filter::getFilteredStacktrace($this)) { $string .= "\n" . $trace; } return $string; } public function __sleep() { return \array_keys(\get_object_vars($this)); } } listeners[] = $listener; } public function removeListener(TestListener $listener) { foreach ($this->listeners as $key => $_listener) { if ($listener === $_listener) { unset($this->listeners[$key]); } } } public function flushListeners() { foreach ($this->listeners as $listener) { if ($listener instanceof Printer) { $listener->flush(); } } } public function addError(Test $test, Throwable $t, $time) { if ($t instanceof RiskyTest) { $this->risky[] = new TestFailure($test, $t); $notifyMethod = 'addRiskyTest'; if ($test instanceof TestCase) { $test->markAsRisky(); } if ($this->stopOnRisky) { $this->stop(); } } elseif ($t instanceof IncompleteTest) { $this->notImplemented[] = new TestFailure($test, $t); $notifyMethod = 'addIncompleteTest'; if ($this->stopOnIncomplete) { $this->stop(); } } elseif ($t instanceof SkippedTest) { $this->skipped[] = new TestFailure($test, $t); $notifyMethod = 'addSkippedTest'; if ($this->stopOnSkipped) { $this->stop(); } } else { $this->errors[] = new TestFailure($test, $t); $notifyMethod = 'addError'; if ($this->stopOnError || $this->stopOnFailure) { $this->stop(); } } if ($t instanceof Error) { $t = new ExceptionWrapper($t); } foreach ($this->listeners as $listener) { $listener->$notifyMethod($test, $t, $time); } $this->lastTestFailed = true; $this->time += $time; } public function addWarning(Test $test, Warning $e, $time) { if ($this->stopOnWarning) { $this->stop(); } $this->warnings[] = new TestFailure($test, $e); foreach ($this->listeners as $listener) { $listener->addWarning($test, $e, $time); } $this->time += $time; } public function addFailure(Test $test, AssertionFailedError $e, $time) { if ($e instanceof RiskyTest || $e instanceof OutputError) { $this->risky[] = new TestFailure($test, $e); $notifyMethod = 'addRiskyTest'; if ($test instanceof TestCase) { $test->markAsRisky(); } if ($this->stopOnRisky) { $this->stop(); } } elseif ($e instanceof IncompleteTest) { $this->notImplemented[] = new TestFailure($test, $e); $notifyMethod = 'addIncompleteTest'; if ($this->stopOnIncomplete) { $this->stop(); } } elseif ($e instanceof SkippedTest) { $this->skipped[] = new TestFailure($test, $e); $notifyMethod = 'addSkippedTest'; if ($this->stopOnSkipped) { $this->stop(); } } else { $this->failures[] = new TestFailure($test, $e); $notifyMethod = 'addFailure'; if ($this->stopOnFailure) { $this->stop(); } } foreach ($this->listeners as $listener) { $listener->$notifyMethod($test, $e, $time); } $this->lastTestFailed = true; $this->time += $time; } public function startTestSuite(TestSuite $suite) { if ($this->topTestSuite === null) { $this->topTestSuite = $suite; } foreach ($this->listeners as $listener) { $listener->startTestSuite($suite); } } public function endTestSuite(TestSuite $suite) { foreach ($this->listeners as $listener) { $listener->endTestSuite($suite); } } public function startTest(Test $test) { $this->lastTestFailed = false; $this->runTests += \count($test); foreach ($this->listeners as $listener) { $listener->startTest($test); } } public function endTest(Test $test, $time) { foreach ($this->listeners as $listener) { $listener->endTest($test, $time); } if (!$this->lastTestFailed && $test instanceof TestCase) { $class = \get_class($test); $key = $class . '::' . $test->getName(); $this->passed[$key] = [ 'result' => $test->getResult(), 'size' => \PHPUnit\Util\Test::getSize( $class, $test->getName(false) ) ]; $this->time += $time; } } public function allHarmless() { return $this->riskyCount() == 0; } public function riskyCount() { return \count($this->risky); } public function allCompletelyImplemented() { return $this->notImplementedCount() == 0; } public function notImplementedCount() { return \count($this->notImplemented); } public function risky() { return $this->risky; } public function notImplemented() { return $this->notImplemented; } public function noneSkipped() { return $this->skippedCount() == 0; } public function skippedCount() { return \count($this->skipped); } public function skipped() { return $this->skipped; } public function errorCount() { return \count($this->errors); } public function errors() { return $this->errors; } public function failureCount() { return \count($this->failures); } public function failures() { return $this->failures; } public function warningCount() { return \count($this->warnings); } public function warnings() { return $this->warnings; } public function passed() { return $this->passed; } public function topTestSuite() { return $this->topTestSuite; } public function getCollectCodeCoverageInformation() { return $this->codeCoverage !== null; } public function run(Test $test) { Assert::resetCount(); $coversNothing = false; if ($test instanceof TestCase) { $test->setRegisterMockObjectsFromTestArgumentsRecursively( $this->registerMockObjectsFromTestArgumentsRecursively ); $annotations = $test->getAnnotations(); if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) { $coversNothing = true; } } $error = false; $failure = false; $warning = false; $incomplete = false; $risky = false; $skipped = false; $this->startTest($test); $errorHandlerSet = false; if ($this->convertErrorsToExceptions) { $oldErrorHandler = \set_error_handler( [\PHPUnit\Util\ErrorHandler::class, 'handleError'], E_ALL | E_STRICT ); if ($oldErrorHandler === null) { $errorHandlerSet = true; } else { \restore_error_handler(); } } $collectCodeCoverage = $this->codeCoverage !== null && !$test instanceof WarningTestCase && !$coversNothing; if ($collectCodeCoverage) { $this->codeCoverage->start($test); } $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests && !$test instanceof WarningTestCase && $test->getSize() == \PHPUnit\Util\Test::SMALL && \function_exists('xdebug_start_function_monitor'); if ($monitorFunctions) { \xdebug_start_function_monitor(ResourceOperations::getFunctions()); } PHP_Timer::start(); try { if (!$test instanceof WarningTestCase && $test->getSize() != \PHPUnit\Util\Test::UNKNOWN && $this->enforceTimeLimit && \extension_loaded('pcntl') && \class_exists('PHP_Invoker')) { switch ($test->getSize()) { case \PHPUnit\Util\Test::SMALL: $_timeout = $this->timeoutForSmallTests; break; case \PHPUnit\Util\Test::MEDIUM: $_timeout = $this->timeoutForMediumTests; break; case \PHPUnit\Util\Test::LARGE: $_timeout = $this->timeoutForLargeTests; break; } $invoker = new PHP_Invoker; $invoker->invoke([$test, 'runBare'], [], $_timeout); } else { $test->runBare(); } } catch (PHP_Invoker_TimeoutException $e) { $this->addFailure( $test, new PHPUnit_Framework_RiskyTestError( $e->getMessage() ), $_timeout ); $risky = true; } catch (PHPUnit_Framework_MockObject_Exception $e) { $e = new Warning( $e->getMessage() ); $warning = true; } catch (AssertionFailedError $e) { $failure = true; if ($e instanceof RiskyTestError) { $risky = true; } elseif ($e instanceof IncompleteTestError) { $incomplete = true; } elseif ($e instanceof SkippedTestError) { $skipped = true; } } catch (AssertionError $e) { $test->addToAssertionCount(1); $failure = true; $frame = $e->getTrace()[0]; $e = new AssertionFailedError( \sprintf( '%s in %s:%s', $e->getMessage(), $frame['file'], $frame['line'] ) ); } catch (Warning $e) { $warning = true; } catch (Exception $e) { $error = true; } catch (Throwable $e) { $e = new ExceptionWrapper($e); $error = true; } $time = PHP_Timer::stop(); $test->addToAssertionCount(Assert::getCount()); if ($monitorFunctions) { $blacklist = new Blacklist; $functions = \xdebug_get_monitored_functions(); \xdebug_stop_function_monitor(); foreach ($functions as $function) { if (!$blacklist->isBlacklisted($function['filename'])) { $this->addFailure( $test, new RiskyTestError( \sprintf( '%s() used in %s:%s', $function['function'], $function['filename'], $function['lineno'] ) ), $time ); } } } if ($this->beStrictAboutTestsThatDoNotTestAnything && $test->getNumAssertions() == 0) { $risky = true; } if ($collectCodeCoverage) { $append = !$risky && !$incomplete && !$skipped; $linesToBeCovered = []; $linesToBeUsed = []; if ($append && $test instanceof TestCase) { try { $linesToBeCovered = \PHPUnit\Util\Test::getLinesToBeCovered( \get_class($test), $test->getName(false) ); $linesToBeUsed = \PHPUnit\Util\Test::getLinesToBeUsed( \get_class($test), $test->getName(false) ); } catch (InvalidCoversTargetException $cce) { $this->addWarning( $test, new Warning( $cce->getMessage() ), $time ); } } try { $this->codeCoverage->stop( $append, $linesToBeCovered, $linesToBeUsed ); } catch (UnintentionallyCoveredCodeException $cce) { $this->addFailure( $test, new UnintentionallyCoveredCodeError( 'This test executed code that is not listed as code to be covered or used:' . PHP_EOL . $cce->getMessage() ), $time ); } catch (OriginalCoveredCodeNotExecutedException $cce) { $this->addFailure( $test, new CoveredCodeNotExecutedException( 'This test did not execute all the code that is listed as code to be covered:' . PHP_EOL . $cce->getMessage() ), $time ); } catch (OriginalMissingCoversAnnotationException $cce) { if ($linesToBeCovered !== false) { $this->addFailure( $test, new MissingCoversAnnotationException( 'This test does not have a @covers annotation but is expected to have one' ), $time ); } } catch (OriginalCodeCoverageException $cce) { $error = true; if (!isset($e)) { $e = $cce; } } } if ($errorHandlerSet === true) { \restore_error_handler(); } if ($error === true) { $this->addError($test, $e, $time); } elseif ($failure === true) { $this->addFailure($test, $e, $time); } elseif ($warning === true) { $this->addWarning($test, $e, $time); } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && !$test->doesNotPerformAssertions() && $test->getNumAssertions() == 0) { $this->addFailure( $test, new RiskyTestError( 'This test did not perform any assertions' ), $time ); } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { $this->addFailure( $test, new OutputError( \sprintf( 'This test printed output: %s', $test->getActualOutput() ) ), $time ); } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof TestCase) { $annotations = $test->getAnnotations(); if (isset($annotations['method']['todo'])) { $this->addFailure( $test, new RiskyTestError( 'Test method is annotated with @todo' ), $time ); } } $this->endTest($test, $time); } public function count() { return $this->runTests; } public function shouldStop() { return $this->stop; } public function stop() { $this->stop = true; } public function getCodeCoverage() { return $this->codeCoverage; } public function setCodeCoverage(CodeCoverage $codeCoverage) { $this->codeCoverage = $codeCoverage; } public function convertErrorsToExceptions($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->convertErrorsToExceptions = $flag; } public function getConvertErrorsToExceptions() { return $this->convertErrorsToExceptions; } public function stopOnError($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnError = $flag; } public function stopOnFailure($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnFailure = $flag; } public function stopOnWarning($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnWarning = $flag; } public function beStrictAboutTestsThatDoNotTestAnything($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutTestsThatDoNotTestAnything = $flag; } public function isStrictAboutTestsThatDoNotTestAnything() { return $this->beStrictAboutTestsThatDoNotTestAnything; } public function beStrictAboutOutputDuringTests($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutOutputDuringTests = $flag; } public function isStrictAboutOutputDuringTests() { return $this->beStrictAboutOutputDuringTests; } public function beStrictAboutResourceUsageDuringSmallTests($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutResourceUsageDuringSmallTests = $flag; } public function isStrictAboutResourceUsageDuringSmallTests() { return $this->beStrictAboutResourceUsageDuringSmallTests; } public function enforceTimeLimit($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->enforceTimeLimit = $flag; } public function enforcesTimeLimit() { return $this->enforceTimeLimit; } public function beStrictAboutTodoAnnotatedTests($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutTodoAnnotatedTests = $flag; } public function isStrictAboutTodoAnnotatedTests() { return $this->beStrictAboutTodoAnnotatedTests; } public function stopOnRisky($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnRisky = $flag; } public function stopOnIncomplete($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnIncomplete = $flag; } public function stopOnSkipped($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnSkipped = $flag; } public function time() { return $this->time; } public function wasSuccessful() { return empty($this->errors) && empty($this->failures) && empty($this->warnings); } public function setTimeoutForSmallTests($timeout) { if (!\is_int($timeout)) { throw InvalidArgumentHelper::factory(1, 'integer'); } $this->timeoutForSmallTests = $timeout; } public function setTimeoutForMediumTests($timeout) { if (!\is_int($timeout)) { throw InvalidArgumentHelper::factory(1, 'integer'); } $this->timeoutForMediumTests = $timeout; } public function setTimeoutForLargeTests($timeout) { if (!\is_int($timeout)) { throw InvalidArgumentHelper::factory(1, 'integer'); } $this->timeoutForLargeTests = $timeout; } public function getTimeoutForLargeTests() { return $this->timeoutForLargeTests; } public function setRegisterMockObjectsFromTestArgumentsRecursively($flag) { if (!\is_bool($flag)) { throw InvalidArgumentHelper::factory(1, 'boolean'); } $this->registerMockObjectsFromTestArgumentsRecursively = $flag; } protected function getHierarchy($className, $asReflectionObjects = false) { if ($asReflectionObjects) { $classes = [new ReflectionClass($className)]; } else { $classes = [$className]; } $done = false; while (!$done) { if ($asReflectionObjects) { $class = new ReflectionClass( $classes[\count($classes) - 1]->getName() ); } else { $class = new ReflectionClass($classes[\count($classes) - 1]); } $parent = $class->getParentClass(); if ($parent !== false) { if ($asReflectionObjects) { $classes[] = $parent; } else { $classes[] = $parent->getName(); } } else { $done = true; } } return $classes; } } message = $message; parent::__construct($className . '::' . $methodName); } protected function runTest() { $this->markTestIncomplete($this->message); } public function getMessage() { return $this->message; } public function toString() { return $this->getName(); } } 1, 'fg-black' => 30, 'fg-red' => 31, 'fg-green' => 32, 'fg-yellow' => 33, 'fg-blue' => 34, 'fg-magenta' => 35, 'fg-cyan' => 36, 'fg-white' => 37, 'bg-black' => 40, 'bg-red' => 41, 'bg-green' => 42, 'bg-yellow' => 43, 'bg-blue' => 44, 'bg-magenta' => 45, 'bg-cyan' => 46, 'bg-white' => 47 ]; protected $column = 0; protected $maxColumn; protected $lastTestFailed = false; protected $numAssertions = 0; protected $numTests = -1; protected $numTestsRun = 0; protected $numTestsWidth; protected $colors = false; protected $debug = false; protected $verbose = false; private $numberOfColumns; private $reverse; private $defectListPrinted = false; public function __construct($out = null, $verbose = false, $colors = self::COLOR_DEFAULT, $debug = false, $numberOfColumns = 80, $reverse = false) { parent::__construct($out); if (!\is_bool($verbose)) { throw InvalidArgumentHelper::factory(2, 'boolean'); } $availableColors = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS]; if (!\in_array($colors, $availableColors)) { throw InvalidArgumentHelper::factory( 3, \vsprintf('value from "%s", "%s" or "%s"', $availableColors) ); } if (!\is_bool($debug)) { throw InvalidArgumentHelper::factory(4, 'boolean'); } if (!\is_int($numberOfColumns) && $numberOfColumns != 'max') { throw InvalidArgumentHelper::factory(5, 'integer or "max"'); } if (!\is_bool($reverse)) { throw InvalidArgumentHelper::factory(6, 'boolean'); } $console = new Console; $maxNumberOfColumns = $console->getNumberOfColumns(); if ($numberOfColumns == 'max' || ($numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns)) { $numberOfColumns = $maxNumberOfColumns; } $this->numberOfColumns = $numberOfColumns; $this->verbose = $verbose; $this->debug = $debug; $this->reverse = $reverse; if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { $this->colors = true; } else { $this->colors = (self::COLOR_ALWAYS === $colors); } } public function printResult(TestResult $result) { $this->printHeader(); $this->printErrors($result); $this->printWarnings($result); $this->printFailures($result); $this->printRisky($result); if ($this->verbose) { $this->printIncompletes($result); $this->printSkipped($result); } $this->printFooter($result); } protected function printDefects(array $defects, $type) { $count = \count($defects); if ($count == 0) { return; } if ($this->defectListPrinted) { $this->write("\n--\n\n"); } $this->write( \sprintf( "There %s %d %s%s:\n", ($count == 1) ? 'was' : 'were', $count, $type, ($count == 1) ? '' : 's' ) ); $i = 1; if ($this->reverse) { $defects = \array_reverse($defects); } foreach ($defects as $defect) { $this->printDefect($defect, $i++); } $this->defectListPrinted = true; } protected function printDefect(TestFailure $defect, $count) { $this->printDefectHeader($defect, $count); $this->printDefectTrace($defect); } protected function printDefectHeader(TestFailure $defect, $count) { $this->write( \sprintf( "\n%d) %s\n", $count, $defect->getTestName() ) ); } protected function printDefectTrace(TestFailure $defect) { $e = $defect->thrownException(); $this->write((string) $e); while ($e = $e->getPrevious()) { $this->write("\nCaused by\n" . $e); } } protected function printErrors(TestResult $result) { $this->printDefects($result->errors(), 'error'); } protected function printFailures(TestResult $result) { $this->printDefects($result->failures(), 'failure'); } protected function printWarnings(TestResult $result) { $this->printDefects($result->warnings(), 'warning'); } protected function printIncompletes(TestResult $result) { $this->printDefects($result->notImplemented(), 'incomplete test'); } protected function printRisky(TestResult $result) { $this->printDefects($result->risky(), 'risky test'); } protected function printSkipped(TestResult $result) { $this->printDefects($result->skipped(), 'skipped test'); } protected function printHeader() { $this->write("\n\n" . PHP_Timer::resourceUsage() . "\n\n"); } protected function printFooter(TestResult $result) { if (\count($result) === 0) { $this->writeWithColor( 'fg-black, bg-yellow', 'No tests executed!' ); return; } if ($result->wasSuccessful() && $result->allHarmless() && $result->allCompletelyImplemented() && $result->noneSkipped()) { $this->writeWithColor( 'fg-black, bg-green', \sprintf( 'OK (%d test%s, %d assertion%s)', \count($result), (\count($result) == 1) ? '' : 's', $this->numAssertions, ($this->numAssertions == 1) ? '' : 's' ) ); } else { if ($result->wasSuccessful()) { $color = 'fg-black, bg-yellow'; if ($this->verbose || !$result->allHarmless()) { $this->write("\n"); } $this->writeWithColor( $color, 'OK, but incomplete, skipped, or risky tests!' ); } else { $this->write("\n"); if ($result->errorCount()) { $color = 'fg-white, bg-red'; $this->writeWithColor( $color, 'ERRORS!' ); } elseif ($result->failureCount()) { $color = 'fg-white, bg-red'; $this->writeWithColor( $color, 'FAILURES!' ); } elseif ($result->warningCount()) { $color = 'fg-black, bg-yellow'; $this->writeWithColor( $color, 'WARNINGS!' ); } } $this->writeCountString(\count($result), 'Tests', $color, true); $this->writeCountString($this->numAssertions, 'Assertions', $color, true); $this->writeCountString($result->errorCount(), 'Errors', $color); $this->writeCountString($result->failureCount(), 'Failures', $color); $this->writeCountString($result->warningCount(), 'Warnings', $color); $this->writeCountString($result->skippedCount(), 'Skipped', $color); $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); $this->writeCountString($result->riskyCount(), 'Risky', $color); $this->writeWithColor($color, '.', true); } } public function printWaitPrompt() { $this->write("\n to continue\n"); } public function addError(Test $test, \Exception $e, $time) { $this->writeProgressWithColor('fg-red, bold', 'E'); $this->lastTestFailed = true; } public function addFailure(Test $test, AssertionFailedError $e, $time) { $this->writeProgressWithColor('bg-red, fg-white', 'F'); $this->lastTestFailed = true; } public function addWarning(Test $test, Warning $e, $time) { $this->writeProgressWithColor('fg-yellow, bold', 'W'); $this->lastTestFailed = true; } public function addIncompleteTest(Test $test, \Exception $e, $time) { $this->writeProgressWithColor('fg-yellow, bold', 'I'); $this->lastTestFailed = true; } public function addRiskyTest(Test $test, \Exception $e, $time) { $this->writeProgressWithColor('fg-yellow, bold', 'R'); $this->lastTestFailed = true; } public function addSkippedTest(Test $test, \Exception $e, $time) { $this->writeProgressWithColor('fg-cyan, bold', 'S'); $this->lastTestFailed = true; } public function startTestSuite(TestSuite $suite) { if ($this->numTests == -1) { $this->numTests = \count($suite); $this->numTestsWidth = \strlen((string) $this->numTests); $this->maxColumn = $this->numberOfColumns - \strlen(' / (XXX%)') - (2 * $this->numTestsWidth); } } public function endTestSuite(TestSuite $suite) { } public function startTest(Test $test) { if ($this->debug) { $this->write( \sprintf( "\nStarting test '%s'.\n", \PHPUnit\Util\Test::describe($test) ) ); } } public function endTest(Test $test, $time) { if (!$this->lastTestFailed) { $this->writeProgress('.'); } if ($test instanceof TestCase) { $this->numAssertions += $test->getNumAssertions(); } elseif ($test instanceof PhptTestCase) { $this->numAssertions++; } $this->lastTestFailed = false; if ($test instanceof TestCase) { if (!$test->hasExpectationOnOutput()) { $this->write($test->getActualOutput()); } } } protected function writeProgress($progress) { $this->write($progress); $this->column++; $this->numTestsRun++; if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) { if ($this->numTestsRun == $this->numTests) { $this->write(\str_repeat(' ', $this->maxColumn - $this->column)); } $this->write( \sprintf( ' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)', $this->numTestsRun, $this->numTests, \floor(($this->numTestsRun / $this->numTests) * 100) ) ); if ($this->column == $this->maxColumn) { $this->writeNewLine(); } } } protected function writeNewLine() { $this->column = 0; $this->write("\n"); } protected function formatWithColor($color, $buffer) { if (!$this->colors) { return $buffer; } $codes = \array_map('trim', \explode(',', $color)); $lines = \explode("\n", $buffer); $padding = \max(\array_map('strlen', $lines)); $styles = []; foreach ($codes as $code) { $styles[] = self::$ansiCodes[$code]; } $style = \sprintf("\x1b[%sm", \implode(';', $styles)); $styledLines = []; foreach ($lines as $line) { $styledLines[] = $style . \str_pad($line, $padding) . "\x1b[0m"; } return \implode("\n", $styledLines); } protected function writeWithColor($color, $buffer, $lf = true) { $this->write($this->formatWithColor($color, $buffer)); if ($lf) { $this->write("\n"); } } protected function writeProgressWithColor($color, $buffer) { $buffer = $this->formatWithColor($color, $buffer); $this->writeProgress($buffer); } private function writeCountString($count, $name, $color, $always = false) { static $first = true; if ($always || $count > 0) { $this->writeWithColor( $color, \sprintf( '%s%s: %d', !$first ? ', ' : '', $name, $count ), false ); $first = false; } } } codeCoverageFilter = $filter; $this->loader = $loader; $this->runtime = new Runtime; } public static function run($test, array $arguments = []) { if ($test instanceof ReflectionClass) { $test = new TestSuite($test); } if ($test instanceof Test) { $aTestRunner = new self; return $aTestRunner->doRun( $test, $arguments ); } throw new Exception('No test case or test suite found.'); } protected function createTestResult() { return new TestResult; } private function processSuiteFilters(TestSuite $suite, array $arguments) { if (!$arguments['filter'] && empty($arguments['groups']) && empty($arguments['excludeGroups'])) { return; } $filterFactory = new Factory; if (!empty($arguments['excludeGroups'])) { $filterFactory->addFilter( new ReflectionClass(ExcludeGroupFilterIterator::class), $arguments['excludeGroups'] ); } if (!empty($arguments['groups'])) { $filterFactory->addFilter( new ReflectionClass(IncludeGroupFilterIterator::class), $arguments['groups'] ); } if ($arguments['filter']) { $filterFactory->addFilter( new ReflectionClass(NameFilterIterator::class), $arguments['filter'] ); } $suite->injectFilter($filterFactory); } public function doRun(Test $suite, array $arguments = [], $exit = true) { if (isset($arguments['configuration'])) { $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; } $this->handleConfiguration($arguments); $this->processSuiteFilters($suite, $arguments); if (isset($arguments['bootstrap'])) { $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; } if ($arguments['backupGlobals'] === true) { $suite->setBackupGlobals(true); } if ($arguments['backupStaticAttributes'] === true) { $suite->setBackupStaticAttributes(true); } if ($arguments['beStrictAboutChangesToGlobalState'] === true) { $suite->setbeStrictAboutChangesToGlobalState(true); } if (\is_int($arguments['repeat']) && $arguments['repeat'] > 0) { $_suite = new TestSuite; foreach (\range(1, $arguments['repeat']) as $step) { $_suite->addTest($suite); } $suite = $_suite; unset($_suite); } $result = $this->createTestResult(); if (!$arguments['convertErrorsToExceptions']) { $result->convertErrorsToExceptions(false); } if (!$arguments['convertNoticesToExceptions']) { Notice::$enabled = false; } if (!$arguments['convertWarningsToExceptions']) { Warning::$enabled = false; } if ($arguments['stopOnError']) { $result->stopOnError(true); } if ($arguments['stopOnFailure']) { $result->stopOnFailure(true); } if ($arguments['stopOnWarning']) { $result->stopOnWarning(true); } if ($arguments['stopOnIncomplete']) { $result->stopOnIncomplete(true); } if ($arguments['stopOnRisky']) { $result->stopOnRisky(true); } if ($arguments['stopOnSkipped']) { $result->stopOnSkipped(true); } if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); } if ($this->printer === null) { if (isset($arguments['printer']) && $arguments['printer'] instanceof Printer) { $this->printer = $arguments['printer']; } else { $printerClass = ResultPrinter::class; if (isset($arguments['printer']) && \is_string($arguments['printer']) && \class_exists($arguments['printer'], false)) { $class = new ReflectionClass($arguments['printer']); if ($class->isSubclassOf(ResultPrinter::class)) { $printerClass = $arguments['printer']; } } $this->printer = new $printerClass( isset($arguments['stderr']) ? 'php://stderr' : null, $arguments['verbose'], $arguments['colors'], $arguments['debug'], $arguments['columns'], $arguments['reverseList'] ); } } $this->printer->write( Version::getVersionString() . "\n" ); self::$versionStringPrinted = true; if ($arguments['verbose']) { $runtime = $this->runtime->getNameWithVersion(); if ($this->runtime->hasXdebug()) { $runtime .= \sprintf( ' with Xdebug %s', \phpversion('xdebug') ); } $this->writeMessage('Runtime', $runtime); if (isset($arguments['configuration'])) { $this->writeMessage( 'Configuration', $arguments['configuration']->getFilename() ); } foreach ($arguments['loadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } foreach ($arguments['notLoadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } } foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $result->addListener($this->printer); if (isset($arguments['testdoxHTMLFile'])) { $result->addListener( new HtmlResultPrinter( $arguments['testdoxHTMLFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxTextFile'])) { $result->addListener( new TextResultPrinter( $arguments['testdoxTextFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxXMLFile'])) { $result->addListener( new XmlResultPrinter( $arguments['testdoxXMLFile'] ) ); } $codeCoverageReports = 0; if (isset($arguments['coverageClover'])) { $codeCoverageReports++; } if (isset($arguments['coverageCrap4J'])) { $codeCoverageReports++; } if (isset($arguments['coverageHtml'])) { $codeCoverageReports++; } if (isset($arguments['coveragePHP'])) { $codeCoverageReports++; } if (isset($arguments['coverageText'])) { $codeCoverageReports++; } if (isset($arguments['coverageXml'])) { $codeCoverageReports++; } if (isset($arguments['noCoverage'])) { $codeCoverageReports = 0; } if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) { $this->writeMessage('Error', 'No code coverage driver is available'); $codeCoverageReports = 0; } if ($codeCoverageReports > 0) { $codeCoverage = new CodeCoverage( null, $this->codeCoverageFilter ); $codeCoverage->setUnintentionallyCoveredSubclassesWhitelist( [SebastianBergmann\Comparator\Comparator::class] ); $codeCoverage->setCheckForUnintentionallyCoveredCode( $arguments['strictCoverage'] ); $codeCoverage->setCheckForMissingCoversAnnotation( $arguments['strictCoverage'] ); if (isset($arguments['forceCoversAnnotation'])) { $codeCoverage->setForceCoversAnnotation( $arguments['forceCoversAnnotation'] ); } if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $codeCoverage->setIgnoreDeprecatedCode( $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ); } if (isset($arguments['disableCodeCoverageIgnore'])) { $codeCoverage->setDisableIgnoredLines(true); } if (isset($arguments['whitelist'])) { $this->codeCoverageFilter->addDirectoryToWhitelist($arguments['whitelist']); } if (isset($arguments['configuration'])) { $filterConfiguration = $arguments['configuration']->getFilterConfiguration(); if (empty($filterConfiguration['whitelist'])) { $this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.'); $codeCoverageReports = 0; unset($codeCoverage); } else { $codeCoverage->setAddUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist'] ); $codeCoverage->setProcessUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist'] ); foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) { $this->codeCoverageFilter->addDirectoryToWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['include']['file'] as $file) { $this->codeCoverageFilter->addFileToWhitelist($file); } foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) { $this->codeCoverageFilter->removeDirectoryFromWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) { $this->codeCoverageFilter->removeFileFromWhitelist($file); } } } if (isset($codeCoverage) && !$this->codeCoverageFilter->hasWhitelist()) { $this->writeMessage('Error', 'Incorrect whitelist config, no code coverage will be generated.'); $codeCoverageReports = 0; unset($codeCoverage); } } $this->printer->write("\n"); if (isset($codeCoverage)) { $result->setCodeCoverage($codeCoverage); if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) { $codeCoverage->setCacheTokens($arguments['cacheTokens']); } } if (isset($arguments['teamcityLogfile'])) { $result->addListener( new TeamCity($arguments['teamcityLogfile']) ); } if (isset($arguments['junitLogfile'])) { $result->addListener( new JUnit( $arguments['junitLogfile'], $arguments['reportUselessTests'] ) ); } $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); $result->enforceTimeLimit($arguments['enforceTimeLimit']); $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); if ($suite instanceof TestSuite) { $suite->setRunTestInSeparateProcess($arguments['processIsolation']); } $suite->run($result); unset($suite); $result->flushListeners(); if ($this->printer instanceof ResultPrinter) { $this->printer->printResult($result); } if (isset($codeCoverage)) { if (isset($arguments['coverageClover'])) { $this->printer->write( "\nGenerating code coverage report in Clover XML format ..." ); try { $writer = new CloverReport; $writer->process($codeCoverage, $arguments['coverageClover']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageCrap4J'])) { $this->printer->write( "\nGenerating Crap4J report XML file ..." ); try { $writer = new Crap4jReport($arguments['crap4jThreshold']); $writer->process($codeCoverage, $arguments['coverageCrap4J']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageHtml'])) { $this->printer->write( "\nGenerating code coverage report in HTML format ..." ); try { $writer = new HtmlReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], \sprintf( ' and PHPUnit %s', Version::id() ) ); $writer->process($codeCoverage, $arguments['coverageHtml']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coveragePHP'])) { $this->printer->write( "\nGenerating code coverage report in PHP format ..." ); try { $writer = new PhpReport; $writer->process($codeCoverage, $arguments['coveragePHP']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageText'])) { if ($arguments['coverageText'] == 'php://stdout') { $outputStream = $this->printer; $colors = $arguments['colors'] && $arguments['colors'] != ResultPrinter::COLOR_NEVER; } else { $outputStream = new Printer($arguments['coverageText']); $colors = false; } $processor = new TextReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], $arguments['coverageTextShowUncoveredFiles'], $arguments['coverageTextShowOnlySummary'] ); $outputStream->write( $processor->process($codeCoverage, $colors) ); } if (isset($arguments['coverageXml'])) { $this->printer->write( "\nGenerating code coverage report in PHPUnit XML format ..." ); try { $writer = new XmlReport(Version::id()); $writer->process($codeCoverage, $arguments['coverageXml']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } } if ($exit) { if ($result->wasSuccessful()) { if ($arguments['failOnRisky'] && !$result->allHarmless()) { exit(self::FAILURE_EXIT); } if ($arguments['failOnWarning'] && $result->warningCount() > 0) { exit(self::FAILURE_EXIT); } exit(self::SUCCESS_EXIT); } if ($result->errorCount() > 0) { exit(self::EXCEPTION_EXIT); } if ($result->failureCount() > 0) { exit(self::FAILURE_EXIT); } } return $result; } public function setPrinter(ResultPrinter $resultPrinter) { $this->printer = $resultPrinter; } protected function runFailed($message) { $this->write($message . PHP_EOL); exit(self::FAILURE_EXIT); } protected function write($buffer) { if (PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg') { $buffer = \htmlspecialchars($buffer); } if ($this->printer !== null) { $this->printer->write($buffer); } else { print $buffer; } } public function getLoader() { if ($this->loader === null) { $this->loader = new StandardTestSuiteLoader; } return $this->loader; } protected function handleConfiguration(array &$arguments) { if (isset($arguments['configuration']) && !$arguments['configuration'] instanceof Configuration) { $arguments['configuration'] = Configuration::getInstance( $arguments['configuration'] ); } $arguments['debug'] = $arguments['debug'] ?? false; $arguments['filter'] = $arguments['filter'] ?? false; $arguments['listeners'] = $arguments['listeners'] ?? []; if (isset($arguments['configuration'])) { $arguments['configuration']->handlePHPConfiguration(); $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration(); if (isset($phpunitConfiguration['backupGlobals']) && !isset($arguments['backupGlobals'])) { $arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals']; } if (isset($phpunitConfiguration['backupStaticAttributes']) && !isset($arguments['backupStaticAttributes'])) { $arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes']; } if (isset($phpunitConfiguration['beStrictAboutChangesToGlobalState']) && !isset($arguments['beStrictAboutChangesToGlobalState'])) { $arguments['beStrictAboutChangesToGlobalState'] = $phpunitConfiguration['beStrictAboutChangesToGlobalState']; } if (isset($phpunitConfiguration['bootstrap']) && !isset($arguments['bootstrap'])) { $arguments['bootstrap'] = $phpunitConfiguration['bootstrap']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['colors']) && !isset($arguments['colors'])) { $arguments['colors'] = $phpunitConfiguration['colors']; } if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) { $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions']; } if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) { $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions']; } if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) { $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions']; } if (isset($phpunitConfiguration['processIsolation']) && !isset($arguments['processIsolation'])) { $arguments['processIsolation'] = $phpunitConfiguration['processIsolation']; } if (isset($phpunitConfiguration['stopOnError']) && !isset($arguments['stopOnError'])) { $arguments['stopOnError'] = $phpunitConfiguration['stopOnError']; } if (isset($phpunitConfiguration['stopOnFailure']) && !isset($arguments['stopOnFailure'])) { $arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure']; } if (isset($phpunitConfiguration['stopOnWarning']) && !isset($arguments['stopOnWarning'])) { $arguments['stopOnWarning'] = $phpunitConfiguration['stopOnWarning']; } if (isset($phpunitConfiguration['stopOnIncomplete']) && !isset($arguments['stopOnIncomplete'])) { $arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete']; } if (isset($phpunitConfiguration['stopOnRisky']) && !isset($arguments['stopOnRisky'])) { $arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky']; } if (isset($phpunitConfiguration['stopOnSkipped']) && !isset($arguments['stopOnSkipped'])) { $arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped']; } if (isset($phpunitConfiguration['failOnWarning']) && !isset($arguments['failOnWarning'])) { $arguments['failOnWarning'] = $phpunitConfiguration['failOnWarning']; } if (isset($phpunitConfiguration['failOnRisky']) && !isset($arguments['failOnRisky'])) { $arguments['failOnRisky'] = $phpunitConfiguration['failOnRisky']; } if (isset($phpunitConfiguration['timeoutForSmallTests']) && !isset($arguments['timeoutForSmallTests'])) { $arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests']; } if (isset($phpunitConfiguration['timeoutForMediumTests']) && !isset($arguments['timeoutForMediumTests'])) { $arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests']; } if (isset($phpunitConfiguration['timeoutForLargeTests']) && !isset($arguments['timeoutForLargeTests'])) { $arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests']; } if (isset($phpunitConfiguration['reportUselessTests']) && !isset($arguments['reportUselessTests'])) { $arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests']; } if (isset($phpunitConfiguration['strictCoverage']) && !isset($arguments['strictCoverage'])) { $arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage']; } if (isset($phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']) && !isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']; } if (isset($phpunitConfiguration['disallowTestOutput']) && !isset($arguments['disallowTestOutput'])) { $arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput']; } if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) { $arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit']; } if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) && !isset($arguments['disallowTodoAnnotatedTests'])) { $arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests']; } if (isset($phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']) && !isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])) { $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']; } if (isset($phpunitConfiguration['verbose']) && !isset($arguments['verbose'])) { $arguments['verbose'] = $phpunitConfiguration['verbose']; } if (isset($phpunitConfiguration['reverseDefectList']) && !isset($arguments['reverseList'])) { $arguments['reverseList'] = $phpunitConfiguration['reverseDefectList']; } if (isset($phpunitConfiguration['forceCoversAnnotation']) && !isset($arguments['forceCoversAnnotation'])) { $arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation']; } if (isset($phpunitConfiguration['disableCodeCoverageIgnore']) && !isset($arguments['disableCodeCoverageIgnore'])) { $arguments['disableCodeCoverageIgnore'] = $phpunitConfiguration['disableCodeCoverageIgnore']; } if (isset($phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']) && !isset($arguments['registerMockObjectsFromTestArgumentsRecursively'])) { $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']; } $groupCliArgs = []; if (!empty($arguments['groups'])) { $groupCliArgs = $arguments['groups']; } $groupConfiguration = $arguments['configuration']->getGroupConfiguration(); if (!empty($groupConfiguration['include']) && !isset($arguments['groups'])) { $arguments['groups'] = $groupConfiguration['include']; } if (!empty($groupConfiguration['exclude']) && !isset($arguments['excludeGroups'])) { $arguments['excludeGroups'] = \array_diff($groupConfiguration['exclude'], $groupCliArgs); } foreach ($arguments['configuration']->getListenerConfiguration() as $listener) { if (!\class_exists($listener['class'], false) && $listener['file'] !== '') { require_once $listener['file']; } if (!\class_exists($listener['class'])) { throw new Exception( \sprintf( 'Class "%s" does not exist', $listener['class'] ) ); } $listenerClass = new ReflectionClass($listener['class']); if (!$listenerClass->implementsInterface(TestListener::class)) { throw new Exception( \sprintf( 'Class "%s" does not implement the PHPUnit\Framework\TestListener interface', $listener['class'] ) ); } if (\count($listener['arguments']) == 0) { $listener = new $listener['class']; } else { $listener = $listenerClass->newInstanceArgs( $listener['arguments'] ); } $arguments['listeners'][] = $listener; } $loggingConfiguration = $arguments['configuration']->getLoggingConfiguration(); if (isset($loggingConfiguration['coverage-clover']) && !isset($arguments['coverageClover'])) { $arguments['coverageClover'] = $loggingConfiguration['coverage-clover']; } if (isset($loggingConfiguration['coverage-crap4j']) && !isset($arguments['coverageCrap4J'])) { $arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j']; if (isset($loggingConfiguration['crap4jThreshold']) && !isset($arguments['crap4jThreshold'])) { $arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold']; } } if (isset($loggingConfiguration['coverage-html']) && !isset($arguments['coverageHtml'])) { if (isset($loggingConfiguration['lowUpperBound']) && !isset($arguments['reportLowUpperBound'])) { $arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound']; } if (isset($loggingConfiguration['highLowerBound']) && !isset($arguments['reportHighLowerBound'])) { $arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound']; } $arguments['coverageHtml'] = $loggingConfiguration['coverage-html']; } if (isset($loggingConfiguration['coverage-php']) && !isset($arguments['coveragePHP'])) { $arguments['coveragePHP'] = $loggingConfiguration['coverage-php']; } if (isset($loggingConfiguration['coverage-text']) && !isset($arguments['coverageText'])) { $arguments['coverageText'] = $loggingConfiguration['coverage-text']; if (isset($loggingConfiguration['coverageTextShowUncoveredFiles'])) { $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles']; } else { $arguments['coverageTextShowUncoveredFiles'] = false; } if (isset($loggingConfiguration['coverageTextShowOnlySummary'])) { $arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary']; } else { $arguments['coverageTextShowOnlySummary'] = false; } } if (isset($loggingConfiguration['coverage-xml']) && !isset($arguments['coverageXml'])) { $arguments['coverageXml'] = $loggingConfiguration['coverage-xml']; } if (isset($loggingConfiguration['plain'])) { $arguments['listeners'][] = new ResultPrinter( $loggingConfiguration['plain'], true ); } if (isset($loggingConfiguration['teamcity']) && !isset($arguments['teamcityLogfile'])) { $arguments['teamcityLogfile'] = $loggingConfiguration['teamcity']; } if (isset($loggingConfiguration['junit']) && !isset($arguments['junitLogfile'])) { $arguments['junitLogfile'] = $loggingConfiguration['junit']; } if (isset($loggingConfiguration['testdox-html']) && !isset($arguments['testdoxHTMLFile'])) { $arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html']; } if (isset($loggingConfiguration['testdox-text']) && !isset($arguments['testdoxTextFile'])) { $arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text']; } if (isset($loggingConfiguration['testdox-xml']) && !isset($arguments['testdoxXMLFile'])) { $arguments['testdoxXMLFile'] = $loggingConfiguration['testdox-xml']; } $testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration(); if (isset($testdoxGroupConfiguration['include']) && !isset($arguments['testdoxGroups'])) { $arguments['testdoxGroups'] = $testdoxGroupConfiguration['include']; } if (isset($testdoxGroupConfiguration['exclude']) && !isset($arguments['testdoxExcludeGroups'])) { $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration['exclude']; } } $arguments['addUncoveredFilesFromWhitelist'] = $arguments['addUncoveredFilesFromWhitelist'] ?? true; $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; $arguments['cacheTokens'] = $arguments['cacheTokens'] ?? false; $arguments['colors'] = $arguments['colors'] ?? ResultPrinter::COLOR_DEFAULT; $arguments['columns'] = $arguments['columns'] ?? 80; $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; $arguments['groups'] = $arguments['groups'] ?? []; $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; $arguments['processUncoveredFilesFromWhitelist'] = $arguments['processUncoveredFilesFromWhitelist'] ?? false; $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; $arguments['repeat'] = $arguments['repeat'] ?? false; $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; $arguments['reverseList'] = $arguments['reverseList'] ?? false; $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; $arguments['verbose'] = $arguments['verbose'] ?? false; } private function writeMessage($type, $message) { if (!$this->messagePrinted) { $this->write("\n"); } $this->write( \sprintf( "%-15s%s\n", $type . ':', $message ) ); $this->messagePrinted = true; } } false, 'listSuites' => false, 'loader' => null, 'useDefaultConfiguration' => true, 'loadedExtensions' => [], 'notLoadedExtensions' => [] ]; protected $options = []; protected $longOptions = [ 'atleast-version=' => null, 'bootstrap=' => null, 'check-version' => null, 'colors==' => null, 'columns=' => null, 'configuration=' => null, 'coverage-clover=' => null, 'coverage-crap4j=' => null, 'coverage-html=' => null, 'coverage-php=' => null, 'coverage-text==' => null, 'coverage-xml=' => null, 'debug' => null, 'disallow-test-output' => null, 'disallow-resource-usage' => null, 'disallow-todo-tests' => null, 'enforce-time-limit' => null, 'exclude-group=' => null, 'filter=' => null, 'generate-configuration' => null, 'globals-backup' => null, 'group=' => null, 'help' => null, 'include-path=' => null, 'list-groups' => null, 'list-suites' => null, 'loader=' => null, 'log-junit=' => null, 'log-teamcity=' => null, 'no-configuration' => null, 'no-coverage' => null, 'no-extensions' => null, 'printer=' => null, 'process-isolation' => null, 'repeat=' => null, 'dont-report-useless-tests' => null, 'reverse-list' => null, 'static-backup' => null, 'stderr' => null, 'stop-on-error' => null, 'stop-on-failure' => null, 'stop-on-warning' => null, 'stop-on-incomplete' => null, 'stop-on-risky' => null, 'stop-on-skipped' => null, 'fail-on-warning' => null, 'fail-on-risky' => null, 'strict-coverage' => null, 'disable-coverage-ignore' => null, 'strict-global-state' => null, 'teamcity' => null, 'testdox' => null, 'testdox-group=' => null, 'testdox-exclude-group=' => null, 'testdox-html=' => null, 'testdox-text=' => null, 'testdox-xml=' => null, 'test-suffix=' => null, 'testsuite=' => null, 'verbose' => null, 'version' => null, 'whitelist=' => null ]; private $versionStringPrinted = false; public static function main($exit = true) { $command = new static; return $command->run($_SERVER['argv'], $exit); } public function run(array $argv, $exit = true) { $this->handleArguments($argv); $runner = $this->createRunner(); if ($this->arguments['test'] instanceof Test) { $suite = $this->arguments['test']; } else { $suite = $runner->getTest( $this->arguments['test'], $this->arguments['testFile'], $this->arguments['testSuffixes'] ); } if ($this->arguments['listGroups']) { $this->printVersionString(); print "Available test group(s):\n"; $groups = $suite->getGroups(); \sort($groups); foreach ($groups as $group) { print " - $group\n"; } if ($exit) { exit(TestRunner::SUCCESS_EXIT); } return TestRunner::SUCCESS_EXIT; } if ($this->arguments['listSuites']) { $this->printVersionString(); print "Available test suite(s):\n"; $configuration = Configuration::getInstance( $this->arguments['configuration'] ); $suiteNames = $configuration->getTestSuiteNames(); foreach ($suiteNames as $suiteName) { print " - $suiteName\n"; } if ($exit) { exit(TestRunner::SUCCESS_EXIT); } return TestRunner::SUCCESS_EXIT; } unset($this->arguments['test']); unset($this->arguments['testFile']); try { $result = $runner->doRun($suite, $this->arguments, $exit); } catch (Exception $e) { print $e->getMessage() . "\n"; } $return = TestRunner::FAILURE_EXIT; if (isset($result) && $result->wasSuccessful()) { $return = TestRunner::SUCCESS_EXIT; } elseif (!isset($result) || $result->errorCount() > 0) { $return = TestRunner::EXCEPTION_EXIT; } if ($exit) { exit($return); } return $return; } protected function createRunner() { return new TestRunner($this->arguments['loader']); } protected function handleArguments(array $argv) { try { $this->options = Getopt::getopt( $argv, 'd:c:hv', \array_keys($this->longOptions) ); } catch (Exception $t) { $this->showError($t->getMessage()); } foreach ($this->options[0] as $option) { switch ($option[0]) { case '--colors': $this->arguments['colors'] = $option[1] ?: ResultPrinter::COLOR_AUTO; break; case '--bootstrap': $this->arguments['bootstrap'] = $option[1]; break; case '--columns': if (\is_numeric($option[1])) { $this->arguments['columns'] = (int) $option[1]; } elseif ($option[1] == 'max') { $this->arguments['columns'] = 'max'; } break; case 'c': case '--configuration': $this->arguments['configuration'] = $option[1]; break; case '--coverage-clover': $this->arguments['coverageClover'] = $option[1]; break; case '--coverage-crap4j': $this->arguments['coverageCrap4J'] = $option[1]; break; case '--coverage-html': $this->arguments['coverageHtml'] = $option[1]; break; case '--coverage-php': $this->arguments['coveragePHP'] = $option[1]; break; case '--coverage-text': if ($option[1] === null) { $option[1] = 'php://stdout'; } $this->arguments['coverageText'] = $option[1]; $this->arguments['coverageTextShowUncoveredFiles'] = false; $this->arguments['coverageTextShowOnlySummary'] = false; break; case '--coverage-xml': $this->arguments['coverageXml'] = $option[1]; break; case 'd': $ini = \explode('=', $option[1]); if (isset($ini[0])) { if (isset($ini[1])) { \ini_set($ini[0], $ini[1]); } else { \ini_set($ini[0], true); } } break; case '--debug': $this->arguments['debug'] = true; break; case 'h': case '--help': $this->showHelp(); exit(TestRunner::SUCCESS_EXIT); break; case '--filter': $this->arguments['filter'] = $option[1]; break; case '--testsuite': $this->arguments['testsuite'] = $option[1]; break; case '--generate-configuration': $this->printVersionString(); \printf( "Generating phpunit.xml in %s\n\n", \getcwd() ); print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; $bootstrapScript = \trim(\fgets(STDIN)); print 'Tests directory (relative to path shown above; default: tests): '; $testsDirectory = \trim(\fgets(STDIN)); print 'Source directory (relative to path shown above; default: src): '; $src = \trim(\fgets(STDIN)); if ($bootstrapScript == '') { $bootstrapScript = 'vendor/autoload.php'; } if ($testsDirectory == '') { $testsDirectory = 'tests'; } if ($src == '') { $src = 'src'; } $generator = new ConfigurationGenerator; \file_put_contents( 'phpunit.xml', $generator->generateDefaultConfiguration( Version::series(), $bootstrapScript, $testsDirectory, $src ) ); \printf( "\nGenerated phpunit.xml in %s\n", \getcwd() ); exit(TestRunner::SUCCESS_EXIT); break; case '--group': $this->arguments['groups'] = \explode(',', $option[1]); break; case '--exclude-group': $this->arguments['excludeGroups'] = \explode( ',', $option[1] ); break; case '--test-suffix': $this->arguments['testSuffixes'] = \explode( ',', $option[1] ); break; case '--include-path': $includePath = $option[1]; break; case '--list-groups': $this->arguments['listGroups'] = true; break; case '--list-suites': $this->arguments['listSuites'] = true; break; case '--printer': $this->arguments['printer'] = $option[1]; break; case '--loader': $this->arguments['loader'] = $option[1]; break; case '--log-junit': $this->arguments['junitLogfile'] = $option[1]; break; case '--log-teamcity': $this->arguments['teamcityLogfile'] = $option[1]; break; case '--process-isolation': $this->arguments['processIsolation'] = true; break; case '--repeat': $this->arguments['repeat'] = (int) $option[1]; break; case '--stderr': $this->arguments['stderr'] = true; break; case '--stop-on-error': $this->arguments['stopOnError'] = true; break; case '--stop-on-failure': $this->arguments['stopOnFailure'] = true; break; case '--stop-on-warning': $this->arguments['stopOnWarning'] = true; break; case '--stop-on-incomplete': $this->arguments['stopOnIncomplete'] = true; break; case '--stop-on-risky': $this->arguments['stopOnRisky'] = true; break; case '--stop-on-skipped': $this->arguments['stopOnSkipped'] = true; break; case '--fail-on-warning': $this->arguments['failOnWarning'] = true; break; case '--fail-on-risky': $this->arguments['failOnRisky'] = true; break; case '--teamcity': $this->arguments['printer'] = TeamCity::class; break; case '--testdox': $this->arguments['printer'] = TextResultPrinter::class; break; case '--testdox-group': $this->arguments['testdoxGroups'] = \explode( ',', $option[1] ); break; case '--testdox-exclude-group': $this->arguments['testdoxExcludeGroups'] = \explode( ',', $option[1] ); break; case '--testdox-html': $this->arguments['testdoxHTMLFile'] = $option[1]; break; case '--testdox-text': $this->arguments['testdoxTextFile'] = $option[1]; break; case '--testdox-xml': $this->arguments['testdoxXMLFile'] = $option[1]; break; case '--no-configuration': $this->arguments['useDefaultConfiguration'] = false; break; case '--no-extensions': $this->arguments['noExtensions'] = true; break; case '--no-coverage': $this->arguments['noCoverage'] = true; break; case '--globals-backup': $this->arguments['backupGlobals'] = true; break; case '--static-backup': $this->arguments['backupStaticAttributes'] = true; break; case 'v': case '--verbose': $this->arguments['verbose'] = true; break; case '--atleast-version': if (\version_compare(Version::id(), $option[1], '>=')) { exit(TestRunner::SUCCESS_EXIT); } exit(TestRunner::FAILURE_EXIT); break; case '--version': $this->printVersionString(); exit(TestRunner::SUCCESS_EXIT); break; case '--dont-report-useless-tests': $this->arguments['reportUselessTests'] = false; break; case '--strict-coverage': $this->arguments['strictCoverage'] = true; break; case '--disable-coverage-ignore': $this->arguments['disableCodeCoverageIgnore'] = true; break; case '--strict-global-state': $this->arguments['beStrictAboutChangesToGlobalState'] = true; break; case '--disallow-test-output': $this->arguments['disallowTestOutput'] = true; break; case '--disallow-resource-usage': $this->arguments['beStrictAboutResourceUsageDuringSmallTests'] = true; break; case '--enforce-time-limit': $this->arguments['enforceTimeLimit'] = true; break; case '--disallow-todo-tests': $this->arguments['disallowTodoAnnotatedTests'] = true; break; case '--reverse-list': $this->arguments['reverseList'] = true; break; case '--check-version': $this->handleVersionCheck(); break; case '--whitelist': $this->arguments['whitelist'] = $option[1]; break; default: $optionName = \str_replace('--', '', $option[0]); $handler = null; if (isset($this->longOptions[$optionName])) { $handler = $this->longOptions[$optionName]; } elseif (isset($this->longOptions[$optionName . '='])) { $handler = $this->longOptions[$optionName . '=']; } if (isset($handler) && \is_callable([$this, $handler])) { $this->$handler($option[1]); } } } $this->handleCustomTestSuite(); if (!isset($this->arguments['test'])) { if (isset($this->options[1][0])) { $this->arguments['test'] = $this->options[1][0]; } if (isset($this->options[1][1])) { $this->arguments['testFile'] = \realpath($this->options[1][1]); } else { $this->arguments['testFile'] = ''; } if (isset($this->arguments['test']) && \is_file($this->arguments['test']) && \substr($this->arguments['test'], -5, 5) != '.phpt') { $this->arguments['testFile'] = \realpath($this->arguments['test']); $this->arguments['test'] = \substr($this->arguments['test'], 0, \strrpos($this->arguments['test'], '.')); } } if (!isset($this->arguments['testSuffixes'])) { $this->arguments['testSuffixes'] = ['Test.php', '.phpt']; } if (isset($includePath)) { \ini_set( 'include_path', $includePath . PATH_SEPARATOR . \ini_get('include_path') ); } if ($this->arguments['loader'] !== null) { $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']); } if (isset($this->arguments['configuration']) && \is_dir($this->arguments['configuration'])) { $configurationFile = $this->arguments['configuration'] . '/phpunit.xml'; if (\file_exists($configurationFile)) { $this->arguments['configuration'] = \realpath( $configurationFile ); } elseif (\file_exists($configurationFile . '.dist')) { $this->arguments['configuration'] = \realpath( $configurationFile . '.dist' ); } } elseif (!isset($this->arguments['configuration']) && $this->arguments['useDefaultConfiguration']) { if (\file_exists('phpunit.xml')) { $this->arguments['configuration'] = \realpath('phpunit.xml'); } elseif (\file_exists('phpunit.xml.dist')) { $this->arguments['configuration'] = \realpath( 'phpunit.xml.dist' ); } } if (isset($this->arguments['configuration'])) { try { $configuration = Configuration::getInstance( $this->arguments['configuration'] ); } catch (Throwable $t) { print $t->getMessage() . "\n"; exit(TestRunner::FAILURE_EXIT); } $phpunitConfiguration = $configuration->getPHPUnitConfiguration(); $configuration->handlePHPConfiguration(); if (isset($this->arguments['bootstrap'])) { $this->handleBootstrap($this->arguments['bootstrap']); } elseif (isset($phpunitConfiguration['bootstrap'])) { $this->handleBootstrap($phpunitConfiguration['bootstrap']); } if (isset($phpunitConfiguration['stderr']) && !isset($this->arguments['stderr'])) { $this->arguments['stderr'] = $phpunitConfiguration['stderr']; } if (isset($phpunitConfiguration['extensionsDirectory']) && !isset($this->arguments['noExtensions']) && \extension_loaded('phar')) { $this->handleExtensions($phpunitConfiguration['extensionsDirectory']); } if (isset($phpunitConfiguration['columns']) && !isset($this->arguments['columns'])) { $this->arguments['columns'] = $phpunitConfiguration['columns']; } if (!isset($this->arguments['printer']) && isset($phpunitConfiguration['printerClass'])) { if (isset($phpunitConfiguration['printerFile'])) { $file = $phpunitConfiguration['printerFile']; } else { $file = ''; } $this->arguments['printer'] = $this->handlePrinter( $phpunitConfiguration['printerClass'], $file ); } if (isset($phpunitConfiguration['testSuiteLoaderClass'])) { if (isset($phpunitConfiguration['testSuiteLoaderFile'])) { $file = $phpunitConfiguration['testSuiteLoaderFile']; } else { $file = ''; } $this->arguments['loader'] = $this->handleLoader( $phpunitConfiguration['testSuiteLoaderClass'], $file ); } if (!isset($this->arguments['testsuite']) && isset($phpunitConfiguration['defaultTestSuite'])) { $this->arguments['testsuite'] = $phpunitConfiguration['defaultTestSuite']; } if (!isset($this->arguments['test'])) { $testSuite = $configuration->getTestSuiteConfiguration($this->arguments['testsuite'] ?? null); if ($testSuite !== null) { $this->arguments['test'] = $testSuite; } } } elseif (isset($this->arguments['bootstrap'])) { $this->handleBootstrap($this->arguments['bootstrap']); } if (isset($this->arguments['printer']) && \is_string($this->arguments['printer'])) { $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']); } if (isset($this->arguments['test']) && \is_string($this->arguments['test']) && \substr($this->arguments['test'], -5, 5) == '.phpt') { $test = new PhptTestCase($this->arguments['test']); $this->arguments['test'] = new TestSuite; $this->arguments['test']->addTest($test); } if (!isset($this->arguments['test'])) { $this->showHelp(); exit(TestRunner::EXCEPTION_EXIT); } } protected function handleLoader($loaderClass, $loaderFile = '') { if (!\class_exists($loaderClass, false)) { if ($loaderFile == '') { $loaderFile = Filesystem::classNameToFilename( $loaderClass ); } $loaderFile = \stream_resolve_include_path($loaderFile); if ($loaderFile) { require $loaderFile; } } if (\class_exists($loaderClass, false)) { $class = new ReflectionClass($loaderClass); if ($class->implementsInterface(TestSuiteLoader::class) && $class->isInstantiable()) { return $class->newInstance(); } } if ($loaderClass == StandardTestSuiteLoader::class) { return; } $this->showError( \sprintf( 'Could not use "%s" as loader.', $loaderClass ) ); } protected function handlePrinter($printerClass, $printerFile = '') { if (!\class_exists($printerClass, false)) { if ($printerFile == '') { $printerFile = Filesystem::classNameToFilename( $printerClass ); } $printerFile = \stream_resolve_include_path($printerFile); if ($printerFile) { require $printerFile; } } if (\class_exists($printerClass)) { $class = new ReflectionClass($printerClass); if ($class->implementsInterface(TestListener::class) && $class->isSubclassOf(Printer::class) && $class->isInstantiable()) { if ($class->isSubclassOf(ResultPrinter::class)) { return $printerClass; } $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null; return $class->newInstance($outputStream); } } $this->showError( \sprintf( 'Could not use "%s" as printer.', $printerClass ) ); } protected function handleBootstrap($filename) { try { Fileloader::checkAndLoad($filename); } catch (Exception $e) { $this->showError($e->getMessage()); } } protected function handleVersionCheck() { $this->printVersionString(); $latestVersion = \file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit'); $isOutdated = \version_compare($latestVersion, Version::id(), '>'); if ($isOutdated) { \printf( "You are not using the latest version of PHPUnit.\n" . "The latest version is PHPUnit %s.\n", $latestVersion ); } else { print "You are using the latest version of PHPUnit.\n"; } exit(TestRunner::SUCCESS_EXIT); } protected function showHelp() { $this->printVersionString(); print << Code Coverage Options: --coverage-clover Generate code coverage report in Clover XML format. --coverage-crap4j Generate code coverage report in Crap4J XML format. --coverage-html Generate code coverage report in HTML format. --coverage-php Export PHP_CodeCoverage object to file. --coverage-text= Generate code coverage report in text format. Default: Standard output. --coverage-xml Generate code coverage report in PHPUnit XML format. --whitelist Whitelist for code coverage analysis. --disable-coverage-ignore Disable annotations for ignoring code coverage. Logging Options: --log-junit Log test execution in JUnit XML format to file. --log-teamcity Log test execution in TeamCity format to file. --testdox-html Write agile documentation in HTML format to file. --testdox-text Write agile documentation in Text format to file. --testdox-xml Write agile documentation in XML format to file. --reverse-list Print defects in reverse order Test Selection Options: --filter Filter which tests to run. --testsuite Filter which testsuite to run. --group ... Only runs tests from the specified group(s). --exclude-group ... Exclude tests from the specified group(s). --list-groups List available test groups. --list-suites List available test suites. --test-suffix ... Only search for test in files with specified suffix(es). Default: Test.php,.phpt Test Execution Options: --dont-report-useless-tests Do not report tests that do not test anything. --strict-coverage Be strict about @covers annotation usage. --strict-global-state Be strict about changes to global state --disallow-test-output Be strict about output during tests. --disallow-resource-usage Be strict about resource usage during small tests. --enforce-time-limit Enforce time limit based on test size. --disallow-todo-tests Disallow @todo-annotated tests. --process-isolation Run each test in a separate PHP process. --globals-backup Backup and restore \$GLOBALS for each test. --static-backup Backup and restore static attributes for each test. --colors= Use colors in output ("never", "auto" or "always"). --columns Number of columns to use for progress output. --columns max Use maximum number of columns for progress output. --stderr Write to STDERR instead of STDOUT. --stop-on-error Stop execution upon first error. --stop-on-failure Stop execution upon first error or failure. --stop-on-warning Stop execution upon first warning. --stop-on-risky Stop execution upon first risky test. --stop-on-skipped Stop execution upon first skipped test. --stop-on-incomplete Stop execution upon first incomplete test. --fail-on-warning Treat tests with warnings as failures. --fail-on-risky Treat risky tests as failures. -v|--verbose Output more verbose information. --debug Display debugging information. --loader TestSuiteLoader implementation to use. --repeat Runs the test(s) repeatedly. --teamcity Report test execution progress in TeamCity format. --testdox Report test execution progress in TestDox format. --testdox-group Only include tests from the specified group(s). --testdox-exclude-group Exclude tests from the specified group(s). --printer TestListener implementation to use. Configuration Options: --bootstrap A "bootstrap" PHP file that is run before the tests. -c|--configuration Read configuration from XML file. --no-configuration Ignore default configuration file (phpunit.xml). --no-coverage Ignore code coverage configuration. --no-extensions Do not load PHPUnit extensions. --include-path Prepend PHP's include_path with given path(s). -d key[=value] Sets a php.ini value. --generate-configuration Generate configuration file with suggested settings. Miscellaneous Options: -h|--help Prints this usage information. --version Prints the version and exits. --atleast-version Checks that version is greater than min and exits. --check-version Check whether PHPUnit is the latest version. EOT; } protected function handleCustomTestSuite() { } private function printVersionString() { if ($this->versionStringPrinted) { return; } print Version::getVersionString() . "\n\n"; $this->versionStringPrinted = true; } private function showError($message) { $this->printVersionString(); print $message . "\n"; exit(TestRunner::FAILURE_EXIT); } private function handleExtensions($directory) { $facade = new File_Iterator_Facade; foreach ($facade->getFilesAsArray($directory, '.phar') as $file) { if (!\file_exists('phar://' . $file . '/manifest.xml')) { $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit'; continue; } try { $applicationName = new ApplicationName('phpunit/phpunit'); $version = new PharIoVersion(Version::series()); $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); if (!$manifest->isExtensionFor($applicationName)) { $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit'; continue; } if (!$manifest->isExtensionFor($applicationName, $version)) { $this->arguments['notLoadedExtensions'][] = $file . ' is not compatible with this version of PHPUnit'; continue; } } catch (ManifestException $e) { $this->arguments['notLoadedExtensions'][] = $file . ': ' . $e->getMessage(); continue; } require $file; $this->arguments['loadedExtensions'][] = $manifest->getName() . ' ' . $manifest->getVersion()->getVersionString(); } } } #!/usr/bin/env php &1'); if (\strpos($tag, '-') === false && \strpos($tag, 'No names found') === false) { print $tag; } else { $branch = @\exec('git rev-parse --abbrev-ref HEAD'); $hash = @\exec('git log -1 --format="%H"'); print $branch . '@' . $hash; } print "\n"; $lock = \json_decode(\file_get_contents(__DIR__ . '/../composer.lock')); foreach ($lock->packages as $package) { print $package->name . ': ' . $package->version; if (!\preg_match('/^[v= ]*(([0-9]+)(\\.([0-9]+)(\\.([0-9]+)(-([0-9]+))?(-?([a-zA-Z-+][a-zA-Z0-9\\.\\-:]*)?)?)?)?)$/', $package->version)) { print '@' . $package->source->reference; } print "\n"; } #!/usr/bin/env php getVersion(); getPathsAfterResolvingWildcards($paths); $exclude = $this->getPathsAfterResolvingWildcards($exclude); if (is_string($prefixes)) { if ($prefixes != '') { $prefixes = array($prefixes); } else { $prefixes = array(); } } if (is_string($suffixes)) { if ($suffixes != '') { $suffixes = array($suffixes); } else { $suffixes = array(); } } $iterator = new AppendIterator; foreach ($paths as $path) { if (is_dir($path)) { $iterator->append( new File_Iterator( new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS) ), $suffixes, $prefixes, $exclude, $path ) ); } } return $iterator; } protected function getPathsAfterResolvingWildcards(array $paths) { $_paths = array(); foreach ($paths as $path) { if ($locals = glob($path, GLOB_ONLYDIR)) { $_paths = array_merge($_paths, $locals); } else { $_paths[] = $path; } } return $_paths; } } getFileIterator( $paths, $suffixes, $prefixes, $exclude ); $files = array(); foreach ($iterator as $file) { $file = $file->getRealPath(); if ($file) { $files[] = $file; } } foreach ($paths as $path) { if (is_file($path)) { $files[] = realpath($path); } } $files = array_unique($files); sort($files); if ($commonPath) { return array( 'commonPath' => $this->getCommonPath($files), 'files' => $files ); } else { return $files; } } protected function getCommonPath(array $files) { $count = count($files); if ($count == 0) { return ''; } if ($count == 1) { return dirname($files[0]) . DIRECTORY_SEPARATOR; } $_files = array(); foreach ($files as $file) { $_files[] = $_fileParts = explode(DIRECTORY_SEPARATOR, $file); if (empty($_fileParts[0])) { $_fileParts[0] = DIRECTORY_SEPARATOR; } } $common = ''; $done = FALSE; $j = 0; $count--; while (!$done) { for ($i = 0; $i < $count; $i++) { if ($_files[$i][$j] != $_files[$i+1][$j]) { $done = TRUE; break; } } if (!$done) { $common .= $_files[0][$j]; if ($j > 0) { $common .= DIRECTORY_SEPARATOR; } } $j++; } return DIRECTORY_SEPARATOR . $common; } } prefixes = $prefixes; $this->suffixes = $suffixes; $this->exclude = $exclude; $this->basepath = $basepath; parent::__construct($iterator); } public function accept() { $current = $this->getInnerIterator()->current(); $filename = $current->getFilename(); $realpath = $current->getRealPath(); if ($this->basepath !== NULL) { $realpath = str_replace($this->basepath, '', $realpath); } if (preg_match('=/\.[^/]*/=', $realpath)) { return FALSE; } return $this->acceptPath($realpath) && $this->acceptPrefix($filename) && $this->acceptSuffix($filename); } protected function acceptPath($path) { foreach ($this->exclude as $exclude) { if (strpos($path, $exclude) === 0) { return FALSE; } } return TRUE; } protected function acceptPrefix($filename) { return $this->acceptSubString($filename, $this->prefixes, self::PREFIX); } protected function acceptSuffix($filename) { return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX); } protected function acceptSubString($filename, array $subStrings, $type) { if (empty($subStrings)) { return TRUE; } $matched = FALSE; foreach ($subStrings as $string) { if (($type == self::PREFIX && strpos($filename, $string) === 0) || ($type == self::SUFFIX && substr($filename, -1 * strlen($string)) == $string)) { $matched = TRUE; break; } } return $matched; } } 3600000, 'minute' => 60000, 'second' => 1000 ); private static $startTimes = array(); public static $requestTime; public static function start() { array_push(self::$startTimes, microtime(true)); } public static function stop() { return microtime(true) - array_pop(self::$startTimes); } public static function secondsToTimeString($time) { $ms = round($time * 1000); foreach (self::$times as $unit => $value) { if ($ms >= $value) { $time = floor($ms / $value * 100.0) / 100.0; return $time . ' ' . ($time == 1 ? $unit : $unit . 's'); } } return $ms . ' ms'; } public static function timeSinceStartOfRequest() { return self::secondsToTimeString(microtime(true) - self::$requestTime); } public static function resourceUsage() { return sprintf( 'Time: %s, Memory: %4.2fMB', self::timeSinceStartOfRequest(), memory_get_peak_usage(true) / 1048576 ); } } if (isset($_SERVER['REQUEST_TIME_FLOAT'])) { PHP_Timer::$requestTime = $_SERVER['REQUEST_TIME_FLOAT']; } elseif (isset($_SERVER['REQUEST_TIME'])) { PHP_Timer::$requestTime = $_SERVER['REQUEST_TIME']; } else { PHP_Timer::$requestTime = microtime(true); } numFiles == -1) { $this->numFiles = 0; foreach ($this->children as $child) { $this->numFiles += count($child); } } return $this->numFiles; } public function getIterator() { return new \RecursiveIteratorIterator( new Iterator($this), \RecursiveIteratorIterator::SELF_FIRST ); } public function addDirectory($name) { $directory = new self($name, $this); $this->children[] = $directory; $this->directories[] = &$this->children[count($this->children) - 1]; return $directory; } public function addFile($name, array $coverageData, array $testData, $cacheTokens) { $file = new File( $name, $this, $coverageData, $testData, $cacheTokens ); $this->children[] = $file; $this->files[] = &$this->children[count($this->children) - 1]; $this->numExecutableLines = -1; $this->numExecutedLines = -1; return $file; } public function getDirectories() { return $this->directories; } public function getFiles() { return $this->files; } public function getChildNodes() { return $this->children; } public function getClasses() { if ($this->classes === null) { $this->classes = []; foreach ($this->children as $child) { $this->classes = array_merge( $this->classes, $child->getClasses() ); } } return $this->classes; } public function getTraits() { if ($this->traits === null) { $this->traits = []; foreach ($this->children as $child) { $this->traits = array_merge( $this->traits, $child->getTraits() ); } } return $this->traits; } public function getFunctions() { if ($this->functions === null) { $this->functions = []; foreach ($this->children as $child) { $this->functions = array_merge( $this->functions, $child->getFunctions() ); } } return $this->functions; } public function getLinesOfCode() { if ($this->linesOfCode === null) { $this->linesOfCode = ['loc' => 0, 'cloc' => 0, 'ncloc' => 0]; foreach ($this->children as $child) { $linesOfCode = $child->getLinesOfCode(); $this->linesOfCode['loc'] += $linesOfCode['loc']; $this->linesOfCode['cloc'] += $linesOfCode['cloc']; $this->linesOfCode['ncloc'] += $linesOfCode['ncloc']; } } return $this->linesOfCode; } public function getNumExecutableLines() { if ($this->numExecutableLines == -1) { $this->numExecutableLines = 0; foreach ($this->children as $child) { $this->numExecutableLines += $child->getNumExecutableLines(); } } return $this->numExecutableLines; } public function getNumExecutedLines() { if ($this->numExecutedLines == -1) { $this->numExecutedLines = 0; foreach ($this->children as $child) { $this->numExecutedLines += $child->getNumExecutedLines(); } } return $this->numExecutedLines; } public function getNumClasses() { if ($this->numClasses == -1) { $this->numClasses = 0; foreach ($this->children as $child) { $this->numClasses += $child->getNumClasses(); } } return $this->numClasses; } public function getNumTestedClasses() { if ($this->numTestedClasses == -1) { $this->numTestedClasses = 0; foreach ($this->children as $child) { $this->numTestedClasses += $child->getNumTestedClasses(); } } return $this->numTestedClasses; } public function getNumTraits() { if ($this->numTraits == -1) { $this->numTraits = 0; foreach ($this->children as $child) { $this->numTraits += $child->getNumTraits(); } } return $this->numTraits; } public function getNumTestedTraits() { if ($this->numTestedTraits == -1) { $this->numTestedTraits = 0; foreach ($this->children as $child) { $this->numTestedTraits += $child->getNumTestedTraits(); } } return $this->numTestedTraits; } public function getNumMethods() { if ($this->numMethods == -1) { $this->numMethods = 0; foreach ($this->children as $child) { $this->numMethods += $child->getNumMethods(); } } return $this->numMethods; } public function getNumTestedMethods() { if ($this->numTestedMethods == -1) { $this->numTestedMethods = 0; foreach ($this->children as $child) { $this->numTestedMethods += $child->getNumTestedMethods(); } } return $this->numTestedMethods; } public function getNumFunctions() { if ($this->numFunctions == -1) { $this->numFunctions = 0; foreach ($this->children as $child) { $this->numFunctions += $child->getNumFunctions(); } } return $this->numFunctions; } public function getNumTestedFunctions() { if ($this->numTestedFunctions == -1) { $this->numTestedFunctions = 0; foreach ($this->children as $child) { $this->numTestedFunctions += $child->getNumTestedFunctions(); } } return $this->numTestedFunctions; } } getData(); $commonPath = $this->reducePaths($files); $root = new Directory( $commonPath, null ); $this->addItems( $root, $this->buildDirectoryStructure($files), $coverage->getTests(), $coverage->getCacheTokens() ); return $root; } private function addItems(Directory $root, array $items, array $tests, $cacheTokens) { foreach ($items as $key => $value) { if (substr($key, -2) == '/f') { $key = substr($key, 0, -2); if (file_exists($root->getPath() . DIRECTORY_SEPARATOR . $key)) { $root->addFile($key, $value, $tests, $cacheTokens); } } else { $child = $root->addDirectory($key); $this->addItems($child, $value, $tests, $cacheTokens); } } } private function buildDirectoryStructure($files) { $result = []; foreach ($files as $path => $file) { $path = explode('/', $path); $pointer = &$result; $max = count($path); for ($i = 0; $i < $max; $i++) { if ($i == ($max - 1)) { $type = '/f'; } else { $type = ''; } $pointer = &$pointer[$path[$i] . $type]; } $pointer = $file; } return $result; } private function reducePaths(&$files) { if (empty($files)) { return '.'; } $commonPath = ''; $paths = array_keys($files); if (count($files) == 1) { $commonPath = dirname($paths[0]) . '/'; $files[basename($paths[0])] = $files[$paths[0]]; unset($files[$paths[0]]); return $commonPath; } $max = count($paths); for ($i = 0; $i < $max; $i++) { if (strpos($paths[$i], 'phar://') === 0) { $paths[$i] = substr($paths[$i], 7); $paths[$i] = strtr($paths[$i], '/', DIRECTORY_SEPARATOR); } $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]); if (empty($paths[$i][0])) { $paths[$i][0] = DIRECTORY_SEPARATOR; } } $done = false; $max = count($paths); while (!$done) { for ($i = 0; $i < $max - 1; $i++) { if (!isset($paths[$i][0]) || !isset($paths[$i + 1][0]) || $paths[$i][0] != $paths[$i + 1][0]) { $done = true; break; } } if (!$done) { $commonPath .= $paths[0][0]; if ($paths[0][0] != DIRECTORY_SEPARATOR) { $commonPath .= DIRECTORY_SEPARATOR; } for ($i = 0; $i < $max; $i++) { array_shift($paths[$i]); } } } $original = array_keys($files); $max = count($original); for ($i = 0; $i < $max; $i++) { $files[implode('/', $paths[$i])] = $files[$original[$i]]; unset($files[$original[$i]]); } ksort($files); return substr($commonPath, 0, -1); } } name = $name; $this->parent = $parent; } public function getName() { return $this->name; } public function getId() { if ($this->id === null) { $parent = $this->getParent(); if ($parent === null) { $this->id = 'index'; } else { $parentId = $parent->getId(); if ($parentId == 'index') { $this->id = str_replace(':', '_', $this->name); } else { $this->id = $parentId . '/' . $this->name; } } } return $this->id; } public function getPath() { if ($this->path === null) { if ($this->parent === null || $this->parent->getPath() === null || $this->parent->getPath() === false) { $this->path = $this->name; } else { $this->path = $this->parent->getPath() . '/' . $this->name; } } return $this->path; } public function getPathAsArray() { if ($this->pathArray === null) { if ($this->parent === null) { $this->pathArray = []; } else { $this->pathArray = $this->parent->getPathAsArray(); } $this->pathArray[] = $this; } return $this->pathArray; } public function getParent() { return $this->parent; } public function getTestedClassesPercent($asString = true) { return Util::percent( $this->getNumTestedClasses(), $this->getNumClasses(), $asString ); } public function getTestedTraitsPercent($asString = true) { return Util::percent( $this->getNumTestedTraits(), $this->getNumTraits(), $asString ); } public function getTestedClassesAndTraitsPercent($asString = true) { return Util::percent( $this->getNumTestedClassesAndTraits(), $this->getNumClassesAndTraits(), $asString ); } public function getTestedMethodsPercent($asString = true) { return Util::percent( $this->getNumTestedMethods(), $this->getNumMethods(), $asString ); } public function getLineExecutedPercent($asString = true) { return Util::percent( $this->getNumExecutedLines(), $this->getNumExecutableLines(), $asString ); } public function getNumClassesAndTraits() { return $this->getNumClasses() + $this->getNumTraits(); } public function getNumTestedClassesAndTraits() { return $this->getNumTestedClasses() + $this->getNumTestedTraits(); } public function getClassesAndTraits() { return array_merge($this->getClasses(), $this->getTraits()); } abstract public function getClasses(); abstract public function getTraits(); abstract public function getFunctions(); abstract public function getLinesOfCode(); abstract public function getNumExecutableLines(); abstract public function getNumExecutedLines(); abstract public function getNumClasses(); abstract public function getNumTestedClasses(); abstract public function getNumTraits(); abstract public function getNumTestedTraits(); abstract public function getNumMethods(); abstract public function getNumTestedMethods(); abstract public function getNumFunctions(); abstract public function getNumTestedFunctions(); } nodes = $node->getChildNodes(); } public function rewind() { $this->position = 0; } public function valid() { return $this->position < count($this->nodes); } public function key() { return $this->position; } public function current() { return $this->valid() ? $this->nodes[$this->position] : null; } public function next() { $this->position++; } public function getChildren() { return new self( $this->nodes[$this->position] ); } public function hasChildren() { return $this->nodes[$this->position] instanceof Directory; } } coverageData = $coverageData; $this->testData = $testData; $this->cacheTokens = $cacheTokens; $this->calculateStatistics(); } public function count() { return 1; } public function getCoverageData() { return $this->coverageData; } public function getTestData() { return $this->testData; } public function getClasses() { return $this->classes; } public function getTraits() { return $this->traits; } public function getFunctions() { return $this->functions; } public function getLinesOfCode() { return $this->linesOfCode; } public function getNumExecutableLines() { return $this->numExecutableLines; } public function getNumExecutedLines() { return $this->numExecutedLines; } public function getNumClasses() { if ($this->numClasses === null) { $this->numClasses = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numClasses++; continue 2; } } } } return $this->numClasses; } public function getNumTestedClasses() { return $this->numTestedClasses; } public function getNumTraits() { if ($this->numTraits === null) { $this->numTraits = 0; foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numTraits++; continue 2; } } } } return $this->numTraits; } public function getNumTestedTraits() { return $this->numTestedTraits; } public function getNumMethods() { if ($this->numMethods === null) { $this->numMethods = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numMethods++; } } } foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numMethods++; } } } } return $this->numMethods; } public function getNumTestedMethods() { if ($this->numTestedMethods === null) { $this->numTestedMethods = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0 && $method['coverage'] == 100) { $this->numTestedMethods++; } } } foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0 && $method['coverage'] == 100) { $this->numTestedMethods++; } } } } return $this->numTestedMethods; } public function getNumFunctions() { return count($this->functions); } public function getNumTestedFunctions() { if ($this->numTestedFunctions === null) { $this->numTestedFunctions = 0; foreach ($this->functions as $function) { if ($function['executableLines'] > 0 && $function['coverage'] == 100) { $this->numTestedFunctions++; } } } return $this->numTestedFunctions; } protected function calculateStatistics() { $classStack = $functionStack = []; if ($this->cacheTokens) { $tokens = \PHP_Token_Stream_CachingFactory::get($this->getPath()); } else { $tokens = new \PHP_Token_Stream($this->getPath()); } $this->processClasses($tokens); $this->processTraits($tokens); $this->processFunctions($tokens); $this->linesOfCode = $tokens->getLinesOfCode(); unset($tokens); for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) { if (isset($this->startLines[$lineNumber])) { if (isset($this->startLines[$lineNumber]['className'])) { if (isset($currentClass)) { $classStack[] = &$currentClass; } $currentClass = &$this->startLines[$lineNumber]; } elseif (isset($this->startLines[$lineNumber]['traitName'])) { $currentTrait = &$this->startLines[$lineNumber]; } elseif (isset($this->startLines[$lineNumber]['methodName'])) { $currentMethod = &$this->startLines[$lineNumber]; } elseif (isset($this->startLines[$lineNumber]['functionName'])) { if (isset($currentFunction)) { $functionStack[] = &$currentFunction; } $currentFunction = &$this->startLines[$lineNumber]; } } if (isset($this->coverageData[$lineNumber])) { if (isset($currentClass)) { $currentClass['executableLines']++; } if (isset($currentTrait)) { $currentTrait['executableLines']++; } if (isset($currentMethod)) { $currentMethod['executableLines']++; } if (isset($currentFunction)) { $currentFunction['executableLines']++; } $this->numExecutableLines++; if (count($this->coverageData[$lineNumber]) > 0) { if (isset($currentClass)) { $currentClass['executedLines']++; } if (isset($currentTrait)) { $currentTrait['executedLines']++; } if (isset($currentMethod)) { $currentMethod['executedLines']++; } if (isset($currentFunction)) { $currentFunction['executedLines']++; } $this->numExecutedLines++; } } if (isset($this->endLines[$lineNumber])) { if (isset($this->endLines[$lineNumber]['className'])) { unset($currentClass); if ($classStack) { end($classStack); $key = key($classStack); $currentClass = &$classStack[$key]; unset($classStack[$key]); } } elseif (isset($this->endLines[$lineNumber]['traitName'])) { unset($currentTrait); } elseif (isset($this->endLines[$lineNumber]['methodName'])) { unset($currentMethod); } elseif (isset($this->endLines[$lineNumber]['functionName'])) { unset($currentFunction); if ($functionStack) { end($functionStack); $key = key($functionStack); $currentFunction = &$functionStack[$key]; unset($functionStack[$key]); } } } } foreach ($this->traits as &$trait) { foreach ($trait['methods'] as &$method) { if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; } else { $method['coverage'] = 100; } $method['crap'] = $this->crap( $method['ccn'], $method['coverage'] ); $trait['ccn'] += $method['ccn']; } if ($trait['executableLines'] > 0) { $trait['coverage'] = ($trait['executedLines'] / $trait['executableLines']) * 100; if ($trait['coverage'] == 100) { $this->numTestedClasses++; } } else { $trait['coverage'] = 100; } $trait['crap'] = $this->crap( $trait['ccn'], $trait['coverage'] ); } foreach ($this->classes as &$class) { foreach ($class['methods'] as &$method) { if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; } else { $method['coverage'] = 100; } $method['crap'] = $this->crap( $method['ccn'], $method['coverage'] ); $class['ccn'] += $method['ccn']; } if ($class['executableLines'] > 0) { $class['coverage'] = ($class['executedLines'] / $class['executableLines']) * 100; if ($class['coverage'] == 100) { $this->numTestedClasses++; } } else { $class['coverage'] = 100; } $class['crap'] = $this->crap( $class['ccn'], $class['coverage'] ); } } protected function processClasses(\PHP_Token_Stream $tokens) { $classes = $tokens->getClasses(); unset($tokens); $link = $this->getId() . '.html#'; foreach ($classes as $className => $class) { $this->classes[$className] = [ 'className' => $className, 'methods' => [], 'startLine' => $class['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'package' => $class['package'], 'link' => $link . $class['startLine'] ]; $this->startLines[$class['startLine']] = &$this->classes[$className]; $this->endLines[$class['endLine']] = &$this->classes[$className]; foreach ($class['methods'] as $methodName => $method) { $this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link); $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName]; $this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName]; } } } protected function processTraits(\PHP_Token_Stream $tokens) { $traits = $tokens->getTraits(); unset($tokens); $link = $this->getId() . '.html#'; foreach ($traits as $traitName => $trait) { $this->traits[$traitName] = [ 'traitName' => $traitName, 'methods' => [], 'startLine' => $trait['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'package' => $trait['package'], 'link' => $link . $trait['startLine'] ]; $this->startLines[$trait['startLine']] = &$this->traits[$traitName]; $this->endLines[$trait['endLine']] = &$this->traits[$traitName]; foreach ($trait['methods'] as $methodName => $method) { $this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link); $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName]; $this->endLines[$method['endLine']] = &$this->traits[$traitName]['methods'][$methodName]; } } } protected function processFunctions(\PHP_Token_Stream $tokens) { $functions = $tokens->getFunctions(); unset($tokens); $link = $this->getId() . '.html#'; foreach ($functions as $functionName => $function) { $this->functions[$functionName] = [ 'functionName' => $functionName, 'signature' => $function['signature'], 'startLine' => $function['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => $function['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $function['startLine'] ]; $this->startLines[$function['startLine']] = &$this->functions[$functionName]; $this->endLines[$function['endLine']] = &$this->functions[$functionName]; } } protected function crap($ccn, $coverage) { if ($coverage == 0) { return (string) (pow($ccn, 2) + $ccn); } if ($coverage >= 95) { return (string) $ccn; } return sprintf( '%01.2F', pow($ccn, 2) * pow(1 - $coverage / 100, 3) + $ccn ); } private function newMethod($methodName, array $method, $link) { return [ 'methodName' => $methodName, 'visibility' => $method['visibility'], 'signature' => $method['signature'], 'startLine' => $method['startLine'], 'endLine' => $method['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => $method['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $method['startLine'], ]; } } getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->addFileToWhitelist($file); } } public function addFileToWhitelist($filename) { $this->whitelistedFiles[realpath($filename)] = true; } public function addFilesToWhitelist(array $files) { foreach ($files as $file) { $this->addFileToWhitelist($file); } } public function removeDirectoryFromWhitelist($directory, $suffix = '.php', $prefix = '') { $facade = new \File_Iterator_Facade; $files = $facade->getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->removeFileFromWhitelist($file); } } public function removeFileFromWhitelist($filename) { $filename = realpath($filename); unset($this->whitelistedFiles[$filename]); } public function isFile($filename) { if ($filename == '-' || strpos($filename, 'vfs://') === 0 || strpos($filename, 'xdebug://debug-eval') !== false || strpos($filename, 'eval()\'d code') !== false || strpos($filename, 'runtime-created function') !== false || strpos($filename, 'runkit created function') !== false || strpos($filename, 'assert code') !== false || strpos($filename, 'regexp code') !== false) { return false; } return file_exists($filename); } public function isFiltered($filename) { if (!$this->isFile($filename)) { return true; } $filename = realpath($filename); return !isset($this->whitelistedFiles[$filename]); } public function getWhitelist() { return array_keys($this->whitelistedFiles); } public function hasWhitelist() { return !empty($this->whitelistedFiles); } public function getWhitelistedFiles() { return $this->whitelistedFiles; } public function setWhitelistedFiles($whitelistedFiles) { $this->whitelistedFiles = $whitelistedFiles; } } filter(); $output = sprintf( 'setData(%s); $coverage->setTests(%s); $filter = $coverage->filter(); $filter->setWhitelistedFiles(%s); return $coverage;', var_export($coverage->getData(true), 1), var_export($coverage->getTests(), 1), var_export($filter->getWhitelistedFiles(), 1) ); if ($target !== null) { return file_put_contents($target, $output); } else { return $output; } } } loadXML(''); $contextNode = $dom->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'file' )->item(0); parent::__construct($contextNode); $this->setName($name); } private function setName($name) { $this->getContextNode()->setAttribute('name', basename($name)); $this->getContextNode()->setAttribute('path', dirname($name)); } public function asDom() { return $this->getDomDocument(); } public function getFunctionObject($name) { $node = $this->getContextNode()->appendChild( $this->getDomDocument()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'function' ) ); return new Method($node, $name); } public function getClassObject($name) { return $this->getUnitObject('class', $name); } public function getTraitObject($name) { return $this->getUnitObject('trait', $name); } private function getUnitObject($tagName, $name) { $node = $this->getContextNode()->appendChild( $this->getDomDocument()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', $tagName ) ); return new Unit($node, $name); } public function getSource() { $source = $this->getContextNode()->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'source' )->item(0); if (!$source) { $source = $this->getContextNode()->appendChild( $this->getDomDocument()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'source' ) ); } return new Source($source); } } setContextNode($context); } protected function setContextNode(\DOMElement $context) { $this->dom = $context->ownerDocument; $this->contextNode = $context; } public function getDom() { return $this->dom; } protected function getContextNode() { return $this->contextNode; } public function getTotals() { $totalsContainer = $this->getContextNode()->firstChild; if (!$totalsContainer) { $totalsContainer = $this->getContextNode()->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new Totals($totalsContainer); } public function addDirectory($name) { $dirNode = $this->getDom()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'directory' ); $dirNode->setAttribute('name', $name); $this->getContextNode()->appendChild($dirNode); return new Directory($dirNode); } public function addFile($name, $href) { $fileNode = $this->getDom()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'file' ); $fileNode->setAttribute('name', $name); $fileNode->setAttribute('href', $href); $this->getContextNode()->appendChild($fileNode); return new File($fileNode); } } contextNode = $context; $this->setName($name); } private function setName($name) { $this->contextNode->setAttribute('name', $name); } public function setLines($start, $executable, $executed) { $this->contextNode->setAttribute('start', $start); $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); } public function setCrap($crap) { $this->contextNode->setAttribute('crap', $crap); } public function setPackage($full, $package, $sub, $category) { $node = $this->contextNode->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'package' )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'package' ) ); } $node->setAttribute('full', $full); $node->setAttribute('name', $package); $node->setAttribute('sub', $sub); $node->setAttribute('category', $category); } public function setNamespace($namespace) { $node = $this->contextNode->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'namespace' )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'namespace' ) ); } $node->setAttribute('name', $namespace); } public function addMethod($name) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'method' ) ); return new Method($node, $name); } } init(); $this->setProjectSourceDirectory($directory); } private function init() { $dom = new \DOMDocument(); $dom->loadXML(''); $this->setContextNode( $dom->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'project' )->item(0) ); } private function setProjectSourceDirectory($name) { $this->getContextNode()->setAttribute('source', $name); } public function getProjectSourceDirectory() { return $this->getContextNode()->getAttribute('source'); } public function getBuildInformation() { $buildNode = $this->getDom()->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'build' )->item(0); if (!$buildNode) { $buildNode = $this->getDom()->documentElement->appendChild( $this->getDom()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'build' ) ); } return new BuildInformation($buildNode); } public function getTests() { $testsNode = $this->getContextNode()->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'tests' )->item(0); if (!$testsNode) { $testsNode = $this->getContextNode()->appendChild( $this->getDom()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'tests' ) ); } return new Tests($testsNode); } public function asDom() { return $this->getDom(); } } contextNode = $context; $this->writer = new \XMLWriter(); $this->writer->openMemory(); $this->writer->startElementNs(null, $context->nodeName, 'http://schema.phpunit.de/coverage/1.0'); $this->writer->writeAttribute('nr', $line); } public function addTest($test) { if ($this->finalized) { throw new RuntimeException('Coverage Report already finalized'); } $this->writer->startElement('covered'); $this->writer->writeAttribute('by', $test); $this->writer->endElement(); } public function finalize() { $this->writer->endElement(); $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); $fragment->appendXML($this->writer->outputMemory()); $this->contextNode->parentNode->replaceChild( $fragment, $this->contextNode ); $this->finalized = true; } } context = $context; } public function setSourceCode(string $source) { $context = $this->context; $tokens = (new Tokenizer())->parse($source); $srcDom = (new XMLSerializer(new NamespaceUri($context->namespaceURI)))->toDom($tokens); $context->parentNode->replaceChild( $context->ownerDocument->importNode($srcDom->documentElement, true), $context ); } } contextNode = $contextNode; } public function setRuntimeInformation(Runtime $runtime) { $runtimeNode = $this->getNodeByName('runtime'); $runtimeNode->setAttribute('name', $runtime->getName()); $runtimeNode->setAttribute('version', $runtime->getVersion()); $runtimeNode->setAttribute('url', $runtime->getVendorUrl()); $driverNode = $this->getNodeByName('driver'); if ($runtime->isHHVM()) { $driverNode->setAttribute('name', 'hhvm'); $driverNode->setAttribute('version', constant('HHVM_VERSION')); return; } if ($runtime->hasPHPDBGCodeCoverage()) { $driverNode->setAttribute('name', 'phpdbg'); $driverNode->setAttribute('version', constant('PHPDBG_VERSION')); } if ($runtime->hasXdebug()) { $driverNode->setAttribute('name', 'xdebug'); $driverNode->setAttribute('version', phpversion('xdebug')); } } private function getNodeByName($name) { $node = $this->contextNode->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', $name )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', $name ) ); } return $node; } public function setBuildTime(\DateTime $date) { $this->contextNode->setAttribute('time', $date->format('D M j G:i:s T Y')); } public function setGeneratorVersions($phpUnitVersion, $coverageVersion) { $this->contextNode->setAttribute('phpunit', $phpUnitVersion); $this->contextNode->setAttribute('coverage', $coverageVersion); } } contextNode = $context; $this->setName($name); } private function setName($name) { $this->contextNode->setAttribute('name', $name); } public function setSignature($signature) { $this->contextNode->setAttribute('signature', $signature); } public function setLines($start, $end = null) { $this->contextNode->setAttribute('start', $start); if ($end !== null) { $this->contextNode->setAttribute('end', $end); } } public function setTotals($executable, $executed, $coverage) { $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); $this->contextNode->setAttribute('coverage', $coverage); } public function setCrap($crap) { $this->contextNode->setAttribute('crap', $crap); } } phpUnitVersion = $version; } public function process(CodeCoverage $coverage, $target) { if (substr($target, -1, 1) != DIRECTORY_SEPARATOR) { $target .= DIRECTORY_SEPARATOR; } $this->target = $target; $this->initTargetDirectory($target); $report = $coverage->getReport(); $this->project = new Project( $coverage->getReport()->getName() ); $this->setBuildInformation(); $this->processTests($coverage->getTests()); $this->processDirectory($report, $this->project); $this->saveDocument($this->project->asDom(), 'index'); } private function setBuildInformation() { $buildNode = $this->project->getBuildInformation(); $buildNode->setRuntimeInformation(new Runtime()); $buildNode->setBuildTime(\DateTime::createFromFormat('U', $_SERVER['REQUEST_TIME'])); $buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id()); } protected function initTargetDirectory($directory) { if (file_exists($directory)) { if (!is_dir($directory)) { throw new RuntimeException( "'$directory' exists but is not a directory." ); } if (!is_writable($directory)) { throw new RuntimeException( "'$directory' exists but is not writable." ); } } elseif (!@mkdir($directory, 0777, true)) { throw new RuntimeException( "'$directory' could not be created." ); } } private function processDirectory(DirectoryNode $directory, Node $context) { $dirname = $directory->getName(); if ($this->project->getProjectSourceDirectory() === $dirname) { $dirname = '/'; } $dirObject = $context->addDirectory($dirname); $this->setTotals($directory, $dirObject->getTotals()); foreach ($directory->getDirectories() as $node) { $this->processDirectory($node, $dirObject); } foreach ($directory->getFiles() as $node) { $this->processFile($node, $dirObject); } } private function processFile(FileNode $file, Directory $context) { $fileObject = $context->addFile( $file->getName(), $file->getId() . '.xml' ); $this->setTotals($file, $fileObject->getTotals()); $path = substr( $file->getPath(), strlen($this->project->getProjectSourceDirectory()) ); $fileReport = new Report($path); $this->setTotals($file, $fileReport->getTotals()); foreach ($file->getClassesAndTraits() as $unit) { $this->processUnit($unit, $fileReport); } foreach ($file->getFunctions() as $function) { $this->processFunction($function, $fileReport); } foreach ($file->getCoverageData() as $line => $tests) { if (!is_array($tests) || count($tests) === 0) { continue; } $coverage = $fileReport->getLineCoverage($line); foreach ($tests as $test) { $coverage->addTest($test); } $coverage->finalize(); } $fileReport->getSource()->setSourceCode( file_get_contents($file->getPath()) ); $this->saveDocument($fileReport->asDom(), $file->getId()); } private function processUnit($unit, Report $report) { if (isset($unit['className'])) { $unitObject = $report->getClassObject($unit['className']); } else { $unitObject = $report->getTraitObject($unit['traitName']); } $unitObject->setLines( $unit['startLine'], $unit['executableLines'], $unit['executedLines'] ); $unitObject->setCrap($unit['crap']); $unitObject->setPackage( $unit['package']['fullPackage'], $unit['package']['package'], $unit['package']['subpackage'], $unit['package']['category'] ); $unitObject->setNamespace($unit['package']['namespace']); foreach ($unit['methods'] as $method) { $methodObject = $unitObject->addMethod($method['methodName']); $methodObject->setSignature($method['signature']); $methodObject->setLines($method['startLine'], $method['endLine']); $methodObject->setCrap($method['crap']); $methodObject->setTotals( $method['executableLines'], $method['executedLines'], $method['coverage'] ); } } private function processFunction($function, Report $report) { $functionObject = $report->getFunctionObject($function['functionName']); $functionObject->setSignature($function['signature']); $functionObject->setLines($function['startLine']); $functionObject->setCrap($function['crap']); $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage']); } private function processTests(array $tests) { $testsObject = $this->project->getTests(); foreach ($tests as $test => $result) { if ($test == 'UNCOVERED_FILES_FROM_WHITELIST') { continue; } $testsObject->addTest($test, $result); } } private function setTotals(AbstractNode $node, Totals $totals) { $loc = $node->getLinesOfCode(); $totals->setNumLines( $loc['loc'], $loc['cloc'], $loc['ncloc'], $node->getNumExecutableLines(), $node->getNumExecutedLines() ); $totals->setNumClasses( $node->getNumClasses(), $node->getNumTestedClasses() ); $totals->setNumTraits( $node->getNumTraits(), $node->getNumTestedTraits() ); $totals->setNumMethods( $node->getNumMethods(), $node->getNumTestedMethods() ); $totals->setNumFunctions( $node->getNumFunctions(), $node->getNumTestedFunctions() ); } protected function getTargetDirectory() { return $this->target; } protected function saveDocument(\DOMDocument $document, $name) { $filename = sprintf('%s/%s.xml', $this->getTargetDirectory(), $name); $document->formatOutput = true; $document->preserveWhiteSpace = false; $this->initTargetDirectory(dirname($filename)); $document->save($filename); } } container = $container; $dom = $container->ownerDocument; $this->linesNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'lines' ); $this->methodsNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'methods' ); $this->functionsNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'functions' ); $this->classesNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'classes' ); $this->traitsNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'traits' ); $container->appendChild($this->linesNode); $container->appendChild($this->methodsNode); $container->appendChild($this->functionsNode); $container->appendChild($this->classesNode); $container->appendChild($this->traitsNode); } public function getContainer() { return $this->container; } public function setNumLines($loc, $cloc, $ncloc, $executable, $executed) { $this->linesNode->setAttribute('total', $loc); $this->linesNode->setAttribute('comments', $cloc); $this->linesNode->setAttribute('code', $ncloc); $this->linesNode->setAttribute('executable', $executable); $this->linesNode->setAttribute('executed', $executed); $this->linesNode->setAttribute( 'percent', $executable === 0 ? 0 : sprintf('%01.2F', Util::percent($executed, $executable, false)) ); } public function setNumClasses($count, $tested) { $this->classesNode->setAttribute('count', $count); $this->classesNode->setAttribute('tested', $tested); $this->classesNode->setAttribute( 'percent', $count === 0 ? 0 : sprintf('%01.2F', Util::percent($tested, $count, false)) ); } public function setNumTraits($count, $tested) { $this->traitsNode->setAttribute('count', $count); $this->traitsNode->setAttribute('tested', $tested); $this->traitsNode->setAttribute( 'percent', $count === 0 ? 0 : sprintf('%01.2F', Util::percent($tested, $count, false)) ); } public function setNumMethods($count, $tested) { $this->methodsNode->setAttribute('count', $count); $this->methodsNode->setAttribute('tested', $tested); $this->methodsNode->setAttribute( 'percent', $count === 0 ? 0 : sprintf('%01.2F', Util::percent($tested, $count, false)) ); } public function setNumFunctions($count, $tested) { $this->functionsNode->setAttribute('count', $count); $this->functionsNode->setAttribute('tested', $tested); $this->functionsNode->setAttribute( 'percent', $count === 0 ? 0 : sprintf('%01.2F', Util::percent($tested, $count, false)) ); } } 'PASSED', 1 => 'SKIPPED', 2 => 'INCOMPLETE', 3 => 'FAILURE', 4 => 'ERROR', 5 => 'RISKY', 6 => 'WARNING' ]; public function __construct(\DOMElement $context) { $this->contextNode = $context; } public function addTest($test, array $result) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'test' ) ); $node->setAttribute('name', $test); $node->setAttribute('size', $result['size']); $node->setAttribute('result', (int) $result['status']); $node->setAttribute('status', $this->codeMap[(int) $result['status']]); } } dom = $context->ownerDocument; $this->contextNode = $context; } protected function getContextNode() { return $this->contextNode; } protected function getDomDocument() { return $this->dom; } public function getTotals() { $totalsContainer = $this->contextNode->firstChild; if (!$totalsContainer) { $totalsContainer = $this->contextNode->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new Totals($totalsContainer); } public function getLineCoverage($line) { $coverage = $this->contextNode->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'coverage' )->item(0); if (!$coverage) { $coverage = $this->contextNode->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'coverage' ) ); } $lineNode = $coverage->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'line' ) ); return new Coverage($lineNode, $line); } } "\x1b[30;42m", 'yellow' => "\x1b[30;43m", 'red' => "\x1b[37;41m", 'header' => "\x1b[1;37;40m", 'reset' => "\x1b[0m", 'eol' => "\x1b[2K", ]; public function __construct($lowUpperBound = 50, $highLowerBound = 90, $showUncoveredFiles = false, $showOnlySummary = false) { $this->lowUpperBound = $lowUpperBound; $this->highLowerBound = $highLowerBound; $this->showUncoveredFiles = $showUncoveredFiles; $this->showOnlySummary = $showOnlySummary; } public function process(CodeCoverage $coverage, $showColors = false) { $output = PHP_EOL . PHP_EOL; $report = $coverage->getReport(); unset($coverage); $colors = [ 'header' => '', 'classes' => '', 'methods' => '', 'lines' => '', 'reset' => '', 'eol' => '' ]; if ($showColors) { $colors['classes'] = $this->getCoverageColor( $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits() ); $colors['methods'] = $this->getCoverageColor( $report->getNumTestedMethods(), $report->getNumMethods() ); $colors['lines'] = $this->getCoverageColor( $report->getNumExecutedLines(), $report->getNumExecutableLines() ); $colors['reset'] = $this->colors['reset']; $colors['header'] = $this->colors['header']; $colors['eol'] = $this->colors['eol']; } $classes = sprintf( ' Classes: %6s (%d/%d)', Util::percent( $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits(), true ), $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits() ); $methods = sprintf( ' Methods: %6s (%d/%d)', Util::percent( $report->getNumTestedMethods(), $report->getNumMethods(), true ), $report->getNumTestedMethods(), $report->getNumMethods() ); $lines = sprintf( ' Lines: %6s (%d/%d)', Util::percent( $report->getNumExecutedLines(), $report->getNumExecutableLines(), true ), $report->getNumExecutedLines(), $report->getNumExecutableLines() ); $padding = max(array_map('strlen', [$classes, $methods, $lines])); if ($this->showOnlySummary) { $title = 'Code Coverage Report Summary:'; $padding = max($padding, strlen($title)); $output .= $this->format($colors['header'], $padding, $title); } else { $date = date(' Y-m-d H:i:s', $_SERVER['REQUEST_TIME']); $title = 'Code Coverage Report:'; $output .= $this->format($colors['header'], $padding, $title); $output .= $this->format($colors['header'], $padding, $date); $output .= $this->format($colors['header'], $padding, ''); $output .= $this->format($colors['header'], $padding, ' Summary:'); } $output .= $this->format($colors['classes'], $padding, $classes); $output .= $this->format($colors['methods'], $padding, $methods); $output .= $this->format($colors['lines'], $padding, $lines); if ($this->showOnlySummary) { return $output . PHP_EOL; } $classCoverage = []; foreach ($report as $item) { if (!$item instanceof File) { continue; } $classes = $item->getClassesAndTraits(); foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; foreach ($class['methods'] as $method) { if ($method['executableLines'] == 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; if ($method['coverage'] == 100) { $coveredMethods++; } } if (!empty($class['package']['namespace'])) { $namespace = '\\' . $class['package']['namespace'] . '::'; } elseif (!empty($class['package']['fullPackage'])) { $namespace = '@' . $class['package']['fullPackage'] . '::'; } else { $namespace = ''; } $classCoverage[$namespace . $className] = [ 'namespace' => $namespace, 'className ' => $className, 'methodsCovered' => $coveredMethods, 'methodCount' => $classMethods, 'statementsCovered' => $coveredClassStatements, 'statementCount' => $classStatements, ]; } } ksort($classCoverage); $methodColor = ''; $linesColor = ''; $resetColor = ''; foreach ($classCoverage as $fullQualifiedPath => $classInfo) { if ($classInfo['statementsCovered'] != 0 || $this->showUncoveredFiles) { if ($showColors) { $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); $resetColor = $colors['reset']; } $output .= PHP_EOL . $fullQualifiedPath . PHP_EOL . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' ' . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor ; } } return $output . PHP_EOL; } protected function getCoverageColor($numberOfCoveredElements, $totalNumberOfElements) { $coverage = Util::percent( $numberOfCoveredElements, $totalNumberOfElements ); if ($coverage >= $this->highLowerBound) { return $this->colors['green']; } elseif ($coverage > $this->lowUpperBound) { return $this->colors['yellow']; } return $this->colors['red']; } protected function printCoverageCounts($numberOfCoveredElements, $totalNumberOfElements, $precision) { $format = '%' . $precision . 's'; return Util::percent( $numberOfCoveredElements, $totalNumberOfElements, true, true ) . ' (' . sprintf($format, $numberOfCoveredElements) . '/' . sprintf($format, $totalNumberOfElements) . ')'; } private function format($color, $padding, $string) { $reset = $color ? $this->colors['reset'] : ''; return $color . str_pad($string, $padding) . $reset . PHP_EOL; } } formatOutput = true; $xmlCoverage = $xmlDocument->createElement('coverage'); $xmlCoverage->setAttribute('generated', (int) $_SERVER['REQUEST_TIME']); $xmlDocument->appendChild($xmlCoverage); $xmlProject = $xmlDocument->createElement('project'); $xmlProject->setAttribute('timestamp', (int) $_SERVER['REQUEST_TIME']); if (is_string($name)) { $xmlProject->setAttribute('name', $name); } $xmlCoverage->appendChild($xmlProject); $packages = []; $report = $coverage->getReport(); unset($coverage); foreach ($report as $item) { if (!$item instanceof File) { continue; } $xmlFile = $xmlDocument->createElement('file'); $xmlFile->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); $coverage = $item->getCoverageData(); $lines = []; $namespace = 'global'; foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; foreach ($class['methods'] as $methodName => $method) { if ($method['executableLines'] == 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; if ($method['coverage'] == 100) { $coveredMethods++; } $methodCount = 0; foreach (range($method['startLine'], $method['endLine']) as $line) { if (isset($coverage[$line]) && ($coverage[$line] !== null)) { $methodCount = max($methodCount, count($coverage[$line])); } } $lines[$method['startLine']] = [ 'ccn' => $method['ccn'], 'count' => $methodCount, 'crap' => $method['crap'], 'type' => 'method', 'visibility' => $method['visibility'], 'name' => $methodName ]; } if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $xmlClass = $xmlDocument->createElement('class'); $xmlClass->setAttribute('name', $className); $xmlClass->setAttribute('namespace', $namespace); if (!empty($class['package']['fullPackage'])) { $xmlClass->setAttribute( 'fullPackage', $class['package']['fullPackage'] ); } if (!empty($class['package']['category'])) { $xmlClass->setAttribute( 'category', $class['package']['category'] ); } if (!empty($class['package']['package'])) { $xmlClass->setAttribute( 'package', $class['package']['package'] ); } if (!empty($class['package']['subpackage'])) { $xmlClass->setAttribute( 'subpackage', $class['package']['subpackage'] ); } $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('complexity', $class['ccn']); $xmlMetrics->setAttribute('methods', $classMethods); $xmlMetrics->setAttribute('coveredmethods', $coveredMethods); $xmlMetrics->setAttribute('conditionals', 0); $xmlMetrics->setAttribute('coveredconditionals', 0); $xmlMetrics->setAttribute('statements', $classStatements); $xmlMetrics->setAttribute('coveredstatements', $coveredClassStatements); $xmlMetrics->setAttribute('elements', $classMethods + $classStatements ); $xmlMetrics->setAttribute('coveredelements', $coveredMethods + $coveredClassStatements ); $xmlClass->appendChild($xmlMetrics); } foreach ($coverage as $line => $data) { if ($data === null || isset($lines[$line])) { continue; } $lines[$line] = [ 'count' => count($data), 'type' => 'stmt' ]; } ksort($lines); foreach ($lines as $line => $data) { $xmlLine = $xmlDocument->createElement('line'); $xmlLine->setAttribute('num', $line); $xmlLine->setAttribute('type', $data['type']); if (isset($data['name'])) { $xmlLine->setAttribute('name', $data['name']); } if (isset($data['visibility'])) { $xmlLine->setAttribute('visibility', $data['visibility']); } if (isset($data['ccn'])) { $xmlLine->setAttribute('complexity', $data['ccn']); } if (isset($data['crap'])) { $xmlLine->setAttribute('crap', $data['crap']); } $xmlLine->setAttribute('count', $data['count']); $xmlFile->appendChild($xmlLine); } $linesOfCode = $item->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); $xmlMetrics->setAttribute('classes', $item->getNumClassesAndTraits()); $xmlMetrics->setAttribute('methods', $item->getNumMethods()); $xmlMetrics->setAttribute('coveredmethods', $item->getNumTestedMethods()); $xmlMetrics->setAttribute('conditionals', 0); $xmlMetrics->setAttribute('coveredconditionals', 0); $xmlMetrics->setAttribute('statements', $item->getNumExecutableLines()); $xmlMetrics->setAttribute('coveredstatements', $item->getNumExecutedLines()); $xmlMetrics->setAttribute('elements', $item->getNumMethods() + $item->getNumExecutableLines() ); $xmlMetrics->setAttribute('coveredelements', $item->getNumTestedMethods() + $item->getNumExecutedLines() ); $xmlFile->appendChild($xmlMetrics); if ($namespace == 'global') { $xmlProject->appendChild($xmlFile); } else { if (!isset($packages[$namespace])) { $packages[$namespace] = $xmlDocument->createElement( 'package' ); $packages[$namespace]->setAttribute('name', $namespace); $xmlProject->appendChild($packages[$namespace]); } $packages[$namespace]->appendChild($xmlFile); } } $linesOfCode = $report->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('files', count($report)); $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); $xmlMetrics->setAttribute('classes', $report->getNumClassesAndTraits()); $xmlMetrics->setAttribute('methods', $report->getNumMethods()); $xmlMetrics->setAttribute('coveredmethods', $report->getNumTestedMethods()); $xmlMetrics->setAttribute('conditionals', 0); $xmlMetrics->setAttribute('coveredconditionals', 0); $xmlMetrics->setAttribute('statements', $report->getNumExecutableLines()); $xmlMetrics->setAttribute('coveredstatements', $report->getNumExecutedLines()); $xmlMetrics->setAttribute('elements', $report->getNumMethods() + $report->getNumExecutableLines() ); $xmlMetrics->setAttribute('coveredelements', $report->getNumTestedMethods() + $report->getNumExecutedLines() ); $xmlProject->appendChild($xmlMetrics); $buffer = $xmlDocument->saveXML(); if ($target !== null) { if (!is_dir(dirname($target))) { mkdir(dirname($target), 0777, true); } file_put_contents($target, $buffer); } return $buffer; } } templatePath . 'directory.html', '{{', '}}'); $this->setCommonTemplateVariables($template, $node); $items = $this->renderItem($node, true); foreach ($node->getDirectories() as $item) { $items .= $this->renderItem($item); } foreach ($node->getFiles() as $item) { $items .= $this->renderItem($item); } $template->setVar( [ 'id' => $node->getId(), 'items' => $items ] ); $template->renderTo($file); } protected function renderItem(Node $node, $total = false) { $data = [ 'numClasses' => $node->getNumClassesAndTraits(), 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), 'numMethods' => $node->getNumMethods(), 'numTestedMethods' => $node->getNumTestedMethods(), 'linesExecutedPercent' => $node->getLineExecutedPercent(false), 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), 'numExecutedLines' => $node->getNumExecutedLines(), 'numExecutableLines' => $node->getNumExecutableLines(), 'testedMethodsPercent' => $node->getTestedMethodsPercent(false), 'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(), 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent() ]; if ($total) { $data['name'] = 'Total'; } else { if ($node instanceof DirectoryNode) { $data['name'] = sprintf( '%s', $node->getName(), $node->getName() ); $data['icon'] = ' '; } else { $data['name'] = sprintf( '%s', $node->getName(), $node->getName() ); $data['icon'] = ' '; } } return $this->renderItemTemplate( new \Text_Template($this->templatePath . 'directory_item.html', '{{', '}}'), $data ); } } getClassesAndTraits(); $template = new \Text_Template( $this->templatePath . 'dashboard.html', '{{', '}}' ); $this->setCommonTemplateVariables($template, $node); $baseLink = $node->getId() . '/'; $complexity = $this->complexity($classes, $baseLink); $coverageDistribution = $this->coverageDistribution($classes); $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); $projectRisks = $this->projectRisks($classes, $baseLink); $template->setVar( [ 'insufficient_coverage_classes' => $insufficientCoverage['class'], 'insufficient_coverage_methods' => $insufficientCoverage['method'], 'project_risks_classes' => $projectRisks['class'], 'project_risks_methods' => $projectRisks['method'], 'complexity_class' => $complexity['class'], 'complexity_method' => $complexity['method'], 'class_coverage_distribution' => $coverageDistribution['class'], 'method_coverage_distribution' => $coverageDistribution['method'] ] ); $template->renderTo($file); } protected function complexity(array $classes, $baseLink) { $result = ['class' => [], 'method' => []]; foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($className != '*') { $methodName = $className . '::' . $methodName; } $result['method'][] = [ $method['coverage'], $method['ccn'], sprintf( '%s', str_replace($baseLink, '', $method['link']), $methodName ) ]; } $result['class'][] = [ $class['coverage'], $class['ccn'], sprintf( '%s', str_replace($baseLink, '', $class['link']), $className ) ]; } return [ 'class' => json_encode($result['class']), 'method' => json_encode($result['method']) ]; } protected function coverageDistribution(array $classes) { $result = [ 'class' => [ '0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0 ], 'method' => [ '0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0 ] ]; foreach ($classes as $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] == 0) { $result['method']['0%']++; } elseif ($method['coverage'] == 100) { $result['method']['100%']++; } else { $key = floor($method['coverage'] / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['method'][$key]++; } } if ($class['coverage'] == 0) { $result['class']['0%']++; } elseif ($class['coverage'] == 100) { $result['class']['100%']++; } else { $key = floor($class['coverage'] / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['class'][$key]++; } } return [ 'class' => json_encode(array_values($result['class'])), 'method' => json_encode(array_values($result['method'])) ]; } protected function insufficientCoverage(array $classes, $baseLink) { $leastTestedClasses = []; $leastTestedMethods = []; $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] < $this->highLowerBound) { if ($className != '*') { $key = $className . '::' . $methodName; } else { $key = $methodName; } $leastTestedMethods[$key] = $method['coverage']; } } if ($class['coverage'] < $this->highLowerBound) { $leastTestedClasses[$className] = $class['coverage']; } } asort($leastTestedClasses); asort($leastTestedMethods); foreach ($leastTestedClasses as $className => $coverage) { $result['class'] .= sprintf( ' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $coverage ); } foreach ($leastTestedMethods as $methodName => $coverage) { list($class, $method) = explode('::', $methodName); $result['method'] .= sprintf( ' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $coverage ); } return $result; } protected function projectRisks(array $classes, $baseLink) { $classRisks = []; $methodRisks = []; $result = ['class' => '', 'method' => '']; foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] < $this->highLowerBound && $method['ccn'] > 1) { if ($className != '*') { $key = $className . '::' . $methodName; } else { $key = $methodName; } $methodRisks[$key] = $method['crap']; } } if ($class['coverage'] < $this->highLowerBound && $class['ccn'] > count($class['methods'])) { $classRisks[$className] = $class['crap']; } } arsort($classRisks); arsort($methodRisks); foreach ($classRisks as $className => $crap) { $result['class'] .= sprintf( ' %s%d' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $crap ); } foreach ($methodRisks as $methodName => $crap) { list($class, $method) = explode('::', $methodName); $result['method'] .= sprintf( ' %s%d' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $crap ); } return $result; } protected function getActiveBreadcrumb(AbstractNode $node) { return sprintf( '
    • %s
    • ' . "\n" . '
    • (Dashboard)
    • ' . "\n", $node->getName() ); } } body { padding-top: 10px; } .popover { max-width: none; } .glyphicon { margin-right:.25em; } .table-bordered>thead>tr>td { border-bottom-width: 1px; } .table tbody>tr>td, .table thead>tr>td { padding-top: 3px; padding-bottom: 3px; } .table-condensed tbody>tr>td { padding-top: 0; padding-bottom: 0; } .table .progress { margin-bottom: inherit; } .table-borderless th, .table-borderless td { border: 0 !important; } .table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { background-color: #dff0d8; } .table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests { background-color: #c3e3b5; } .table tbody tr.covered-by-small-tests, li.covered-by-small-tests { background-color: #99cb84; } .table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { background-color: #f2dede; } .table tbody td.warning, li.warning, span.warning { background-color: #fcf8e3; } .table tbody td.info { background-color: #d9edf7; } td.big { width: 117px; } td.small { } td.codeLine { font-family: monospace; white-space: pre; } td span.comment { color: #888a85; } td span.default { color: #2e3436; } td span.html { color: #888a85; } td span.keyword { color: #2e3436; font-weight: bold; } pre span.string { color: #2e3436; } span.success, span.warning, span.danger { margin-right: 2px; padding-left: 10px; padding-right: 10px; text-align: center; } #classCoverageDistribution, #classComplexity { height: 200px; width: 475px; } #toplink { position: fixed; left: 5px; bottom: 5px; outline: 0; } svg text { font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #666; fill: #666; } .scrollbox { height:245px; overflow-x:hidden; overflow-y:scroll; } .nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc}/*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} /*# sourceMappingURL=bootstrap.min.css.map */ $(function() { var $window = $(window) , $top_link = $('#toplink') , $body = $('body, html') , offset = $('#code').offset().top , hidePopover = function ($target) { $target.data('popover-hover', false); setTimeout(function () { if (!$target.data('popover-hover')) { $target.popover('hide'); } }, 300); }; $top_link.hide().click(function(event) { event.preventDefault(); $body.animate({scrollTop:0}, 800); }); $window.scroll(function() { if($window.scrollTop() > offset) { $top_link.fadeIn(); } else { $top_link.fadeOut(); } }).scroll(); $('.popin') .popover({trigger: 'manual'}) .on({ 'mouseenter.popover': function () { var $target = $(this); $target.data('popover-hover', true); // popover already displayed if ($target.next('.popover').length) { return; } // show the popover $target.popover('show'); // register mouse events on the popover $target.next('.popover:not(.popover-initialized)') .on({ 'mouseenter': function () { $target.data('popover-hover', true); }, 'mouseleave': function () { hidePopover($target); } }) .addClass('popover-initialized'); }, 'mouseleave.popover': function () { hidePopover($(this)); } }); }); !function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}();/*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under the MIT license */ if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);/* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */ !function(){var a={};a.dev=!1,a.tooltip=a.tooltip||{},a.utils=a.utils||{},a.models=a.models||{},a.charts={},a.logs={},a.dom={},a.dispatch=d3.dispatch("render_start","render_end"),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d&&a?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),a.dev&&(a.dispatch.on("render_start",function(){a.logs.startTime=+new Date}),a.dispatch.on("render_end",function(){a.logs.endTime=+new Date,a.logs.totalTime=a.logs.endTime-a.logs.startTime,a.log("total",a.logs.totalTime)})),a.log=function(){if(a.dev&&window.console&&console.log&&console.log.apply)console.log.apply(console,arguments);else if(a.dev&&window.console&&"function"==typeof console.log&&Function.prototype.bind){var b=Function.prototype.bind.call(console.log,console);b.apply(console,arguments)}return arguments[arguments.length-1]},a.deprecated=function(a,b){console&&console.warn&&console.warn("nvd3 warning: `"+a+"` has been deprecated. ",b||"")},a.render=function(b){b=b||1,a.render.active=!0,a.dispatch.render_start();var c=function(){for(var d,e,f=0;b>f&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g"); x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f); var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
    open:"+b.yAxis.tickFormat()(c.open)+"
    close:"+b.yAxis.tickFormat()(c.close)+"
    high"+b.yAxis.tickFormat()(c.high)+"
    low:"+b.yAxis.tickFormat()(c.low)+"
    "}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
    open:"+b.yAxis.tickFormat()(c.open)+"
    close:"+b.yAxis.tickFormat()(c.close)+"
    high"+b.yAxis.tickFormat()(c.high)+"
    low:"+b.yAxis.tickFormat()(c.low)+"
    "}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D] }if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale(); var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text(""),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text(""),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left }},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b) }},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}();/** * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */ ;(function(window, document) { /*jshint evil:true */ /** version */ var version = '3.7.3'; /** Preset options */ var options = window.html5 || {}; /** Used to skip problem elements */ var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; /** Not all elements can be cloned in IE **/ var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; /** Detect whether the browser supports default html5 styles */ var supportsHtml5Styles; /** Name of the expando, to work with multiple documents or to re-shiv one document */ var expando = '_html5shiv'; /** The id for the the documents expando */ var expanID = 0; /** Cached data for each document */ var expandoData = {}; /** Detect whether the browser supports unknown elements */ var supportsUnknownElements; (function() { try { var a = document.createElement('a'); a.innerHTML = ''; //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles supportsHtml5Styles = ('hidden' in a); supportsUnknownElements = a.childNodes.length == 1 || (function() { // assign a false positive if unable to shiv (document.createElement)('a'); var frag = document.createDocumentFragment(); return ( typeof frag.cloneNode == 'undefined' || typeof frag.createDocumentFragment == 'undefined' || typeof frag.createElement == 'undefined' ); }()); } catch(e) { // assign a false positive if detection fails => unable to shiv supportsHtml5Styles = true; supportsUnknownElements = true; } }()); /*--------------------------------------------------------------------------*/ /** * Creates a style sheet with the given CSS text and adds it to the document. * @private * @param {Document} ownerDocument The document. * @param {String} cssText The CSS text. * @returns {StyleSheet} The style element. */ function addStyleSheet(ownerDocument, cssText) { var p = ownerDocument.createElement('p'), parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; p.innerHTML = 'x'; return parent.insertBefore(p.lastChild, parent.firstChild); } /** * Returns the value of `html5.elements` as an array. * @private * @returns {Array} An array of shived element node names. */ function getElements() { var elements = html5.elements; return typeof elements == 'string' ? elements.split(' ') : elements; } /** * Extends the built-in list of html5 elements * @memberOf html5 * @param {String|Array} newElements whitespace separated list or array of new element names to shiv * @param {Document} ownerDocument The context document. */ function addElements(newElements, ownerDocument) { var elements = html5.elements; if(typeof elements != 'string'){ elements = elements.join(' '); } if(typeof newElements != 'string'){ newElements = newElements.join(' '); } html5.elements = elements +' '+ newElements; shivDocument(ownerDocument); } /** * Returns the data associated to the given document * @private * @param {Document} ownerDocument The document. * @returns {Object} An object of data. */ function getExpandoData(ownerDocument) { var data = expandoData[ownerDocument[expando]]; if (!data) { data = {}; expanID++; ownerDocument[expando] = expanID; expandoData[expanID] = data; } return data; } /** * returns a shived element for the given nodeName and document * @memberOf html5 * @param {String} nodeName name of the element * @param {Document|DocumentFragment} ownerDocument The context document. * @returns {Object} The shived element. */ function createElement(nodeName, ownerDocument, data){ if (!ownerDocument) { ownerDocument = document; } if(supportsUnknownElements){ return ownerDocument.createElement(nodeName); } if (!data) { data = getExpandoData(ownerDocument); } var node; if (data.cache[nodeName]) { node = data.cache[nodeName].cloneNode(); } else if (saveClones.test(nodeName)) { node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); } else { node = data.createElem(nodeName); } // Avoid adding some elements to fragments in IE < 9 because // * Attributes like `name` or `type` cannot be set/changed once an element // is inserted into a document/fragment // * Link elements with `src` attributes that are inaccessible, as with // a 403 response, will cause the tab/window to crash // * Script elements appended to fragments will execute when their `src` // or `text` property is set return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; } /** * returns a shived DocumentFragment for the given document * @memberOf html5 * @param {Document} ownerDocument The context document. * @returns {Object} The shived DocumentFragment. */ function createDocumentFragment(ownerDocument, data){ if (!ownerDocument) { ownerDocument = document; } if(supportsUnknownElements){ return ownerDocument.createDocumentFragment(); } data = data || getExpandoData(ownerDocument); var clone = data.frag.cloneNode(), i = 0, elems = getElements(), l = elems.length; for(;i #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b>16&255)),e.push(String.fromCharCode(f>>8&255)),e.push(String.fromCharCode(255&f)),g=0,f=0),d+=1;return 12===g?(f>>=4,e.push(String.fromCharCode(255&f))):18===g&&(f>>=2,e.push(String.fromCharCode(f>>8&255)),e.push(String.fromCharCode(255&f))),e.join("")},a.btoa=a.btoa||function(a){a=String(a);var c,d,e,f,g,h,i,j=0,k=[];if(/[^\x00-\xFF]/.test(a))throw Error("InvalidCharacterError");for(;j>2,g=(3&c)<<4|d>>4,h=(15&d)<<2|e>>6,i=63&e,j===a.length+2?(h=64,i=64):j===a.length+1&&(i=64),k.push(b.charAt(f),b.charAt(g),b.charAt(h),b.charAt(i));return k.join("")}}(a),Object.prototype.hasOwnProperty||(Object.prototype.hasOwnProperty=function(a){var b=this.__proto__||this.constructor.prototype;return a in this&&(!(a in b)||b[a]!==this[a])}),function(){if("performance"in a==!1&&(a.performance={}),Date.now=Date.now||function(){return(new Date).getTime()},"now"in a.performance==!1){var b=Date.now();performance.timing&&performance.timing.navigationStart&&(b=performance.timing.navigationStart),a.performance.now=function(){return Date.now()-b}}}(),a.requestAnimationFrame||(a.webkitRequestAnimationFrame?!function(a){a.requestAnimationFrame=function(b){return webkitRequestAnimationFrame(function(){b(a.performance.now())})},a.cancelAnimationFrame=webkitCancelAnimationFrame}(a):a.mozRequestAnimationFrame?!function(a){a.requestAnimationFrame=function(b){return mozRequestAnimationFrame(function(){b(a.performance.now())})},a.cancelAnimationFrame=mozCancelAnimationFrame}(a):!function(a){a.requestAnimationFrame=function(b){return a.setTimeout(b,1e3/60)},a.cancelAnimationFrame=a.clearTimeout}(a))}}(this),function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):"object"==typeof exports?exports.Holder=b():a.Holder=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){(function(b){function d(a,b,c,d){var f=e(c.substr(c.lastIndexOf(a.domain)),a);f&&h({mode:null,el:d,flags:f,engineSettings:b})}function e(a,b){var c={theme:B(J.settings.themes.gray,null),stylesheets:b.stylesheets,instanceOptions:b};return a.match(/([\d]+p?)x([\d]+p?)(?:\?|$)/)?f(a,c):g(a,c)}function f(a,b){var c=a.split("?"),d=c[0].split("/");b.holderURL=a;var e=d[1],f=e.match(/([\d]+p?)x([\d]+p?)/);if(!f)return!1;if(b.fluid=-1!==e.indexOf("p"),b.dimensions={width:f[1].replace("p","%"),height:f[2].replace("p","%")},2===c.length){var g=A.parse(c[1]);if(g.bg&&(b.theme.background=(-1===g.bg.indexOf("#")?"#":"")+g.bg),g.fg&&(b.theme.foreground=(-1===g.fg.indexOf("#")?"#":"")+g.fg),g.theme&&b.instanceOptions.themes.hasOwnProperty(g.theme)&&(b.theme=B(b.instanceOptions.themes[g.theme],null)),g.text&&(b.text=g.text),g.textmode&&(b.textmode=g.textmode),g.size&&(b.size=g.size),g.font&&(b.font=g.font),g.align&&(b.align=g.align),b.nowrap=z.truthy(g.nowrap),b.auto=z.truthy(g.auto),z.truthy(g.random)){J.vars.cache.themeKeys=J.vars.cache.themeKeys||Object.keys(b.instanceOptions.themes);var h=J.vars.cache.themeKeys[0|Math.random()*J.vars.cache.themeKeys.length];b.theme=B(b.instanceOptions.themes[h],null)}}return b}function g(a,b){var c=!1,d=String.fromCharCode(11),e=a.replace(/([^\\])\//g,"$1"+d).split(d),f=/%[0-9a-f]{2}/gi,g=b.instanceOptions;b.holderURL=[];for(var h=e.length,i=0;h>i;i++){var j=e[i];if(j.match(f))try{j=decodeURIComponent(j)}catch(k){j=e[i]}var l=!1;if(J.flags.dimensions.match(j))c=!0,b.dimensions=J.flags.dimensions.output(j),l=!0;else if(J.flags.fluid.match(j))c=!0,b.dimensions=J.flags.fluid.output(j),b.fluid=!0,l=!0;else if(J.flags.textmode.match(j))b.textmode=J.flags.textmode.output(j),l=!0;else if(J.flags.colors.match(j)){var m=J.flags.colors.output(j);b.theme=B(b.theme,m),l=!0}else if(g.themes[j])g.themes.hasOwnProperty(j)&&(b.theme=B(g.themes[j],null)),l=!0;else if(J.flags.font.match(j))b.font=J.flags.font.output(j),l=!0;else if(J.flags.auto.match(j))b.auto=!0,l=!0;else if(J.flags.text.match(j))b.text=J.flags.text.output(j),l=!0;else if(J.flags.size.match(j))b.size=J.flags.size.output(j),l=!0;else if(J.flags.random.match(j)){null==J.vars.cache.themeKeys&&(J.vars.cache.themeKeys=Object.keys(g.themes));var n=J.vars.cache.themeKeys[0|Math.random()*J.vars.cache.themeKeys.length];b.theme=B(g.themes[n],null),l=!0}l&&b.holderURL.push(j)}return b.holderURL.unshift(g.domain),b.holderURL=b.holderURL.join("/"),c?b:!1}function h(a){var b=a.mode,c=a.el,d=a.flags,e=a.engineSettings,f=d.dimensions,g=d.theme,h=f.width+"x"+f.height;if(b=null==b?d.fluid?"fluid":"image":b,null!=d.text&&(g.text=d.text,"object"===c.nodeName.toLowerCase())){for(var j=g.text.split("\\n"),k=0;k1){var n,o=0,p=0,q=0;j=new e.Group("line"+q),("left"===a.align||"right"===a.align)&&(m=a.width*(1-2*(1-J.setup.lineWrapRatio)));for(var r=0;r=m||t===!0)&&(b(g,j,o,g.properties.leading),g.add(j),o=0,p+=g.properties.leading,q+=1,j=new e.Group("line"+q),j.y=p),t!==!0&&(i.moveTo(o,0),o+=h.spaceWidth+s.width,j.add(i))}if(b(g,j,o,g.properties.leading),g.add(j),"left"===a.align)g.moveTo(a.width-l,null,null);else if("right"===a.align){for(n in g.children)j=g.children[n],j.moveTo(a.width-j.width,null,null);g.moveTo(0-(a.width-l),null,null)}else{for(n in g.children)j=g.children[n],j.moveTo((g.width-j.width)/2,null,null);g.moveTo((a.width-g.width)/2,null,null)}g.moveTo(null,(a.height-g.height)/2,null),(a.height-g.height)/2<0&&g.moveTo(null,0,null)}else i=new e.Text(a.text),j=new e.Group("line0"),j.add(i),g.add(j),"left"===a.align?g.moveTo(a.width-l,null,null):"right"===a.align?g.moveTo(0-(a.width-l),null,null):g.moveTo((a.width-h.boundingBox.width)/2,null,null),g.moveTo(null,(a.height-h.boundingBox.height)/2,null);return d}function k(a,b,c){var d=parseInt(a,10),e=parseInt(b,10),f=Math.max(d,e),g=Math.min(d,e),h=.8*Math.min(g,f*J.defaults.scale);return Math.round(Math.max(c,h))}function l(a){var b;b=null==a||null==a.nodeType?J.vars.resizableImages:[a];for(var c=0,d=b.length;d>c;c++){var e=b[c];if(e.holderData){var f=e.holderData.flags,g=D(e);if(g){if(!e.holderData.resizeUpdate)continue;if(f.fluid&&f.auto){var h=e.holderData.fluidConfig;switch(h.mode){case"width":g.height=g.width/h.ratio;break;case"height":g.width=g.height*h.ratio}}var j={mode:"image",holderSettings:{dimensions:g,theme:f.theme,flags:f},el:e,engineSettings:e.holderData.engineSettings};"exact"==f.textmode&&(f.exactDimensions=g,j.holderSettings.dimensions=f.dimensions),i(j)}else p(e)}}}function m(a){if(a.holderData){var b=D(a);if(b){var c=a.holderData.flags,d={fluidHeight:"%"==c.dimensions.height.slice(-1),fluidWidth:"%"==c.dimensions.width.slice(-1),mode:null,initialDimensions:b};d.fluidWidth&&!d.fluidHeight?(d.mode="width",d.ratio=d.initialDimensions.width/parseFloat(c.dimensions.height)):!d.fluidWidth&&d.fluidHeight&&(d.mode="height",d.ratio=parseFloat(c.dimensions.width)/d.initialDimensions.height),a.holderData.fluidConfig=d}else p(a)}}function n(){for(var a,c=[],d=Object.keys(J.vars.invisibleImages),e=0,f=d.length;f>e;e++)a=J.vars.invisibleImages[d[e]],D(a)&&"img"==a.nodeName.toLowerCase()&&(c.push(a),delete J.vars.invisibleImages[d[e]]);c.length&&I.run({images:c}),b.requestAnimationFrame(n)}function o(){J.vars.visibilityCheckStarted||(b.requestAnimationFrame(n),J.vars.visibilityCheckStarted=!0)}function p(a){a.holderData.invisibleId||(J.vars.invisibleId+=1,J.vars.invisibleImages["i"+J.vars.invisibleId]=a,a.holderData.invisibleId=J.vars.invisibleId)}function q(a,b){return null==b?document.createElement(a):document.createElementNS(b,a)}function r(a,b){for(var c in b)a.setAttribute(c,b[c])}function s(a,b,c){var d,e;null==a?(a=q("svg",E),d=q("defs",E),e=q("style",E),r(e,{type:"text/css"}),d.appendChild(e),a.appendChild(d)):e=a.querySelector("style"),a.webkitMatchesSelector&&a.setAttribute("xmlns",E);for(var f=0;f=0;h--){var i=g.createProcessingInstruction("xml-stylesheet",'href="'+f[h]+'" rel="stylesheet"');g.insertBefore(i,g.firstChild)}g.removeChild(g.documentElement),e=d.serializeToString(g)}var j=d.serializeToString(a);return j=j.replace(/\&(\#[0-9]{2,}\;)/g,"&$1"),e+j}}function u(){return b.DOMParser?(new DOMParser).parseFromString("","application/xml"):void 0}function v(a){J.vars.debounceTimer||a.call(this),J.vars.debounceTimer&&b.clearTimeout(J.vars.debounceTimer),J.vars.debounceTimer=b.setTimeout(function(){J.vars.debounceTimer=null,a.call(this)},J.setup.debounce)}function w(){v(function(){l(null)})}var x=c(1),y=c(2),z=c(3),A=c(4),B=z.extend,C=z.getNodeArray,D=z.dimensionCheck,E="http://www.w3.org/2000/svg",F=8,G="2.7.1",H="\nCreated with Holder.js "+G+".\nLearn more at http://holderjs.com\n(c) 2012-2015 Ivan Malopinsky - http://imsky.co\n",I={version:G,addTheme:function(a,b){return null!=a&&null!=b&&(J.settings.themes[a]=b),delete J.vars.cache.themeKeys,this},addImage:function(a,b){var c=document.querySelectorAll(b);if(c.length)for(var d=0,e=c.length;e>d;d++){var f=q("img"),g={};g[J.vars.dataAttr]=a,r(f,g),c[d].appendChild(f)}return this},setResizeUpdate:function(a,b){a.holderData&&(a.holderData.resizeUpdate=!!b,a.holderData.resizeUpdate&&l(a))},run:function(a){a=a||{};var c={},f=B(J.settings,a);J.vars.preempted=!0,J.vars.dataAttr=f.dataAttr||J.vars.dataAttr,c.renderer=f.renderer?f.renderer:J.setup.renderer,-1===J.setup.renderers.join(",").indexOf(c.renderer)&&(c.renderer=J.setup.supportsSVG?"svg":J.setup.supportsCanvas?"canvas":"html");var g=C(f.images),i=C(f.bgnodes),j=C(f.stylenodes),k=C(f.objects);c.stylesheets=[],c.svgXMLStylesheet=!0,c.noFontFallback=f.noFontFallback?f.noFontFallback:!1;for(var l=0;l1){c.nodeValue="";for(var u=0;u=0?b:1)}function f(a){v?e(a):w.push(a)}null==document.readyState&&document.addEventListener&&(document.addEventListener("DOMContentLoaded",function y(){document.removeEventListener("DOMContentLoaded",y,!1),document.readyState="complete"},!1),document.readyState="loading");var g=a.document,h=g.documentElement,i="load",j=!1,k="on"+i,l="complete",m="readyState",n="attachEvent",o="detachEvent",p="addEventListener",q="DOMContentLoaded",r="onreadystatechange",s="removeEventListener",t=p in g,u=j,v=j,w=[];if(g[m]===l)e(b);else if(t)g[p](q,c,j),a[p](i,c,j);else{g[n](r,c),a[n](k,c);try{u=null==a.frameElement&&h}catch(x){}u&&u.doScroll&&!function z(){if(!v){try{u.doScroll("left")}catch(a){return e(z,50)}d(),b()}}()}return f.version="1.4.0",f.isReady=function(){return v},f}a.exports="undefined"!=typeof window&&b(window)},function(a,b,c){var d=c(5),e=function(a){function b(a,b){for(var c in b)a[c]=b[c];return a}var c=1,e=d.defclass({constructor:function(a){c++,this.parent=null,this.children={},this.id=c,this.name="n"+c,null!=a&&(this.name=a),this.x=0,this.y=0,this.z=0,this.width=0,this.height=0},resize:function(a,b){null!=a&&(this.width=a),null!=b&&(this.height=b)},moveTo:function(a,b,c){this.x=null!=a?a:this.x,this.y=null!=b?b:this.y,this.z=null!=c?c:this.z},add:function(a){var b=a.name;if(null!=this.children[b])throw"SceneGraph: child with that name already exists: "+b;this.children[b]=a,a.parent=this}}),f=d(e,function(b){this.constructor=function(){b.constructor.call(this,"root"),this.properties=a}}),g=d(e,function(a){function c(c,d){if(a.constructor.call(this,c),this.properties={fill:"#000"},null!=d)b(this.properties,d);else if(null!=c&&"string"!=typeof c)throw"SceneGraph: invalid node name"}this.Group=d.extend(this,{constructor:c,type:"group"}),this.Rect=d.extend(this,{constructor:c,type:"rect"}),this.Text=d.extend(this,{constructor:function(a){c.call(this),this.properties.text=a},type:"text"})}),h=new f;return this.Shape=g,this.root=h,this};a.exports=e},function(a,b){(function(a){b.extend=function(a,b){var c={};for(var d in a)a.hasOwnProperty(d)&&(c[d]=a[d]);if(null!=b)for(var e in b)b.hasOwnProperty(e)&&(c[e]=b[e]);return c},b.cssProps=function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+":"+a[c]);return b.join(";")},b.encodeHtmlEntity=function(a){for(var b=[],c=0,d=a.length-1;d>=0;d--)c=a.charCodeAt(d),b.unshift(c>128?["&#",c,";"].join(""):a[d]);return b.join("")},b.getNodeArray=function(b){var c=null;return"string"==typeof b?c=document.querySelectorAll(b):a.NodeList&&b instanceof a.NodeList?c=b:a.Node&&b instanceof a.Node?c=[b]:a.HTMLCollection&&b instanceof a.HTMLCollection?c=b:b instanceof Array?c=b:null===b&&(c=[]),c},b.imageExists=function(a,b){var c=new Image;c.onerror=function(){b.call(this,!1)},c.onload=function(){b.call(this,!0)},c.src=a},b.decodeHtmlEntity=function(a){return a.replace(/&#(\d+);/g,function(a,b){return String.fromCharCode(b)})},b.dimensionCheck=function(a){var b={height:a.clientHeight,width:a.clientWidth};return b.height&&b.width?b:!1},b.truthy=function(a){return"string"==typeof a?"true"===a||"yes"===a||"1"===a||"on"===a||"✓"===a:!!a}}).call(b,function(){return this}())},function(a,b,c){var d=encodeURIComponent,e=decodeURIComponent,f=c(6),g=c(7),h=/(\w+)\[(\d+)\]/,i=/\w+\.\w+/;b.parse=function(a){if("string"!=typeof a)return{};if(a=f(a),""===a)return{};"?"===a.charAt(0)&&(a=a.slice(1));for(var b={},c=a.split("&"),d=0;d=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" wOFF[\FFTMXm*GDEFt DOS/2E`gkcmaprڭcvt (gaspglyfM}]oheadQ46M/hheaQ$ DhmtxROt `locaS`'0omaxpU jnameU,postWH- Ѻ5webf[xTP=vuvsxc`d``b `b`d`d,`HJxc`fft! B3.a0b ?@u" @aF$% 1 x?hSAiSm߽44,qPK q XE](2 .ԩ] "ED i]DԡZJ\8wwV"FpUԯ.Χ(gK4O n;NR{g`'!PMUHEՠJʫ*Yq9c<U9!QIYׅ-KC+ դU)Q94JYp]Nq9.qyVV n)9[{vVכ־FWb++{>׍a|*gQ,K<'W@Ex̢D&Ud# & x Mx2c 5*.lN/h]GtT(xŽ |յ0>wm#Ye[%Y-YR'rYjD% ,@BKZjHڤ@b-R+nhK~룼$;h^fܹsn{ι ˴0 kb8Fd:%Lה"1AՔ AY>,ؔ#pZ4؟5made ?Ȝy=I:C D(nIxL .1!P'JDtHj@L4Ph' )b)vHX,f1c\'cGu>1 ~t?!xT_q?qBF#L%Dћ"?Yǯj??8>NSkemAYDb4 J);@jP$ 'qh8`;aX6CF*dYc"'?hLV㗌,>ce3eVh =C~xC\((qb@ 4xK&hׁ 4\2DZ6N1|-;j Yu@jѫxi䊧mK ٍDEwq3̷.cAw@4t.gkgr{~Wl~{lW2} 276a2\6oz@$HSH gbtX70Ktc1,7B oLƏ66[,%iZ ,l>TpKSGg\> #A#3Eyk6v;u3!ZI8Mk?8CWq{`C*h>H1_skh)ojOO' !~dXgB(0< kOYxeƧĭ5k =d ϧ> +tC-o Ǫ/_koܶs+fOztpu7-}d9 se \9.H4!0S\ ʱk2"?ip7\2zlްt=W\!KyOXimUnov 6: 2 LZkAA^qCޔ &PaFI0>&Q #FQl> A·q*OȦ_@27l,sf 6p7ܩ?M1vA2]$j";vlk~va0gjzRD:gc6yw%g(þ#'uB#=_@?>FVb0a!aL4tXv:Fh9j^xތz}Wn}7}jΚiHitKSaXEEbbBQ1ftxFȮ -"dqA\~F`6i䁕+ Ԣ^Ȳ}ש׆k&Ĺ<- \;g1>w00v^x 7l#Ot^5+xe.^]׼G8^ m(t1 sbfJ %<4H@e8C,5<(kc5YIA]|ךl6+=HVcbKՋB6i4 #_|&>NvQk#pW=u7HɰR$ [5싙 g %19}&@$&l=1RI}9#ςz??1z&ı_ac|PI[:u;l->k4GYm|Zw }HnR=-B ~m.ِ .Mz^,0%8EG**|sg|ozO֬0sz.WN^ yHk<v3t{8-|' ea~H94xA-@y bT4@0b#]DDljDSio:AgSP z:;-|yH"r {B{\5RLi6AAtM]taRKC!1CgC샂 +1EG!Xzٛnzv@x-#i^x*$)W=O\f[WX~V? `Lei::v4$?=Ra#c]8YFJb&'{%LCE Cf]^$/fߪM;À; 6CXV#X~ F< :vCcyBpLv1Fv#9 /8VF01_K?x>}#G7т\Wp!.@bwɡ+{o#ԍPQҮnī66 cZD(. u;nM}?vtxF{+` ="rPπlDV̶?Z@H䰅][35%O )\^ Z;>Ftf-IzӮ yu1uo<:oa:uqwykk ⋜}0?jvX+}VG$s ?26YI5c$Cfb!X*|F^$p7p55߶6[mjgl>* KO& 8ܝ:ǰokKm~oS-*4E}P/% k:e"1AJCAX8= LŢ>ܱav{|K.3 :\Bxwbeb>1ۿvH?f58 %6$ɲ'pL^HXbpIVqnA8Kg'i!UzSEI5N=hpV?(E Vr?޴7Vڋɿ.O;p 4NRZm.O> MuL'j5`;MtAQܶMyV<` $m)yڳXDa:݁q1JFq15-l\3~X-2pFDe/f!2i:=h{%{t^ *PBͽ]YD3jd *w|GLϽ}ˑk7Ç=06oz*zo1~Jw00SePw%#@BJB %+ ';%!& )Hq 7fqH.!Eǎf,9՚$9 H{~i Z)O|!"D.KQ a2 %2Wɂ\{*B{7,9.'ew U^W&$r9rcGBwll<ʷSQゅh! iѨvJ :Y?#_m4q[ },EA{VПP|Dg?9MId?{)/ /\[ Jҏ[f4G>QK^ m O -7w]„<U3jƏ,:Yq~0/mŵ@CCFq{,Θ쬷ΘQSo lsɿh?A2q`5Z&*X1L5:6ς+O]uej%?ۼ&aW?{2[}W?JbΙk-\b7sIkf&Λfx~nO-9V ~cW"ȗy)b\)2MrWf;MU7'[-c/.ؾuMl&.9) G!!W* 60Cф#qrqOKZOWq,8́/XpTȑg<>¤)[J8o` ;S\S%h~p|J˾F~K=E0NQX*8;D7Q1QC% *Eyy} UG?>I`>'6<+3IVgϮyOQ$WBvH v[Ϗ 2+ 'ø6N߆<ɕ 2S娚9X1\┣df>B~-t>W]pPrZ['+ƌl9]8qC!' @AAOuШ !?M\JMͭfǞ)ߕ=w?AN>¼}jQ<ǏpǠ^(}1+2q F4RiHďITr8^!gm>'ڸhE`s̊ol!(9~ o%#)~ƃj$@ՔLpGOa{߿fé)zؔY<~^cs潺ݴNRURTY%8Ks3qd]^QTb' zx)HFҩPmUZjQ&XƁo<0jYGz]$8c&hyݼwΞ{9^sf߹m[vӣ!(ZAsۧyB8RiԣBg6{UmtyW!bpǮd n/ŷʼ@v/%cxEn:4Y²,yZ-krcH&^ȩC'Ȯ'^T5r)((IJU&#݌! +YM.JEX^|Lw@ھZsgY洺\xԟxyLCyo?eV"_[Q/5Y|qI/\9diEBh$v wOL fpa ,?HgHf2RbL v >USo^1/,ēvcYGmŨ~Amz ?/40yj̸pk2H eERb/"M 75ul[drC&Y͐&I `!>p;J-b--.VM4>Fj/5σt5}>C*<'d?,cdGf2ҁ0w6Lh"fKζp;ǿ϶Pdc1EOi%Ř(DCWV2I)TiMFTz0U S7V mBW6;nYZUzSTg>(hF"޽T뽷R]L۶|Lx[s,'NU|E<4)Rp*vU#g*gjə*=~܃ASēA JHw3@NurbwȀʌx}[`7ZtPlh L.)NU}kq'vFQr׷{ˤS]ZL(@*Sf^+uPe_k#.8ɂ%ՠ,@TKх t`ߑXAD;b|pA7}q2 @Y`~iԬK0jY( R~^ҧ8>=F"˜A[DqvQCX|ZsO \/f.F;kPbdz7ԐeͶ-6bybaWjnh7YLF!4wssFCnh_0> MZ nC *#5/OUN\(3o@[7`Mg8xge;f\y|f֤ޑ]i5q5q&>'353kYꭑ=W7+΋yxIeOYǏs(p6[B/t爁*̠-n: <Ц) +ް~q_}oxt>LV FG@d9[2?2ȳ8笞={fgcsCmre#E>45qo:JX^ioP,xf:/yn9VѥS7=u-\%KϦUv,ⳀZ=vkN*+_.ڊ֞iڃ=w @lmr>Oo,VԲɝz &:'45!9pI 0@I[PU""sInvR>A9t$3/|k8yiE c8E!Q\ۂ} %Af4s*A8A΀>D=5uwjnG z?2Q/I=fH4n]澀YmG"2PEHfvZn<šPiA_q/PDտ $$~%NyhrOdM\-m(@\#ƼNJO>a+ uJ*(%¢FPJW,$))} B\_wV] 0TOCÊQ}5{Ho*;;葞rǨMc54S : M7(kY:z`gp Jstˉv'eG^~iD16dA @'N ֭N.?f…1bzJD V o@7R@6<%IF0mj= [}Nۊ57pyv4@<mЭ9Tp?R70қQG[jzib~/)wC? רa-/Cn.ĕH j63pKrhXIƎj o19 f\~:-ѓK47BY̆y%DC~em@]%rs4T G-Ug>HOpVB]{9&^6|m _PLLI7ǒi "'T }? 4|[Fǭtu/_y;Z?HK0Wzc#)~.rĥ+B&JG0[.ΡrOk;VCoX K۝S߳rt:zX\xmJhxNh5 K`;ydp.Ec4XD<-llip.^p: u/.Y[rl_4kz$~Dq]7/T_<菵4K$Ɩ &w S7|K^7MsMGhw㢴0]?fja5aiЦ6C2no• f=)d^v qNcԎl=u]?;f-E~nv}5%Oջd덿=Z%v  nKu ̓*J#1hu1Hr o}SZu=w;nϗU `FȶEn?߫k&l9YdgA8NSGD09MAK{ހK3݊[_]%W4zۈu9\~n3~zir X3k`Psn=m]ԃJksT9deYN`}/]U#b;Rt,lh*#JB+ (iGx\}~IֳFv@Tu֭J @-LwzYgw`wx-(d٢]F3_XcYmQԃWb-F K5d-0b球—֨T+_Zxcj*`}|x~LF*S*oMتAT1p71?R t>R'"Ey)oP7%$rv QeE+nzlVlFrkt''?R'ZCEIKy ga0^}pE;Kq{T/?i"%1ޒb-Ծqƛ˵+ 8]rIڣV{dȪ͜\AQvOS]0.NX9svb?OE~FPU}o[YKrA̓U%7Dw q b/h AhPbQؓJB8I ?I%=XtO;(PhLd S 'hݱ>|TV?,O"\`7.2>D fmg;-C'u, zA`-ټ$x vck2[xp\cbl΀ihsivaÛM,gĨlMz7JvˑVRWϋNo4(-XB^Cl&Vnnn D4[k6N&}f3YQw@$U$(Ǫo:-ZG#&/} ?N}ƥ7A!MhW>?iXprA١b?uϱι-h6;SB#/@ѿJ !%Q)Dq:{JI^ޑˡPY7UG(h?HmъvREH=N`P)QG9FMSMG@2E$Q $s~TkN"9Ն8cF^"?+G٠ ^*gUlFVxUpoC.XCƵ׵͉qK[k[K(l; ӡn%^Rj,$) 1n.G:Cf(,;ĴR—F_~^;իD;6|/jGGSSGGӎļDzbR/X?Up14u$`[ߜH477I~~Irߙs#6+heW6@wK̸h6, 1C"=meA =@z sls];kklr^"s青>&Մ-[{JiҴ9[ݵȩ-]dޢc An۹g}ꒇ6hTɖ?3s^kLcY 1Zn[bݴE߆դwk3f> fMDՠaD ~}&@5u gnOȢ<'` &bӬ-6;X"d*awYvtLXָkUߩa=HR_@+j2T*£%/͸oƤy 19/7 ~7_o+$DүsIH:r yiF:v(dO":omdM8 ;Z9uʩHCg\K/*ԙg*-I_ERqR'[f?GUAovb A$e]/Կo?|ԐQm4G7G833+ 74z*)$݋JpDNj5pqeDf/>%gW{U:g,nlU\t'%E}͝uCꘒܻߺp}U+^b'o(5gVBIOEm>5yzg}AP-P/ޫ6)x5/t;1p1L9Aܳ|)X]mkFEH/4}:,oLMo6]YM50u[yҫfVh?E-A_i﫝j . 6|5`#Z-svfqӟs͚>w7C{ A]Bz,iH'dv?`E x,mz`F[2avhp%(̒ʂ5Ԧ;Gюh\y";|"ٝʖrxzsPHCTvP$ly}iyhvMCr)#x-.(t%fu€(ۅeUUo pqeˡ啗syi Xk`>X@2P. 2͌>n|,/4} ?A&Jr+ɐCV]{Z0- A= F$+%UZyޗٲR B)wT8(aRΣ*-sr5v !^tZ:/K,'F  9=G<Cu"$-FS2(F 0Q+Xw,]=bh[qBQI ;)"Ō926r?}lV =b[j4AzKkQ?T[%$KQ-l_@l/ &;차Dr?P_dE1~z^I~breufP/պ# E+S\G-R4 SSV俑; *`G*5'dL ~ 5Fhb` ꁜ4[b$~GNAX$~ }[W}_z×6m&~O%j/r&|_Sy<-*Lϛ,JQzͤ𫷣|V|GVW~z  HE YnH4r7P?99ߡ|O-5 %4 dzO/4L_PsT>LQD( J8F+)jCb Mu2Xc8$t}&@Qr-֤U_o6q7P1ˤ+rc6I \ (*v24Uc(A ̣93]z;0'=*,e56Va,qh*P@wȬG/Oj|FIm #Pz;Jwʎ}< z Tt~`ȱGP%;? 5((u# vՊI#9,?Gb4K]Qgԟ]E[ phʯG+`Ęp?@>!}" ҽr=CD5 62ZY? iA T(E UJu;"}պ#LcӗVWO&CIԙu8*烞QaQ^*z(L|Jӏ^fp104~CUx*rV*N9π׳Pūsp_L3Z"}&rO|l~kC/Wj><SxMbSg(]J(Z#x\$OC68-f:{Sҳ蚨o4:)Wb"uiuh~d%BAM sWH.gv%4v+=¿ SGϋjWHWu>[B{[uɶs;laziW߭\zC|\fte&ߕ+Bk/t  CM /@S>Tm G`v`?G(,zb" eAAi7QR<"iX:I܋(aV;4R]}^1vԵ7=p|[Jοeµ{)e#ief0KJq"*F#(GjJFhX#шݍk5ERP΋ ^pCeoe:{6۬5͝sƙ8X K6V[=}V+hͧJlZZ5W;TeV-@HID<͙[)֐l^bXeNN"K]@b?.HH gzXaْA}MOeXHNrڟW;htgttOyu3=*פؿCFGsh9JͽZ-k]L-~hii.49Qr5I,Vݓ^jf_},Q6?5NV ޞˍYٜN%ezqƨ>Z Nt1 a %= yhޙ HJZ? hvrk@mY`^insF\*|Lz!/?)(0 MS4(ȗh{-'ho7cCҞ?6'|ubգ@!bÙf{tz1UA?=@ t%䕉iu[ NiD GT@:p<(cXUm2ϱ7zOM^FϴYUfwGs#t:/~Os]Fݑ((^?L$Sʽ WzT>m'_d:5Lh;H7WgzgZZb3{2d5Jj9c+\vqzDbbƶg "l@צpQBbS Q>+d p%}L!cdwHopx(Tpxp#:dvQ qdAQFdLKmPR pU?l zg-jPbGaR&^q>u8p&Ӯф `MGSܵaoWܛZaâٟݰV5Rs2NX qGB OKg BW)Sg\ӡl]z<߲o-_- AKMqӭ!æSigy۰]K;ST'kPqee7cZT{~*7b\H?jٵl3P оwT2jY;)l DueytOTjöUHXgɬ,WϢ^u![]vF| QGh`(# R'5XDQqM6gc'bu:'H( ?yյ6~.e[n *UyZst9R!GMM$xz$]{L<}4JZ~MVՕhy >@u +]2FqO8jѥWCQqrw.䄫ޥ\_y\On)IKGRHŁqI. d+u@ϴ kŤ}9Tv6*xge7?ì}S-AU OMlJ pժݧYwhi6\fAZc,rjFTMj8kO51TqW_n`7%KWsd0:`OXs$4?:SI1W-Pr}² 9.&P^f 8(WI``@5a}ziV pPԽ+:d\j"=aj)W$q{͜p)V|7hj$L֡9\ځn[ k{lG.m m~TEbȭm` wnyP&:PLJY_pNWzVS׃]7Ed%i癬| EWM7r HB6`UGZ 9N2l2ɅHY(ŗiwݓ[`cZR;Yz=TrvH9c. ֲG6*p΅'[:/ҪXCYхMt-']n,{@ cObIN.xN F9뛝NK[Xr=Wm ݏƦY+?sJgXuP%ȗV^[ W;W xvi/XS3ȼ2ԩZ f2/y?8M@Q*˄CXk?MzTy?ZYu׳)]͕1-a7j~ .d  'VztXK2k̹d?zzK.>,BZ`q'kHqy5j>a\C#H;#p7l4} IR7ފ0$=V#_.vs{g>h!Ab/p7=zmi%͟3)^Oj<_UNY63dsIr8EjU* 33|v ;OB@,,\cwd}6k.ukF9'26D]exGJK.׽}S$@ t";2ɩ*41_x7QbjX9Q;#{9eI -奐br B<9dpzIVQ:l+si #=T+R(MDC$ a̱ ONgj19gqXk}FdcG,&..^ɷwwc>E_]3U|t{Jf窂u_.\*W=}lNo+^Ṿ vP>~sTjWz~_ogS}-DTd -TAaYf3,PATcm ռ4g}mE$BwŪ8>9JW⁩O/9PJCXA{,@c,tEJTj98Q& HPl~K%ƞ1ѻ -eD zxNXuz.9}Mc&:Z5ә8% յսmomCB:l8~ܦEjTYHYvnV^IN]]ŽCXkg#s cSB$Ý=$k}cG&/z}_v6<7IVGGg*l\RXST)šE%Yu~Q~>XЅ`9Wk*@_ՊpM]0*%a3X팁KM|{FԔ 췾d7[nlͬD@m8e cż#gHdd@~.jllɛeRcxE(( Km¼GXA7S@[l.%գnMDs]n_Q 5i?zGTG3T@e i,r O2<l+/,%m ۚXn|E]lí[m<|#z+5 7&\5S-{AE^tK M^rq]FmC%2vJ)W-}OM"`9l+=%"T'8zH3QҐѩYP~VزNi 7ۛ ?w1xc`d```d?oAePBYt?;"@.Hc xc`d`` &]aA_x}SJAS<` b)6 >@D"X\o!ι{,_oggg #JVYp>uC4&*<=$g9W@.0q- ;:pt"HUe5 Vg([Ax9!޴EMߗ4N&ӞwjtԞeσLp>w>Gpfz`|^aż>)o oMg+RmRq,RJ1XTN7t{IE\F8U mb:fN&j9Yxc``ЂM /^0Kؘژ0=avcca>bĒIJk ."/ I888qqpnǥ5w)^-8 ||||[5? JPKLpPa) "Z"WDmDWc3K O~/cLuNN+9K8;99/p>"k676-nܷ0h8)iʋK+s9@.xڭNAwh /"TD#J$rqr|!'O3XFާ0wY 1fg;73;3xE0C q=qX4GA$x ZB8ڃ Dw!IaSX w.0?oN؍gڍ@\A`sb k`sݡ},0Ya DȵȵMyFMvYdS20~>/qJG i<#c0C~G9ee Kvв[ڷ{&V(Ө1j1MZqr7,gKܥX0QY{ MYжz=a:[jEݢ BZZ=ns`+ȍxmUSgFB]9I$uw-J;mPwwwwwwwwlޕ]<3)e׿7R^ VV_@$zГ^З~g`0m[czf`(3233 23s2s32  eD*954XXeXX14i++ kk [[ ۲3Qfvd ;1qgg& nLdOboa_c@`PpHhXxNDNdNarsgrgsrsrs rsWrWs rs7r7s rswrwsrOO // oo __ ?? f,˺eݳYϬW;MelP68s䘉GE{RαM 7nܺp;ڛZ[ݛƵ? ѵֵykx~yj?\3V+wE5=QMjzTӣ(vN؉k/셽d/Kd/Kdbbbbbbbbbbjjjjjjjjjj/r{^n/+v ;NaS)ԼffffffffnnnnnnnnnnaaaaaaaChQN-ܩ?C?C?C?C?݇C}>t݇C}C?C?C?C?vNjHMp[qn???????>>=<<<<<:::::::U>::::::::=;;;;;;;;;;;;}VhSoTP NAMLP',(GLYPHICONS HalflingsRegularxVersion 1.009;PS 001.009;hotconv 1.0.70;makeotf.lib2.5.583298GLYPHICONS Halflings RegularBSGPMMF٣(uʌ<0DB/X N CC^ rmR2skPJ"5+glW*iW/E4#ԣU~fUDĹJ1/!/s7k(hN8od$yq19@-HGS"Fjؠ6C3&W51BaQaRU/{*=@dh$1Tۗnc+cA Zɀ@Qcal2>Km' CHMĬfBX,Ype U*Ҕz miO1nE. hx!aC XTV‹ R%|IHP5"bN=r/_R_ %҄uzҘ52ġP)F7SqF{nia@Ds;}9⬥?ź R{Tk;޵ǜU\NZQ-^s7f 0S3A _n`W7Ppi!g/_pZ-=ץ~WZ#/4 KF` z0| Dѵ&däIÏ;M{'omm I !wi9|H:ۧ{~qO, L]&J09/9&Y 蓰{;'3`e@vHyDZ$3Dx28 W Cx5xwB`$C$'ElyhԀ DJ $(pQAA܉A@'$ hp0V0 `se$4$"t2=f4A{Tk0|rH`L&sh]A<`R'!1N;_t3# V *veF`E O${)W=p:F`22ړC^.ćG<.pNe2ִ+Ysl:˼ ܫu5tu^86ȄTmyQ%u~%~1rҘawߚ^_ZZa0!N`. uqYB\ᨀ[e:@J'Eہ,3ubj@pfeW9( ޅ=lG7gj SM609OˑlBa݁ <Bՙ(VRApf^+g9qMt]تpEr@]@VkV ud^X R@?EY2]#Ǽ4JK'dPC|mmn#$+48u'e&[n[L%{BCDL:^!bƙ:&g3-3ub iLZڂWFSId6.k5Pl77UzT:NN.")['|U"AIvwptdk9嫫9nDmq7I|6Kbc]MBABȪ_JT q  6@Fhd`GT:M7'L,IhFP ~j $¡„ 3hA-S^چ-%qe~Qqln"i&Qe?FlK"As(3Y;"Let'RzM1 0{=) K%$C 9M4c EotjVGD)l8,\w !%$3t TBzҴ iUJ[xgdBr$!eq"J> )\~3(^ R€8#>bHG'7_ fӫcκtDoAA߃(qB<``VΫ֘*buP4v@+.Qԥ$V@C0 RܐP[z:XH#e s>?EWO>@I$|si ES)0A?9ab,@K̩o&Q% ϞLu+ +H|Ɛ?NK4CnPt 'OT.j5Ĵ8vw֜I&+`yScaO[#gQd[KI矗`ČLP # )27aTi@c\ސ 0nCpߖ運4͵x*RzYbT[\kUvHʈqp঄IIŗ) bB XPNtz 2 I== ;}bqjiކa#" >11Ap1POOuxQ Fϲ(h݄O'MDxLK$ȵh& 14SirHJPtDM;rM+ *ؗ5u2$f3K %ѳb (@,2f,~"7R;E;HX(42Z'Tۿ2J+^!#oY~4-׃GW*!A0&8f{`W=DP8'= R g}iP>#4EBRY^4eN8V,[BĨD#X],LBsNC> +o^x jC.4Ya_{eA2=r+9POA!! }YPJeGn%x1/}RgHa ^3- 5 |qSaWK{ 1al`I1 Qf_yyCZ)L3X] W6@DMT<.uGK8DsбWr\7Z\V"ISd>CUjeD 3MtWcPӉ6#3QnቩJ\7#磱`؀K lV6 &T ~l. @61`! ` wYk/a0A¹ԁYhdxk:fEao̟^<IwYgq7s[ -y1ع5aMKאRBYFq}8*Nt'.YbZvK (]&ɜ(ՙ2:0 oΏхPKiBH4UX,[$ 0mXش f50VR 8%ާDtUs`-BPzPsvI8z-t1DiB "˶YTJ .?07jLN[2tĮ̎ #6?E׻:ɞY;A&qSIR)ss 9*x0Bj)mHAhyЏhMm&4Ŋ4 gV&tYOCS0Yd7MvNj)wA(o "͢[ E`7ezď-Q]6+Bca@^I:һ=sSnc 6 OB4LGpBq/>O pwj A*@JC[h&3B Qbϩ8 :%f~v/lS00a"B8(f uGoǚgyt_y~͔ %mL !I$Xt0~ePz]Ug Н=_?.j#+`li BM5 őGp7a ֒%Y[UG9@\bDY{{ED0 $Q+FvC`ݨ3Q E\uC9![$l 6DoDgG*+X!%#Cq ?8ZUB)U@opgީZq89|uccAќW;@">Ph_9}.6V/O:3}ZS {:~ykcO6;OB=bV. Rk o ^GV= }oI"+ ]wFzϷ`<30h3]Rf859s`KM8 XUq<\ZOssM&j&  .%PBL~^Gˈ3pD:Z<\ǠiW̆"(:zX~0PG]8RQMNTqfW~!0R%Ց0xvGFy/F-wu/*+ \8@6c<L;c[º nr QS'oQuT{qҐ_ͿSdA*ð:m8Yuz2PB Hh`lkpLLh cEb6eۏҋ ?!>| *=VK@rx0G`%ryr[6Y37 f**n%9df11ޢځ^'] Rq.,^%l e#wWs56!=!q[ %Ԯ]5^:m5)?V b|u7fw,:Ye R% [ o gFAzFPx{dxíw8ٔ{{L> d2CLL,L,(mS$=|%֝lu& ą83 N Xx \VnJ[)Iw/鹻 |GźYDH*Sp60cJ2@W%Ѧc_^$#*:G6n>D;~`9hXB UJB_вˈ%w'$v|#T<68KMϑ-5U+'B ĪNbJOv'|+*Mk(d }C˱@q&aR%} !VЃs3w2a2awHz/Q0F ]~;ä NDP mK3xke_ S!V&=v_PL9؃Yi NU_)J69f*S  17F|BR$y,Ʊ.&=uqsODBR=ɳeؽɇBH 2lu'h7^#S)Xi2..Pe/@FK$](%|2Y1pC8tI11N//+\pjdWmI=߽YZxMЉP81/ JG^U ,Pd1O^ypql2h$jvI%]V .'[+WU8[D,߻-=[ O wE)3J&dقݶR¡S\. 5J$I&oHȳ~ lz> Ux/Hu;?Gt{?;TH L|F8}{p:2t͆aѧp65Y"LD.rVS_ k]n&Hz~9æ p $4ق'{&M\ΰч!qi (.h' B T|{I6cL.빍iI꫿\!;g`1 j%C o3*60E؎]t.-%0 YK_nft] *VFCtJT+\WZ8gF^ ޞf 5I=#6.@2z;W`B/ęQghjyJNAX3, K66ڲM0T@O{4kj|"ftџۄU<-a5b)^R8:ilKa6@!]buvΏ$ oUœ~:.Lte JξP l$S[z~Rq39钺9Q/m"%ʤ7 5MKL鑧"IߏG XTގXLFݧV jp^/Mgۻ{w *9Oʈ<"aAq.M2@mp^'wߕmkxO8 $[&|YZy`2_|%r/J?QṈl3ÞKE$wvCh a@U1M%0?1* $GZ{!|ʿ$ە-٪Ev;͓:`Bl˸쌧ɬoQ0&,F?^s,ch˕$Ecl0w`⏺ň@/r^l8cT3k@JݔuP&ʪNdJjTKi *uX{tj~ɡ}i\BKenȵ|N u#]@lCZ$iPa㸩t04y20 s֪,Au!QBϖ^@Vsɑ\Za7쾉ш6-TrU u~1HJ(<αbRԖqi J?eG *jVħ ":Y);-Fd!HG~ux cb6m)&;0dU?8X~12ۼtIx5{(z '[ŃkZЅi,b1̇`(mHNeK/ [(#QGduT^m%!(7KgP=hϕkɐU+.[eC"GDΨ<*Ř 9&܂?)\<&Ŏ5 LJu@Y,냲ھ_w0^17p޻*>D8_)$UźR!jOF>{ t,-bP,m`D"/zA ͔إQZG&U]xejxLwv~=)@B6?!;53/ps@tOZS7ؙnlxZ?Zj a{6L41 2Qi&֥l]o=7ļ ofЖr MEV@H/aD٦HlK5)ŒZ OE3IG'г;D'zl(E$.ٜ-W R'\w+)w3꺾 @%R).~9;].šg+)%ȝk҉^NW>b1z:soD K2w[|>9vWMFu`axchիU`*ʆe]OV'6xd?H]_rA+zdFH ʋ<ǴkUsFzaH9-gvb=L/E).x9j%B)$AB t b.bAEZRbH(Jya9Wj0fF'Xz $DQ6q` o i={#4FYH@J3 3i~tYТhkHP17YD"pĦ;'16fpu>FoDQin̒- @P# hj ނŀfC 7°T5HVXpklĭ]yXr)?ͺBNJ B#9e&&_0=pZ6h) ̗a b=(p);.N,W^ *hԺCm}E7i6aIvͲxp*Ac#4N&`)ĉHWey7jloEh_n3 jp?4p2WE'kT_ &!ȖjVlHӻ_kɚʳaY s@[G"bYLܫXi Cq8&zVaY{#I@2m!d[1 AƢnKeם/>dmuX:xʷ\pNl+H+ctSǶC[~3e}6 \,Ʉ|Yݧv]'|&M2 ddsx-((76aXm=ӊQ<$Q†\ qiH阇i'i$"{S*VwF/tfQCWUZ{S;Nx}H&* 9׸qU1 a`(M-aG}n̽0 pmcn ɘ_\l} 9FvHþkJZNO mZQҤ aSf )QC+2 d[ H"t* c*bڢq,#S#u'Ҭ:4asCDMF|ɸm_1L]Y\*X>tgDd@&[)8;<{8<+VG\H^aae-4sJA \ hM[\`#pD5Z97g;BWmqTXX%0v&]E 4]FIJ&S_4R0D+meY gO+M{03v'ͅft:;ر Nn\ǔ^,)1laBZZ[  ZSUYh߆wS\/*?zQЋ`X4gr[CWG.Y0Q|RԃE[wy),ш$NK@c/b -#ZI G$ƗtmH#)XwPZAD|S ofTH)>M1b 7ɆSuq jK4[s xL Ǣ]5 !M!AdƧN><:ǻZ(8)e /W| bDDŃtT7rur0Ң`ܴh5 5S}4hrvalc!ZjB]xDbTxzYS6_)op>#@PS*bS\q ƋxYfQ><" Y6IEr_7Ұ VH!IrEL6!Nq"'daqMvA% v n.;A/2ʲa8D$GWv#̏ 9k'o؟o@ (]gk+}/ (nqK(f Ɵиp23YwpDdGq2$}KӯA"E&Ntg'Nes!Ю4qo}쿝S,ojr/s TMT&Qf\12h'&ctN'Tx7]2 ;G ʅ|T++:%/ 1T  ˀ<4͔˗ ,0~!WO' :suҦن(^ﮎ )7fmlҹ1ūtZh L0 6X"J҂ 49 ֩B}ԭ``Ӓ #Jn_F H|$OK=œi17o-Hqp[ɫ%%:Ɉi3۠G CLL4S:dBj|pYSDP>pv5KLe{t0yEND$*;z5NBIgn.N|׶nRaSZJcH mXek;_ 6,yb0#ZA e|wG U1lLD7ÄVqt[xuEQULPBlZSh.1Q0Uٱ8Rip;{H#GON!?t>Q |pkq!gT,j2sǍ4툊tjnƛ/IOE!ˋnF4M&1x$ew+vS  bm]e%8 P !s_06)Q2JB [t9'Ԝ,[fÆג]BB@r&Bs|Q gOC1J D&LJiC`A^#X8tH?daĖTSTaH0@U)^e}Jb7%ܔ%:ƿ@M+ysqL Y00ÔGD >ĩAW 2I:F 32ʠq:6S]K"g[ ϑHB5VEqLJX{CB!PIq9Llxʪ7>֤]@!@9H!pə$ ?)܎l/"́+@`}}:\ 8zQgS+򒤿C}R:HUF\Xg/AZ%c1wlETwX ZNhyf2D ø&vLq47z\iJyJ-kN3 -sJ5)V0N0d\ӛd0d-E[mf\UmxCR<(`ѕp4^!hQ `!l ~ƙ:JɠlW9˸ZXB=l)`jeVJUG!s1?Ƽ3Ê.}bIa6ʕ t?SxZJ'p i,.R2T`5-R BxrWH JPe#Bb|-[PEh‹(5Sfr/]IƊ dE#OS39ӻ]eۮɹ.9_beM9b#e(- 0Ra9"U,%~X܀z۽{'6[@t[W%* .d'vR {h!AedCE}x=E[|B$7J* B- ,=k7[_-I J5e̶{ ( ;WMw`~pAz 8f))(@ Īم<.a%N n@bz>%T*?lgbd<ĵw9Na8;<^*%y:tDҕZ<@0q4l\ 1`/$IJ ғsN);:A;)$ו Wwy%KrIv\bV\nd{6tv/~*O 7U>8rAC<jE-j牷xs)D1Ì/qp**̸$ّ,  Bȼpk MhpK7U]h&-$鎻Y;q6wzW˄֭AhD^R"s5fw +Q&/9ȂwNbz{Y> ]NEc,ߞ# BF:0/-EȾŒ׃F\I{tAZCORuk i)ytkdN&vA P{P'>xƆ`.%,;:Կ:aFoTQ}v#ףQk's~z5hMQʒY>CʍiUNF#J0uC8k! fv {E/IKIE> pyde ʾ=z:@7J|5g8x 3O 3H1؄F.yfzWIM j[.w%i?҆Uf|}@+[8k7CxSEOޯp$Q+:<]K3T-y[Nz;y-HZY^.M*'h8A.N2rLB 7:Or}CS˚S9Jq#WI}*8D!# g#Y>8` В ?a2H,^'?^nhOƒi<Ya2+6aFaMG-Gkè1TbL `*ـVX *xe§֊Z*c`VSbJU*6TK@zqPhg*ߔU(QU49L cM*TR!R,BȅE*C|TzpF@4*텰جXbL.T2y`Upb T,%@` #?@tGLŞS)ÿztϲFy׎ 14Lhfe(.)pK@\ Xe@TbvhD&0-IbD d@ZD1@ DyѧCN| 94Ӛ#Nc l;, `cX@(2$0 "@- $B@<$А8p7C b(@ PA@F 0tGORIJITySMW52\ToRKV0Ȏ( -$ !6wHGO r~e~/]V~/P~7SzKFv`;`9v# JBN,ӭ'`'`\LTApBs)r! ( i`wOF2Fl\F M?FFTM `r $e6$t 0 "Q?webfe5옏@? t,3+2q FYO&>bm5ZH$Y{H jd Չ %٧y"+@]e{vNc)n?~?萤h_&iѝ?>^K v-cۍ12Ky,'n(3EwiB& Tlh0M҆dYrﲬnti]yurVXsjgMnәHW r2>iT`V7R(+o6'cB4ι㿚T ]a[Qd<3wq8,rTI80>E?*E痦#7'S ocʷ_7&#*+)+4aA6cy٣f(bF$;{ YA1vP-tG"Cf- WԙuKְK#*K< (Z`٫ [%YT{%Ɋ$s{oջvt"p4`ߩϤ}o `'ne> G5sz_N PKӦvmU ɾ{z"3`l W#Ԑ^@+,ckoAOpnuzzJ)Υ1}O=xR`J`qUs/+kv1xljlEl\nDƶVjg{Zdz7 5!xm5o[u&1ڂHBkAqrR (\gh7Ҋy=HZUPh$8RgzgͭN:1u$܅>R]"f7 K^'3+E/^YU5]NB.ʋ8+͏8,|{M|Aua|a˅՝% lKGP,Nukc8mX@d̘?Y&{?P(G]Or-\LF9,&y8r3ܟ?p>~sDz1?\U5q=tzԒ&Znj%mM"}tkDwh-=mB76&:һqt" 1:Еu;"K_/Jdc0l0'^B8VCzg[ ;d Ybȃuu;@*}y| .'C>\g=9VŐ[o|g^ >d 9 *E|A*M[[*mOQz?Pn?R)YoT&[U*5S MB [ oYDh{,}1f?NN ]O/^;\J BEsJrĚ'g/B%o Cn7:|yKt&$s|wP\i]$Z@+ Հ90x]r%+RUEm+ܰ;wu9/I77զQlu\yWN)8ܰvY*umm( fEG8 j#IRz #q߷ )Y$ Лc_%m-{!0-` ;公hyV]Hv! ta\K[1{"j 6@3T0%Θ"ԙZIGS.ΣpӬS1eٓ؛ Yv8d\BlSR)ӆ {Iӆ%>0Ўڦ\'cg2%4QD 0͒3B"MՎ&ۊhIڧRgME I(5UD] }b8$8>X h"l΀j.%ۀHH- Iݸ#1C4Y7YݖV o>P]6O47f ~AJdYF€.oy) 8l 22e1H[t@!ȅ 2\@5ٓ%Zkޒa@.`n3OFR(󅥶ZkLkF HWjY I5*6eSbk.5F,.N0ԙ|V||~N( 4],Jp|~xeA5/ڻSvy?'_v|rXHQēB@= XB94TBBcHP+_YH#$`FB;+BPR4̼ t:t"ZEJ^!XǓq4_dTW(5܀IUŇAz@U6n.WGXHRK&'swMjʎ<3)`#F@  F Ԣvob$x +u&}|X&[٪8F-E&/>/G.az^/})'x$O=<zoA9M؝&~3r3g'8ң\-MDzk5A G9|1-! 87[,mRu|57 =X,aJ^tN4\fЄ]AzH^7F&k"LU>}>rBX(ۂT% JdhKPKTFaA3HHC[r;ad54 lLkjG{8h~ fR@9wB0 zS'a7@@Nƹlbj3hNXF/es'DsQjw}Jz^:V.:ڋ{ͼ(ȲBɦx<Db#"S{PHuN/{r6;wUsPО p8+6g_2lΡ6H džH: dBtGNmx@j |{s9=wR/oDJs5z>;'xEq^r^=G?9AA_K%Dɮ:uikjkIeG՝#*)jm|t}`JZ؈H=4{g߁)qXMA,H71V"o,Y#hݨS_;a_ԗZ^cn4HE?} ȝ٤=}BWvުUehGF;@2S@f n2#fY:]JyH]-G׌wgv'|0e _7Ґn+fٸY<( ?y%wm+j&&!c^u'b&hm6¤*2 ?AIƲ5FWؙ[ƜBUzIE!m:xheǮnz|]% mrUFگ1 };!n F&gP;&$$F).tBQ3(C=Xes;iي@~NΡE SRh\BeobTnΒju g@'qQ딎nx.u6bVU& ];!C_  5*zɺmRQuqPZ0}mn^nOrT:U'h0nZp^R|DF_b\@mDE8{oGM᠜q}Sd C,iܚE/Ë[d8],MCI_u,]Vc"pg@`"y),;B^el2'.(Ęy>-|hw;jՍiԽ_o|!@)ɢ=̌SPz*!z})|ƧT}jEtCZný*՞4ۆ׽[ 9Юݓz`Wmeo|j8j59@.EV/ZW@|f_\"${v/;a:Sei3TG*]ơ/h2C32$1}DNXt?Fϝ~n,Pj9.>ף{ 9EN-v|3hCиE XT;P$=J-gݕigz~q(A<:h193N̽Q}CLWߧ׎~ b"|4u}cy62[ \d,ҎճbkD%0Tx{=;Է(i LS13Nh/6?'E^~P{sZZKĞB{Dt&z)Uoa5Q3ȗr~ F]$<tm(} MB@[GxFh8#},#u Laz(Qh4%xm`Uչ.Ev1a4_'/[d{FxI59 D<&8VEFg 芘#I䟍2S_]QqAn_Q>bޘ4g-0&E#ci8 vR/4rP7KsOWN3ՏvE\bqQ5ZڽVy5]h/ i)-/kNю#e)"P {KSQx>a&, _g-mc<n]Ч-52cz 7d PzVOPvfR Rఓ9Z -dC`,at=k?v4#P Bإ/[s.-bH)ɺz '}׶w!rXZ .:Vn;->: 6rUcs4kVW{#5ߑ0B`ܝ0u".QdB0Cr]#Q9lqN^ֳh~NU\ 16 ~SnTl\THҲڛ-~G~)$oQ7-C}q%/avO|[q4~Bc-$N76w{V餃.&(o*n NeRi4!3R"4nbm-y[X."!QKE\N4gՠםaNp >k)90BZBs yrer)vDtrv\v[>rJm a̼~uՏ>rMZcB<`)\yt|ۍr'<>[Îh7Z8caI! p⢟̮,G k5@`iw nО8pv *'O A[.rhT pR?+;\*HsLqUf:ql-ć *6!h+ˬ{h- jgkMMP#:}{/VŶC]옙&[W$ګ^#4fWa\ 5躺M[6)T3~ :. Z`si(RQ|/` il^L#f-;-C;_*{@EMCooÂ_7TrqzF%ׯ|UEƫUs^ݜv{fQ<ĐVPTfͦ?mpP*&QG{cJEPe2)xP0AMɪZHj"׻"AC+zqmVzᖞU%C:@1W [y)J@ob% jA>)Nǀi$At`>?f0gH36p6D|M 4N 4JJڃ jƇ\ p38Я6pV?:$sDNƹ2n,HO\[ոK-)W~im?T:޺UeY-#dJe)Z5?$\dW<,Ɇ;ط5SոTT̄f(PYv=Q ~DX*8辩s- ˨΀55 XRl QC l|5{ӦT\t꼕+en۸Psl3UO[ZS3*,:ÛZLS'̵**@ı~xgno2- WV;pZ9?~$6҄xJ>\QA_Cihbl] 64*A˯ɰqX7YX.-ոaɇVhiKgqNRĆN(r']%٘@3̀jZJ.;nm,S0xͻOF33ҧ<$'GE+}'1f3y5/&Z\RB7dm]8\3߂Ȫ@oT3eu^W@e7l!B,s1$Z&?dC (YЦSm>J"&pt܈P㇄BF4G5 t^Ć$j-a㠍g^ʐCAsT=kTS,|r9IBϘЬ'vGA@thQNj&T=xt;2]P|T- LÞe1ݽWZŚ*MrH5?=o"9K5='k-*AE| qҔ_?\7%|M6f++S*}W_]3fmܮ˳m w!.R#鬪;qq71$•ݙկ_iK&JάMemV5P0> Q5WHIh&4ҍIlE7}sm[cȾ|d^ %Uv1D>.T7*=tZ_㟾1Х:=0pZ6ҋNt(uƝ; B]$kڌ.{F*/UZN砦|oqKG;^侞9NexK \wh~ZpHb䉸 [k8k.bX.QXpxYa^"#Bwnbum5F~>8bN:p4 [gv^ BFUz)?60F8/2C8>N8G%l%5FH{46h4%# 7x oN t\'Ȩ E0#jNãVӹd?WlcW žֵu-}22EN}#䵵2H^a3rqs-S3&f퇣fwl.=W8,cHjcTWנs90ZDMC2ZMdjt"8:g{.Ʊ1Fb618"yԦ> W9 V `jT򔔑r,ni d qN .g+ S Q KaB?_QE rjh>Eӛ;C׭7^q `Ue#-;oJċԝ>) ;Jg׭9R;OgiI7}8Kہqjeؓ+ٗ'nϷk3eFρ0V#pMAzb^PVu~1uғwn ^.II_vdW[Q,+Lbćq 9V} ΏVw4qU3&jıHYb ttT7ρarBwP9?)uT/aA19kM \Psq+=[5͔?9W+^o^E8s)f 2aQxi& NE>"^Naa;f9]NE& t^CLz'e8ZRs&67_ãcyJ1 @TZ?SD2 |POӌ\dR7zH9iQ#zrc.4GR4qx<2~Xhnੳ2auBNC+kX0 aj5n>މe3vާ<>_ uH:XR%~9!4oѼ38? 1d#A&{A!i6 /Xa㇤=W;|) g~ ?*悽 }ڧKt>5|E.A Q6 (6 6є7<9_C f1Ўi8, V4$uti,.`v6r P gFBɎ t C3; ,oÂx| /KMp1S_X.fV#U>Ȓ#B] AIVoІϵGTV1nr+OXS% ³fOZ[_9P߰ {Gln%#hdwH= ye/W>,IP,*MV~ºK&eċM콣=)qFS"GTF*LX,h[wweWQEx ?{^چExhiׂJH|^͓e*^Я.uxEb#;ԝ<]z]\wNhochqE=4Q17W̓lÕ6᧿HE_̣qy YR۫9~l4sVy`Uߛ,#_u+DeM~hq벇#Yz$; 5ͯ9$ z> *jO$$O/xRtf-}*oɦ|3M;xިUl/.~XǎY4x3&x";$KI5dڭ ~w[M9O%4Q}S^t@w[Y;-s;bwH-* imI-1e/~TNN.p)H$W~ƦO (9, ]gM6r+#%/swA$q4O> d9}+$s?0a,>yڈs<=,c_*\D}2MT8/4g'ڦ8'}"C*\9#Y>z$7c[s|"$} ymzQx 5%o$jkp)x-:И|?ofgFr2SZq}q o,wyOgCF1l'L5T33yM92"s5uD6-JUbs O)wR -2/5frϛf@=BFCB&'F}@&yubC?'S49+ÓCIî+f/RU C Fu:C*} T:}{ݽⲷue[!>? ڸ"M 8gz0\HkZ:h~@+#N fjyio!B R'5>`[!T`mC Iѝ}n >W!M}Uav43)!kcȂm? dwv!ה;Xϡۨ}8vt"Ӽ# kvXJ[l[ZݙMÀXC3l[ TaVjʻѬ"œ t:(<cZveQTqHi{銀Q埓'ÖiP■mKAIBF =Tᅽ(&TS?/؁A:ַОV(@wFa^]o]*99Ri_2vM`Pf{QYH#V7v7Ұq>@~uɘ׆Ax/xB3Ġtyb0nG` EDٍA: PwI7nW2ED}.(h"U]9Ih_V@GZ0C pb :L 3tN*N 2!3 Cayn.ɋW`̳}QBCi 8*{57O#aTBUoi0 _^ ChrU}~rL 1z>..=%GG o EuPPsؘ޸8Pu&;*|i&Pbțh;[|y*cVhҼ(~_AqU2GIQ3`^v=@K'ЇZ#4sJ=:sY sڥbyj S_E܃"@~>86#y[cSŬ#SJGZyvvSя扝pwaT/, 9'Jkv%%.~o[ 衧RBjSȀ*$'腁pçSu +9\_f+8u\,tpэkخJ0h(]NQvW7 86:ݣ WcY_i>"R(e]6RA%U6&F]7@̳k3X h?KQ2Bk[?..KKAb65ke+]FeWHU0Oק5 e3Hco>l]02cH9{Z {sO!A,7?ŷ3w俎A Fj8B&8U$G$Y5FL5n1> q2.6e +@/kb{(7i={l͍݂濦81g(%h/EfMҍt5̼vgo ~ਜ਼WKi父UأݖwRSEFT% `=|*=1*SX^w)lfQH(YSSˌK1W]f7ך^&p@T'.%3 5zaTf6A5LX̡|L-ηTg{A)F."hjA;.~o% G#}&]׾c`ChH9xnNY lc\+v\EƧ1D9KX)2b.NWQש$/|6tð32ԛ72иyu0e)Nuh'd~xY ># b"k3 :9v$ПC:)H> զz;ed\jmfOa%9cKxۥ!k%HDn{Y"{n_} )9= _/Z(>lYVgQ#߭:Qbw$zwٮ#U?|Ghz{o$wϜ)|Vh? ZV7%Go/׆E"KӲlp76-z !l4n>$\zV?szqejQ]m^=^ !lHB4sL i9}2^K5OB)O v^~݀xrm\K&G^5CL}&FB]Kn3|sGjykObsܽaW?R6Jfh2 lBS\=jV*Y^˺^E)*\ rr(a@6nԌ?}dLgIvqNcaƮkmLcA!hdVwc=憖s_:җsLg>1*4-%&0Ub)Eܬ*b51 ++;<`!qfM*,[/GK+{,>CLR%%c~'EGAG=h䟔8:IDN)W̻AF)ucw'qhXèL@a~6Pc2L"A2bU & 9A#QLO:E9kfKFb93tL$cˬpLz5dp۰>$`.~X=?NͰ/LPNo0p b8AR4r Jj} Ӳ04ˋquۏAFP 'HfXDIVTM7Lv\(N,/ʪnڮi^m?~ QU Ӳ04ˋquۏb$tV&gϖr>32+&/#"&/.=/&6?#"&'&546?>;'.?654676X& j  j )"& j  j )L j )"& j  j )"& j LL#32!2#!+"&5!"&=463!46^^L^^p@LE32!2+!2++"&=!"&?>;5!"&?>;&'&6;22?69  x } x }  x } x v L   d    d  l d;2#4.#"!!!!32>53#"'.'#7367#73>76p<#4@9+820{dd 09B49@4#bkv$B dpd>uhi-K0! .O2d22dJtB+"0J+ku0wd/5dW%{L>G!2+!2++"&=!"&?>;5!"&?>;4632654&#^CjB0  0BjC x  x u x u@--@$?2O*$ $*P2@%d    d   BVT@L!2#!"&=46 %A+32!546;5467.=#"&=!54&'.467>=2cQQc22cQQc2A7 7AA7 7Ad[##[[##[dd76!' Pԇ $ op zy#%**%$ pdL #7!2"'&6&546 6'&4#!"&7622?62~   \l lL 7  &   l 2'7' & c_"fn &\`tfjpO32!546;! 22&&L%6.676.67646p'0SFO$WOHBXAO$WOHB"7Q)mr *`)nq&* )2"'#'".4>"2>4&ȶNN;)wdNNrVVVVNdy%:MNȶ[VVVdXD>.54>0{xuX6Cy>>xC8ZvxyDH-Sv@9yUUy9@vS-H^{62!2'%&7%&63 a o  ^{"62!2'%&7%&63#7'7#'JJN a o  d⋌&2##!"&=467%>="&=46X|>& f   f &>|.hK  ]  ]  Kh.| L#'+/37GKOSW!2#!"&54635)"3!2654&33535!3535!35!"3!2654&35!3535!35~  Ud  & sdd dd d  & d dd dL   ddd  ^ ddddddddddd  ^ dddddddddLL/?!2#!"&546)2#!"&546!2#!"&546)2#!"&5462pmppmpLpppp LL/?O_o32+"&=46!32+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=462LppL/?O_32+"&=46)2#!"&=4632+"&=46)2#!"&=4632+"&=46)2#!"&=462DDDLpp&,  62"'&4?622;;nnBB# "' "/&47 &4?62 62    ;    %I2"'#".4>"2>4&3232++"&=#"&=46;546ijMN,mwbMMoXXXX K  K K  KMbyl+MMijMXXX# K K  K K %52"'#".4>"2>4&!2#!"&=46ijMN,mwbMMoXXXXX^  Mbyl+MMijMXXX  -32+"&5465".5472>54&&dd[֛[ҧg|rr|p>ٸu֛[[u'>7xtrrtxd/?32+"&54632+"&54632+"&54632+"&=46  ޖ  ޖ  ޖ    ~ p     >     GO27'#"/&/&'7'&/&54?6?'6776?6"264X!)&1-=+PP08,2&+!)&1-<,P  P/:-1&+x~~~P09,1&+"(&1,=,QQ09-0&* !(&0-=,P~~~d!%)-1!2!2!5463!546!5#!"&53333333,);  ;),,;)D);dddddddd;)d KK d);ddd);;) dDDDD 62++"&5!+"&5#"&l`    j`  w  ? d3!#!"&5463#"&=X;),Rp);vLp02".4>"2>4&3232+"&546֛[[֛[[rrrr|2   [֛[[֛;rrr   2  ^  )#!3333))p,p,d/3232"'&6;4632#!"&546;2!546& & T2   2 >p  ^  12".4>"2>4&3232"'&6;46֛[[֛[[rrrr|  & [֛[[֛;rrr   12".4>"2>4&%++"&5#"&762֛[[֛[[rrrr   &[֛[[֛;rrr  9!2#!"&'&547>!";2;26?>;26'.    W & & W tW    >     '2".4>"2>4&&546֛[[֛[[rrrr[֛[[֛;rrr] $  (76#!"&?&#"2>53".4>32  mtrrr[֛[[u$  Lrrrtu֛[[֛[576#!"&?&#"#4>323#"'&5463!232>  ntr[u[u  h ntr$  Krtu֛[u֛[v h  Lr d/?O_o!2#!"&546!"3!2654&32+"&=463!2#!"&=4632+"&=463!2#!"&=4632+"&=463!2#!"&=4632+"&=463!2#!"&=46}    R 2  2   > 2  2   > 2  2   > 2  2   >   ~   R d 2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2 L#54&#!"#"3!2654&#!546;2uSRvd);;));;) SuvR;));;)X);dLL 732#462#".'.#"#"'&5>763276}2 d!C@1?*'),GUKx;(.9)-EgPL 3 0[;P$ 97W W!1A2+"&54. +"&54>32+"&546!32+"&546ޣc 2  2 c*  `  ct  ,rr  ,tޣ 4  4  G9%6'%&+"&546;2762"/"/&4?'&4?62A   Xx"xx"xx"ww".   ^ x"xx"ww"xx"r/%6'%&+"&546;2%3"/.7654'&6?6A    `Z  HN.   ^ d  g~jb1K3#"/.7654&'&6?6%6'%&+"&546;2%3"/.7654'&6?6 D@  *o;7 *    `Z  HN iT "ZG !   ^ d  g~j  !%-;?CGKO3#!#!#3!##5!!!!#53#533!3533##5#535#5!!#53#53#53!5!ddpddX,,ddddD dddd,D,ddddd dd,dddX d,,d,,ddd dddddd,dddddd  #7#3#3#3#3#3!5!#53#53#53ddddddd,,dddd,Pdd[[[[[   "'463&"260V C;S;;S;V0 ;;T;;  ! "'463!"/ &"260V 08D;S;;S;V0 V08;;T;;d&!2&54&#!"3!2#!"&54?6,9K@  D@   K|@  @  J  L !2 46 >>CEU!"3!26?6'.#"#!"&/.+";26=463!2;2654&!"3!26/.6D N9  >SV N N      & X & l l- p  v       dL!)13232#!"&546;>35"264$2"&48]4$);;));;) '3]dϾV<?!(% _5,Ry:" *28 T2*BBW-ޑY". BB % Zd'2;#!5>54.'52%32654.+32654&+50;*7Xml0 ); !9uc>--Ni*S>vPR}^3:R.CuN7Y3(;  G)IsC3[:+ 1aJ);4ePZo!56764.'&'5mSB ,J   95(1(aaR@ 9%/#4.+!52>5#"#!#3'3#72 &2p"& 2KK}}KK} dd R ,১ !%/#4.+!52>5#"#!5!'7!5L2 &2p"& 2C১  vdd  ,}KK}}KKL/?!2#!"&=46!2#!"&=46!2#!"&=46!2#!"&=462X LLddddddddL/?!2#!"&=46!2#!"&=46!2#!"&=46!2#!"&=46DLDLLddddddddL/?5463!2#!"&5463!2#!"&5463!2#!"&5463!2#!"&Xp LddddddddL/?!2#!"&=46!2#!"&=46!2#!"&=46!2#!"&=462LLLLLddddddddL/?O_o32+"&=46)2#!"&=4632+"&=46)2#!"&=4632+"&=46)2#!"&=4632+"&=46)2#!"&=462ddA ddA ddA ddA LddddddddddddddddL#*:J!#;2+"&=46!2#!"&=465#535!2#!"&=46!2#!"&=46dddd ,XLdddd}KdKddddL#*:J32+"&=46#3!2#!"&=463#'7!2#!"&=46!2#!"&=462ddgdd /ȧ,XLddLdddK}}dddd!2#!"&546 K,,,,,,v,,,D,,L!2#!"&5467'2"&4,XJ*J%pNNpNL d>tNoOOo62.'&54>"264usFE66 !^Xm)!fhHuXyHÂ2".4>"֛[[֛[[Ktrr[֛[[֛oVrru5.54>6?6&'.'&76#&*IOWN>%3Vp}?T|J$?LWPI)(!1 )  HuwsuEG^F&:cYEvsxv!K:%A'# " A)Y l */7>%!2!"3!26=7#!"&546 7l l27);;));Ȼp87cs* s ;) );;)2cL6!#"3!2657#!"&546&'5&>75>^i4);;));ȹpS 9dTX .9I@F* L6;) );;)g  0!;bA4 L5!2!"3!26=7#!"&546 62"/&4?622^^  Ȫ   ȯ  ȭ   ȭ   L326'+"&546d0dLJJL#3266''+"&5462d00dLJJJJ3''&47660J*J36 &546.2   d32+"&546!32+"&546  dL#!"&5463!2L  346&5&5460d * ;O#72#"&5&5&5464646dd12N: 9  > =,L32+"&5&54646Rdd0L;;dH  #!"&762!2#!"&=46  *9HdduJ  u`((&;(J ' 7(a#aa32".4>#"#";;26=326=4&+54&֛[[֛[[}dd[֛[[֛dd2".4>!"3!26=4&֛[[֛[[E [֛[[֛~dd32".4>"'&"2?2?64/764/֛[[֛[[ xx  xx  xx  xx [֛[[֛ xx  xx  xx  xx  $2".4>'&"2764/&"֛[[֛[[Tw[֛[[֛1Uw;K2".4>";7>32";2>54.#";26=4&֛[[֛[[?2".4>#";26=4&#";#"3!26=4&+4&֛[[֛[[    KK  ^  K[֛[[֛V   2  2  2  /_3232++"&=.'#"&=46;>7546+"&=32+546;2>7#"&=46;. g  g g  g Df  fD Df  f g g  g g ͨ  fD Df  fD Df?2".4>"2>4&"/"/&4?'&4?62762֛[[֛[[rrrr@||@||@||@||[֛[[֛;rrrZ@||@||@||@||02".4>"2>4&"/&4?62762֛[[֛[[rrrrjjO[֛[[֛;rrr}jjO!2".4>"&32>54֛[[֛[[KtrAKihstr[֛[[֛;rtxiKA>rtsS6!2#!'&4' &F   &S &5!"&=463!46 &U & U ## ] #!+"&5!"&762   && ]32!2"'&63!46&# U & U # &] &5>746 ^$,[~UU & U #$DuMiqF +!2/"/&4?'&6!"&546762R,^j^!^j^^j^P,^j^IIgg+#!"&546762!2/"/&4?'&6j^^ ,^j^`j^,^^j^/2".4>#";2676&#";26=4&֛[[֛[[:#6#:1  [֛[[֛.   IUaho276?67632;2+"!#!54&+"&=46;2654?67>;26/.'&;26!"&5)#!  &0  =  2 pp 2  =   353  X  v  v !{,  2  ,ԯ  2 0y    r w  +I6.'&&&547>7>'.>7>&67>7>7>-BlabD8=3*U  :1'Ra\{%&=>8\tYR-!q[Fak[)ȕX1 "@&J<7_?3J5%#/D &/q!!6ROg58<'([@1%@_U2]rO.>7'&767>.'&'.'&>77>.'&>' '8GB    `H  >JS>H7 '+" NA 5M[`/Pg!;('2"&"IbYCe\D9$ 886#1%)*J7gG:    8G\au9hoK$]54<&"&5476&2>76&'&6?6&'&'.{nO9:On{{nO:9On{FZ  2Z__Z2  Z# %8-#,- "F-I\b\I*I\b\I--I\b\I*I\b\I9>||;7Es1$F^D10E^E$1u$/D0 "%,I';L!#7.54>327377>76&'&%7.5476&6?'&'.P[vY,9On{R=A &/l'PjR.Mv&  6QFZ  *HLh5)k|# %8- ,- "xatzbI\b\I-yRU4Zrnc1?1FrEs11) ]@ @] )1ES>L'+/37;?CGKOSW[_c3232!546;546;2!546#!"&5353353353353353533533533533535335335335335Rd22ddddddddddd|ddddddddd|ddddddddd2222pddddddddddddddddddddddddddddddw%7&=#!"&=46;3546'#"&=463!&=#'73546oXz#z*dXzdM*zL!2#!#"&546d);;)d);;L;));,;)X);dL ?32!546!32!546".5!2>&54=(LffL(, '6B6'p)IjV\>((>\VjI), +'%! !%'*L 'L'a'M 7 Maa'aQd_)!232"/&6;!%+!!"&5#"&?62**p&032!2#!!2+"&=!"&=#"&/#"&468^&d,!02**6%%+*2222 *L !53463!2!!P;),);DPdd);;)L 3463!2!!;),*:,P, pX);;)dDEk+32"/&6;#"&?62{**YDk&=!/&4?6!546X`)  )   !.#!"!"3!26=4&53353$`$-);;));;ddd-(d;)d);;)d);dddddL #12"&54%##"+"&'=454>;%".=4>7i**d]&/T7 " LRQ  )2( Jf,53232#"./.46;7>7'&6327"&)^Sz?vdjO9t\U>/ v?zS$2451 7F8%M)(  ()GM~ 1==7'''7'7'7'77 N괴N--N괴N-N--N괴N--N괴d!-=32!2+"&/#"&54?>335!7532+"&5462(<H(<,F=-7` 1dd>2vddQ,}Q,d-!2$'$(ddw} L 0<32#!+"&/&546;632+"&546!#35'!5X,<(<(21 `7-=|dd_dd22L!-d,Qv,Q($'$dd dԯ}wdO7G%6!2+#!"&5467!>;26&#!*.'&?'32+"&546dkn  T.TlnTj:d%8   VOddip &yLN(  % H YS(22S dO6F#!"&'#"&463!'&6?6*#!32!7%32+"&546n jUmlT.U  nJ   %&jPddO (SNLy& pd(Y aL7G2#!"&/&?>454&/!7%.!2#!"&=46ސNS( % p &y22SY( nTjkn  T.T8   Vd% dd-I!26=4&#!""&5&/&7>3!2766=467%'^ NLy& p  (S22(SYLddjTnlT.T  nk V   8%d%2".4>%&!"3!7%64֛[[֛[[  [֛[[֛9   &%2".4> 6=!26=4&#!54&֛[[֛[[%  [֛[[֛ &   %2".4>&";;265326֛[[֛[[K &   [֛[[֛@  %2".4>#"#"276&+4&֛[[֛[[  & [֛[[֛  2".4>%&277>7.'.'"'&65.'6.'&767>'&>7>7&72267.'4>&'?6.'.'>72>՛\\՛\\d+: =?1 " "/ ?9 #hu!$ 0 E.(,3)  (     *!A 7 ,8 !?*  \՛\\՛  ' "r"v G  .&* r$>   #1    %  *  '"  $  g2( % 67'"/&47&6PM<;+oX"O\e~Y+" n+We`#'7;!2#!"&=46#3!2#!"&=46!!!2#!"&=46!!d);;));;);;));; );;));;,;)d);;)d);dd;)d);;)d);dd;)d);;)d);dddL !2#!"&46!|;**Dd%32!2!5#!463!54635#!"&=);,); ;),;);));;)d;)pdd);d);dddD);;)+AW!2"/&546)2/"/&4?'&6#!"&54676276#!"&?'&4?622,^j^5,^j^/j^^^^j^j^,^j^&j^,^^^j#;CK2".4>"2>4&$2"&4$2#"'"&546?&542"&4$2"&4ݟ__ݠ^^oooo-- - L- 73H3)z - - - - _ݠ^^ݟWooo -!!- -! $33$ 1~ - - - -Z[%676&'&#"3276'.#"&477>32#"&'&6767632'."[v_"A0?! -  Y7J3$$ )G"#A.,= # (wnkV8@Fv"0DG([kPHNg8B*[eb2!5(7>B3$$' )M"#!7)/c# *xnfL@9NDH7!$W]B$&dXDD>.54>"".#"2>767>54&0{xuX6Cy>>xC8Zvxy#!?2-*!')-?"CoA23:+1! "3)@ +)?jDH-Sv@9yUUy9@vS-H-&65&&56&oM8J41<*.0(@  )*D*2Om9w.2&/7'/&477"/&4?BB8"._{iBBi BBBBBB7._BB^*k"5._{jBBFi BBBBBB77/_2#!"&54>!"264d:;));XV==V=.2G);;)3-D=V==V "/''!'&462*$3, #**#4$*' 2@K#.'#5&'.'3'.54>75>4.&ER<, 3'@" MOW(kVMbO/9X6FpH*M6&+  4C4%dfJ2#4.#"3#>36327#".'>7>'#53&'.>761T^'<;%T)-6"b "S5268 jt&'V7  0 $ݦ -$aPN(?",9J0* d2>2 ""   7Gd/9+DAL!X32"/&6;3+##"&?62*Ȗ*,|%#5##!32"/&6;3353!57#5!ddd,*dc,dd|ddd!%32"/&6;33!57#5!#5##!35*X,ddd,d,ddPdddL32"/&6;3##53#5#!35*Xdddd,d, dPddL32"/&6;3#5#!35##53*d,ddd, ddd32"/&6;3#53!5!!5!!5!*d,dpd , 32"/&6;3!5!!5!!5!#53* dpd,d, LL!2#!"&546!"3!2654&^pg );;));;Lp;) );;));LL+!2#!"&546!"3!2654&&546^pd );;));;oLp;) );;)); $  LL+!2#!"&546!"3!2654&!2"/&6^pg );;));; $ Lp;) );;));LL+!2#!"&546!"3!2654&#!"&?62^pg );;));; p $Lp;) );;));L5!2#!"&=463!2654&#!"&=46&=#"&=46;546&p);;)>DLpd;));d&  #%2"+'&7>?!"'&766763 ,  P'' K    S#  nnV/L5!2#!"3!2#!"&546&=#"&=46;546^>);;)pDLd;) );d&  1!2/"/&47'&6#"3!26=7#!"&5463!m)8m);;));Ȼp,pm)8m;) );;)֥#2".4>"2>4&2"&4ٝ]]ٝ]]qqqq{rrr]ٝ]]ٝGqqqsrrrL#3232"'&6;46!2!54635 ' gdV^|d22L# ++"&=#"&7>!2!54635Gz " 'gdM !d22LK" 62"'&4?62!2!54635qgdq#d22L #'762'&476#"&?'7!2!54635*MMК=gdML*Л:d22L#'/'7'&6"/&4?!2!54635^WЛԛL*MgdКԚPM*MXd22% ! q3gqdL+!#"&546;!3#53LDdddp,E/'&"!#"&546;!3#53"/&4?6262L_  Ȗdddj\jO)_ p,j[jO) >'.!#"&546;!3#53"/"/&4?'&4?62762Lg%dddFF))FF))gp,F))FF))F/!"!#"&546;!3#533232"/&6;546L dddd*p,/'&"!#"&546;!3#53++"&=#"&?62L*ndddd*pp,L !2!546#!"&5!52LPdL&}-1;&=!5!546#"&=46;#5376!!/&4#5;2+p/22ddpddd33*ȖdȖ*yddQ%6+"&5.546%2+"&5.54>323<>3234>^%"% "  d d 1t5gD >?1) A..@  ^  ^ dL3"!5265!3!52>54&/5!"!4"2pK Kp"2KKL8 88 %v% 88 x88 %v% 8LL  $(4!2#5'!7!!2#!"&546!55%!5#!!'!73wipdw%,);;));;),p,ddibbd;) );;));dfdd&767>".'.7.wfw3 .1LOefx;JwF2 1vev/ 5Cc;J|sU@L#A2/.=& &=>2#!"&=46754>ud?,  1;ftpR&mm&L!((" """" '$+  222/2 ! '!'3353353!2+!7#"&46!2!546L J LP*dd*22dL #"!4&#"!4&!46;2d);,;gd);,;;)d);L;));;)D););;)L%)!2#!"&546!#3!535#!#33||D| ,dddL| |||Dddd,ddd,L%)!2#!"&546!#5##3353#33||D| dddddddddL| |||Dddd,L#!2#!"&546!#3!!#3!!||D| ,,L| |||DdddL!2#!"&546!- ||D| ,L| |||D ,L )!2#!"&546!!!#";32654&#||D|dDd&96) )69&L| |||DdVAAT,TAAVL%)!2#!"&546!#3!535#!##53#53||D| ,ddddL| |||Dddd, d dL#'!2#!"&546!3!3##5335#53||D|DdXddd,ddL| |||Dp ddL"&!2#!"&546!#575#5!##53#53||D| d,ddddL| |||Dp2Ȗd d d %2".4>"2>4&!!!'57!۞^^۞^^qqqql,dd,^۞^^۞Lqqqddd '+2".4>"2>4&#'##!35۞^^۞^^qqqql2dddd,^۞^^۞Lqqqd2d2dddddA 62632+54&#!"#"&5467&54>3232"/&6;46n,,.xxPpVAbz  & AwasOEkdb  A32632&"#"&5467&54>++"&5#"&76762n,+.yxZ % OqVAb   AwaxchsOEkdc  dLm%5!33 33!#"!54&#Ԫ2dd,,Md22y7/2#"'2!54635#"&547.546324&546X^Y{;2 iJ7--7Ji/9iJqYZ=gJi22iJX5Jit'*BJb{"&'&7>2"3276767>/&'&"327>7>/&'&&"267"327>76&/&"327>76&/&oOOoSSoOOoS=y" $GF`   Pu "Q9   ccccVQ:   Pu "GF`   y" $ooSWWSo++oSWW"y  `FG # uP  :Q # cccc:Q # uP  $`FG # "y  d "!#5!!463!#53'353!"&5+, ?,dԢdu       d !! 463!#5##5#7!"&=)+5, ?,>dԪ |  ^G |d 77 P#3!#732!!34>3!!ddԢ!,d!s, d,+$d$+ppLL293232#!"&=46;54652#!"'74633!265#535d22s);;);)X>,>XL2dd2;));FD);>XXԢddL6=3232#!"&=46;54652#3#!"&54633!265#535d22s);!);;)X>,>XL2dd2;) $+;) );>XXԢd  #!"&762#";2676&35} ,, }@D:#6#:&77&P'L. dd LL/?O_o32+"&=4632+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=46                  L                  )33#!2!&/&63!5#5353!2+!7#"&46!2!546dd^>1B)(()B1>^dd> J LPdO7S33S7Odd|*dd*22+52#4!!2!'&63!&54!2+!%5#"&46!2!5460P9<:H)"Z" )HJLP;))%&!!&**22$.2"&432!65463!2+!7#"&46!2!546 jjj."+''+# J LPjjj9:LkkL:9r*dd*22,62"&5477'632!65463!2+!7#"&46!2!546X/[3oo"o"."+''+# J LPk6NooN>Qo 9:LkkL:9r*dd*22",!!.54>7!2+!7#"&46!2!546X,%??M<=BmJ J LP9fQ?HSTTvK~*dd*22)2!546754!2#3#3#3#!"&546/R;.6p6.d6\uSpSuu;)N\6226\N)G6.dddddSuuSSudLL/3!2#!"&546!2#!"/!"&4?!"&=46!'|  % XW & dDdL D 2  % XX %  2 dddL#-7!2#4&+"#4&+"#546!2!46+"&=!+"&= Sud;));d;));du);P;ddLuS);;));;)Su ;),); 2222  !&4762 !2!546 'YV/ |UYY(n0U22!/.#!"3!26=326!546;546;33232!'p'q*}20/222,2 "!#!5463!#5!#!"&5463!#5,  w,, v  w, O,T    dGFV32676'&7>++"&?+"'+"&?&/.=46;67'&6;6#";26=4&KjI C   )V=>8'"d 1*) "dT,| -otE  GAkI ! "% ,=?W7|&F@Je5&2WO_e_ 2  2 ~ $4<Rb%6%32!2&'&#!"&=46#";2654&'&"2647>?&/&6%?6'.'.. +jCHf7" *:>XXP* @--@- -?0 !3P/|)( )f!% =  &* x"62&CX>>X83 D-@--@ۂ # =I+E( //}X&+ 5!H d9Q`o322#+"&=#+"&=#"&=46;#"&=46;546;23546!2>574.#!2>574.#q Oh ..40:*"6-@# d   KK   d)  )k)  ) m!mJ.M-(2N-;]<* K  KK  K X K  KK  "p "),!2#!"&'.546"!7.# Vz$RR(z }VG+0 )IU!zV`3BBWwvXZ3Vz&--% ,(1#32#!"&546+"&=ۖgT)>)TH66g )TT)g6633#!"&546+"&=`T)>)TH66B)TT)g66 %'5754&>?' %5%Ndd/\^^<ǔȖ  (Abd 2"&4$2"&4$2"&4|XX|X|XX|X|XX|X X|XX|XX|XX|XX|XX|L2"&42"&42"&4|XX|XX|XX|XX|XX|XLX|XX|X|XX|X|XX|ddLL/!2#!"&=46!2#!"&=46!2#!"&=46}  J    J    J L  p  p  /3!2#!"&546!"3!2654&!2#!"&546!5^ );;)X);; G ;));;)X);d,dddL;!2+32+32+32#!"&46;5#"&46;5#"&46;5#"&46222222222222L********, *.62"&%#462"&%#46"&=32W??WW??||||||*(CBB||||԰||||ӐB76+2+"47&"+".543#"&'&676/!'.6E*  '?) T 0I' *L #3{,# n  6F82 *5#"#!#4.+3#525#"#5!2 &2p"& 2D d 2d  dd R , W 22 L 05"'./#!5"&?!##!"&=463!2E  1;E%= !'y,2 " # 22+."A2VddGJ!2#!"&546#"3!26=4&#"'&?!#"3!26=4&'"'&'#&#2LFF &  7 ? 9   9 gLR   2 2  2 2 $ #'!5!!2#!"&546)2#!"&546!PpmpG,Ld|pd,#'!2#!"&546!2#!"&546!!5!2pmpG,P| pd,dd'+!235463!23##!"&=##!"&546!2dddpdp,d ,'3#3!2#!"&546!!2#!"&546dddpG,|dpd, pdL'+32+!2#!"&5463!5#"&546;53!X|^d,Lpdpdd,'!#3!2#!"&546!!2#!"&546ddvpG,|dpd, p,0o #"&54632a5*A2~ 6'&4O**{))*2A~ !2"'&6d)***2,~o #!"&762{))*a**( 5-5!5!Lc d 1#3!35#5!34>;!5".5323!,P2 &d2"d& 2dd,dd  dd & ,L%1#4.+!52>5#"#!#3!35#5! 2 &d2p"d& 2 ,, dd & ,dd,ddfrJ32 +"'&476 0  ) J 00   >fJ32+"&7 &6S )  0 J ))   fJr"'&=46 4 ))  w  )  0f>J ' &=4762j  00  )  0  =:#463267>"&#""'./.>'&6|Vd&O "(P3G*+*3M, :I G79_7&%*>7F1 ||5KmCKG\JBktl$#?hI7 !2+&5#"&546!5X,p dddL!2%!#4675'=DXDd dQ,[u}4]ddMo__<vsvsQQ(dpEHEd{ d&ndd ddddd5d!u ,d;I]ddQEJadd9'dddd dy'dddddddd,d,A22>ff****NNNNNNNNNNNNNN"~Fn2b\r bb 6 ( L 0  X * ^ h(T*v 8|t*<6`R.j(h6h^2Dl.vb F !2!v!"@""##"#8#z##$$0$^$$%4%`%&&~&'P''(4(p())*&*J*+ +z,,h,,---.(.f..//F/~//0>0011`112$2^223"3>3h344`445,556>6|677N7788B889 9J99::l::;;<:>>?(?n??@H@@AA~BBBCCBCvCCDD`DDEZEFFtFFG6GvGGHH2HNHjHHII8I^IIJJ.JR@. j (|  L 8 x6 6   $ $4 $X | 0 www.glyphicons.comCopyright 2014 by Jan Kovarik. All rights reserved.GLYPHICONS HalflingsRegular1.009;UKWN;GLYPHICONSHalflings-RegularGLYPHICONS Halflings RegularVersion 1.009;PS 001.009;hotconv 1.0.70;makeotf.lib2.5.58329GLYPHICONSHalflings-RegularJan KovarikJan Kovarikwww.glyphicons.comwww.glyphicons.comwww.glyphicons.comWebfont 1.0Wed Oct 29 06:36:07 2014Font Squirrel2       !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~     glyph1glyph2uni00A0uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni202Funi205FEurouni20BDuni231Buni25FCuni2601uni26FAuni2709uni270FuniE001uniE002uniE003uniE005uniE006uniE007uniE008uniE009uniE010uniE011uniE012uniE013uniE014uniE015uniE016uniE017uniE018uniE019uniE020uniE021uniE022uniE023uniE024uniE025uniE026uniE027uniE028uniE029uniE030uniE031uniE032uniE033uniE034uniE035uniE036uniE037uniE038uniE039uniE040uniE041uniE042uniE043uniE044uniE045uniE046uniE047uniE048uniE049uniE050uniE051uniE052uniE053uniE054uniE055uniE056uniE057uniE058uniE059uniE060uniE062uniE063uniE064uniE065uniE066uniE067uniE068uniE069uniE070uniE071uniE072uniE073uniE074uniE075uniE076uniE077uniE078uniE079uniE080uniE081uniE082uniE083uniE084uniE085uniE086uniE087uniE088uniE089uniE090uniE091uniE092uniE093uniE094uniE095uniE096uniE097uniE101uniE102uniE103uniE104uniE105uniE106uniE107uniE108uniE109uniE110uniE111uniE112uniE113uniE114uniE115uniE116uniE117uniE118uniE119uniE120uniE121uniE122uniE123uniE124uniE125uniE126uniE127uniE128uniE129uniE130uniE131uniE132uniE133uniE134uniE135uniE136uniE137uniE138uniE139uniE140uniE141uniE142uniE143uniE144uniE145uniE146uniE148uniE149uniE150uniE151uniE152uniE153uniE154uniE155uniE156uniE157uniE158uniE159uniE160uniE161uniE162uniE163uniE164uniE165uniE166uniE167uniE168uniE169uniE170uniE171uniE172uniE173uniE174uniE175uniE176uniE177uniE178uniE179uniE180uniE181uniE182uniE183uniE184uniE185uniE186uniE187uniE188uniE189uniE190uniE191uniE192uniE193uniE194uniE195uniE197uniE198uniE199uniE200uniE201uniE202uniE203uniE204uniE205uniE206uniE209uniE210uniE211uniE212uniE213uniE214uniE215uniE216uniE218uniE219uniE221uniE223uniE224uniE225uniE226uniE227uniE230uniE231uniE232uniE233uniE234uniE235uniE236uniE237uniE238uniE239uniE240uniE241uniE242uniE243uniE244uniE245uniE246uniE247uniE248uniE249uniE250uniE251uniE252uniE253uniE254uniE255uniE256uniE257uniE258uniE259uniE260uniF8FFu1F511u1F6AATP Code Coverage for {{full_path}}
    {{items}}
     
    Code Coverage
     
    Classes and Traits
    Functions and Methods
    Lines
    {{lines}}
    Dashboard for {{full_path}}

    Classes

    Coverage Distribution

    Complexity

    Insufficient Coverage

    {{insufficient_coverage_classes}}
    Class Coverage

    Project Risks

    {{project_risks_classes}}
    Class CRAP

    Methods

    Coverage Distribution

    Complexity

    Insufficient Coverage

    {{insufficient_coverage_methods}}
    Method Coverage

    Project Risks

    {{project_risks_methods}}
    Method CRAP
    htmlspecialcharsFlags = ENT_COMPAT; $this->htmlspecialcharsFlags = $this->htmlspecialcharsFlags | ENT_HTML401 | ENT_SUBSTITUTE; } public function render(FileNode $node, $file) { $template = new \Text_Template($this->templatePath . 'file.html', '{{', '}}'); $template->setVar( [ 'items' => $this->renderItems($node), 'lines' => $this->renderSource($node) ] ); $this->setCommonTemplateVariables($template, $node); $template->renderTo($file); } protected function renderItems(FileNode $node) { $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}'); $methodItemTemplate = new \Text_Template( $this->templatePath . 'method_item.html', '{{', '}}' ); $items = $this->renderItemTemplate( $template, [ 'name' => 'Total', 'numClasses' => $node->getNumClassesAndTraits(), 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), 'numMethods' => $node->getNumMethods(), 'numTestedMethods' => $node->getNumTestedMethods(), 'linesExecutedPercent' => $node->getLineExecutedPercent(false), 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), 'numExecutedLines' => $node->getNumExecutedLines(), 'numExecutableLines' => $node->getNumExecutableLines(), 'testedMethodsPercent' => $node->getTestedMethodsPercent(false), 'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(), 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), 'crap' => 'CRAP' ] ); $items .= $this->renderFunctionItems( $node->getFunctions(), $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getTraits(), $template, $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getClasses(), $template, $methodItemTemplate ); return $items; } protected function renderTraitOrClassItems(array $items, \Text_Template $template, \Text_Template $methodItemTemplate) { if (empty($items)) { return ''; } $buffer = ''; foreach ($items as $name => $item) { $numMethods = count($item['methods']); $numTestedMethods = 0; foreach ($item['methods'] as $method) { if ($method['executedLines'] == $method['executableLines']) { $numTestedMethods++; } } if ($item['executableLines'] > 0) { $numClasses = 1; $numTestedClasses = $numTestedMethods == $numMethods ? 1 : 0; $linesExecutedPercentAsString = Util::percent( $item['executedLines'], $item['executableLines'], true ); } else { $numClasses = 'n/a'; $numTestedClasses = 'n/a'; $linesExecutedPercentAsString = 'n/a'; } $buffer .= $this->renderItemTemplate( $template, [ 'name' => $name, 'numClasses' => $numClasses, 'numTestedClasses' => $numTestedClasses, 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'], false ), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => Util::percent( $numTestedMethods, $numMethods, false ), 'testedMethodsPercentAsString' => Util::percent( $numTestedMethods, $numMethods, true ), 'testedClassesPercent' => Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1, false ), 'testedClassesPercentAsString' => Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1, true ), 'crap' => $item['crap'] ] ); foreach ($item['methods'] as $method) { $buffer .= $this->renderFunctionOrMethodItem( $methodItemTemplate, $method, ' ' ); } } return $buffer; } protected function renderFunctionItems(array $functions, \Text_Template $template) { if (empty($functions)) { return ''; } $buffer = ''; foreach ($functions as $function) { $buffer .= $this->renderFunctionOrMethodItem( $template, $function ); } return $buffer; } protected function renderFunctionOrMethodItem(\Text_Template $template, array $item, $indent = '') { $numTestedItems = $item['executedLines'] == $item['executableLines'] ? 1 : 0; return $this->renderItemTemplate( $template, [ 'name' => sprintf( '%s%s', $indent, $item['startLine'], htmlspecialchars($item['signature']), isset($item['functionName']) ? $item['functionName'] : $item['methodName'] ), 'numMethods' => 1, 'numTestedMethods' => $numTestedItems, 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'], false ), 'linesExecutedPercentAsString' => Util::percent( $item['executedLines'], $item['executableLines'], true ), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => Util::percent( $numTestedItems, 1, false ), 'testedMethodsPercentAsString' => Util::percent( $numTestedItems, 1, true ), 'crap' => $item['crap'] ] ); } protected function renderSource(FileNode $node) { $coverageData = $node->getCoverageData(); $testData = $node->getTestData(); $codeLines = $this->loadFile($node->getPath()); $lines = ''; $i = 1; foreach ($codeLines as $line) { $trClass = ''; $popoverContent = ''; $popoverTitle = ''; if (array_key_exists($i, $coverageData)) { $numTests = count($coverageData[$i]); if ($coverageData[$i] === null) { $trClass = ' class="warning"'; } elseif ($numTests == 0) { $trClass = ' class="danger"'; } else { $lineCss = 'covered-by-large-tests'; $popoverContent = '
      '; if ($numTests > 1) { $popoverTitle = $numTests . ' tests cover line ' . $i; } else { $popoverTitle = '1 test covers line ' . $i; } foreach ($coverageData[$i] as $test) { if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] == 'small') { $lineCss = 'covered-by-small-tests'; } switch ($testData[$test]['status']) { case 0: switch ($testData[$test]['size']) { case 'small': $testCSS = ' class="covered-by-small-tests"'; break; case 'medium': $testCSS = ' class="covered-by-medium-tests"'; break; default: $testCSS = ' class="covered-by-large-tests"'; break; } break; case 1: case 2: $testCSS = ' class="warning"'; break; case 3: $testCSS = ' class="danger"'; break; case 4: $testCSS = ' class="danger"'; break; default: $testCSS = ''; } $popoverContent .= sprintf( '%s', $testCSS, htmlspecialchars($test) ); } $popoverContent .= '
    '; $trClass = ' class="' . $lineCss . ' popin"'; } } if (!empty($popoverTitle)) { $popover = sprintf( ' data-title="%s" data-content="%s" data-placement="bottom" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent) ); } else { $popover = ''; } $lines .= sprintf( ' %s' . "\n", $trClass, $popover, $i, $i, $i, $line ); $i++; } return $lines; } protected function loadFile($file) { $buffer = file_get_contents($file); $tokens = token_get_all($buffer); $result = ['']; $i = 0; $stringFlag = false; $fileEndsWithNewLine = substr($buffer, -1) == "\n"; unset($buffer); foreach ($tokens as $j => $token) { if (is_string($token)) { if ($token === '"' && $tokens[$j - 1] !== '\\') { $result[$i] .= sprintf( '%s', htmlspecialchars($token) ); $stringFlag = !$stringFlag; } else { $result[$i] .= sprintf( '%s', htmlspecialchars($token) ); } continue; } list($token, $value) = $token; $value = str_replace( ["\t", ' '], ['    ', ' '], htmlspecialchars($value, $this->htmlspecialcharsFlags) ); if ($value === "\n") { $result[++$i] = ''; } else { $lines = explode("\n", $value); foreach ($lines as $jj => $line) { $line = trim($line); if ($line !== '') { if ($stringFlag) { $colour = 'string'; } else { switch ($token) { case T_INLINE_HTML: $colour = 'html'; break; case T_COMMENT: case T_DOC_COMMENT: $colour = 'comment'; break; case T_ABSTRACT: case T_ARRAY: case T_AS: case T_BREAK: case T_CALLABLE: case T_CASE: case T_CATCH: case T_CLASS: case T_CLONE: case T_CONTINUE: case T_DEFAULT: case T_ECHO: case T_ELSE: case T_ELSEIF: case T_EMPTY: case T_ENDDECLARE: case T_ENDFOR: case T_ENDFOREACH: case T_ENDIF: case T_ENDSWITCH: case T_ENDWHILE: case T_EXIT: case T_EXTENDS: case T_FINAL: case T_FINALLY: case T_FOREACH: case T_FUNCTION: case T_GLOBAL: case T_IF: case T_IMPLEMENTS: case T_INCLUDE: case T_INCLUDE_ONCE: case T_INSTANCEOF: case T_INSTEADOF: case T_INTERFACE: case T_ISSET: case T_LOGICAL_AND: case T_LOGICAL_OR: case T_LOGICAL_XOR: case T_NAMESPACE: case T_NEW: case T_PRIVATE: case T_PROTECTED: case T_PUBLIC: case T_REQUIRE: case T_REQUIRE_ONCE: case T_RETURN: case T_STATIC: case T_THROW: case T_TRAIT: case T_TRY: case T_UNSET: case T_USE: case T_VAR: case T_WHILE: case T_YIELD: $colour = 'keyword'; break; default: $colour = 'default'; } } $result[$i] .= sprintf( '%s', $colour, $line ); } if (isset($lines[$jj + 1])) { $result[++$i] = ''; } } } } if ($fileEndsWithNewLine) { unset($result[count($result) - 1]); } return $result; } } templatePath = $templatePath; $this->generator = $generator; $this->date = $date; $this->lowUpperBound = $lowUpperBound; $this->highLowerBound = $highLowerBound; $this->version = Version::id(); } protected function renderItemTemplate(\Text_Template $template, array $data) { $numSeparator = ' / '; if (isset($data['numClasses']) && $data['numClasses'] > 0) { $classesLevel = $this->getColorLevel($data['testedClassesPercent']); $classesNumber = $data['numTestedClasses'] . $numSeparator . $data['numClasses']; $classesBar = $this->getCoverageBar( $data['testedClassesPercent'] ); } else { $classesLevel = ''; $classesNumber = '0' . $numSeparator . '0'; $classesBar = ''; $data['testedClassesPercentAsString'] = 'n/a'; } if ($data['numMethods'] > 0) { $methodsLevel = $this->getColorLevel($data['testedMethodsPercent']); $methodsNumber = $data['numTestedMethods'] . $numSeparator . $data['numMethods']; $methodsBar = $this->getCoverageBar( $data['testedMethodsPercent'] ); } else { $methodsLevel = ''; $methodsNumber = '0' . $numSeparator . '0'; $methodsBar = ''; $data['testedMethodsPercentAsString'] = 'n/a'; } if ($data['numExecutableLines'] > 0) { $linesLevel = $this->getColorLevel($data['linesExecutedPercent']); $linesNumber = $data['numExecutedLines'] . $numSeparator . $data['numExecutableLines']; $linesBar = $this->getCoverageBar( $data['linesExecutedPercent'] ); } else { $linesLevel = ''; $linesNumber = '0' . $numSeparator . '0'; $linesBar = ''; $data['linesExecutedPercentAsString'] = 'n/a'; } $template->setVar( [ 'icon' => isset($data['icon']) ? $data['icon'] : '', 'crap' => isset($data['crap']) ? $data['crap'] : '', 'name' => $data['name'], 'lines_bar' => $linesBar, 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 'lines_level' => $linesLevel, 'lines_number' => $linesNumber, 'methods_bar' => $methodsBar, 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 'methods_level' => $methodsLevel, 'methods_number' => $methodsNumber, 'classes_bar' => $classesBar, 'classes_tested_percent' => isset($data['testedClassesPercentAsString']) ? $data['testedClassesPercentAsString'] : '', 'classes_level' => $classesLevel, 'classes_number' => $classesNumber ] ); return $template->render(); } protected function setCommonTemplateVariables(\Text_Template $template, AbstractNode $node) { $template->setVar( [ 'id' => $node->getId(), 'full_path' => $node->getPath(), 'path_to_root' => $this->getPathToRoot($node), 'breadcrumbs' => $this->getBreadcrumbs($node), 'date' => $this->date, 'version' => $this->version, 'runtime' => $this->getRuntimeString(), 'generator' => $this->generator, 'low_upper_bound' => $this->lowUpperBound, 'high_lower_bound' => $this->highLowerBound ] ); } protected function getBreadcrumbs(AbstractNode $node) { $breadcrumbs = ''; $path = $node->getPathAsArray(); $pathToRoot = []; $max = count($path); if ($node instanceof FileNode) { $max--; } for ($i = 0; $i < $max; $i++) { $pathToRoot[] = str_repeat('../', $i); } foreach ($path as $step) { if ($step !== $node) { $breadcrumbs .= $this->getInactiveBreadcrumb( $step, array_pop($pathToRoot) ); } else { $breadcrumbs .= $this->getActiveBreadcrumb($step); } } return $breadcrumbs; } protected function getActiveBreadcrumb(AbstractNode $node) { $buffer = sprintf( '
  • %s
  • ' . "\n", $node->getName() ); if ($node instanceof DirectoryNode) { $buffer .= '
  • (Dashboard)
  • ' . "\n"; } return $buffer; } protected function getInactiveBreadcrumb(AbstractNode $node, $pathToRoot) { return sprintf( '
  • %s
  • ' . "\n", $pathToRoot, $node->getName() ); } protected function getPathToRoot(AbstractNode $node) { $id = $node->getId(); $depth = substr_count($id, '/'); if ($id != 'index' && $node instanceof DirectoryNode) { $depth++; } return str_repeat('../', $depth); } protected function getCoverageBar($percent) { $level = $this->getColorLevel($percent); $template = new \Text_Template( $this->templatePath . 'coverage_bar.html', '{{', '}}' ); $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); return $template->render(); } protected function getColorLevel($percent) { if ($percent <= $this->lowUpperBound) { return 'danger'; } elseif ($percent > $this->lowUpperBound && $percent < $this->highLowerBound) { return 'warning'; } else { return 'success'; } } private function getRuntimeString() { $runtime = new Runtime; $buffer = sprintf( '%s %s', $runtime->getVendorUrl(), $runtime->getName(), $runtime->getVersion() ); if ($runtime->hasXdebug() && !$runtime->hasPHPDBGCodeCoverage()) { $buffer .= sprintf( ' with Xdebug %s', phpversion('xdebug') ); } return $buffer; } } generator = $generator; $this->highLowerBound = $highLowerBound; $this->lowUpperBound = $lowUpperBound; $this->templatePath = __DIR__ . '/Renderer/Template/'; } public function process(CodeCoverage $coverage, $target) { $target = $this->getDirectory($target); $report = $coverage->getReport(); unset($coverage); if (!isset($_SERVER['REQUEST_TIME'])) { $_SERVER['REQUEST_TIME'] = time(); } $date = date('D M j G:i:s T Y', $_SERVER['REQUEST_TIME']); $dashboard = new Dashboard( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $directory = new Directory( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $file = new File( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $directory->render($report, $target . 'index.html'); $dashboard->render($report, $target . 'dashboard.html'); foreach ($report as $node) { $id = $node->getId(); if ($node instanceof DirectoryNode) { if (!file_exists($target . $id)) { mkdir($target . $id, 0777, true); } $directory->render($node, $target . $id . '/index.html'); $dashboard->render($node, $target . $id . '/dashboard.html'); } else { $dir = dirname($target . $id); if (!file_exists($dir)) { mkdir($dir, 0777, true); } $file->render($node, $target . $id . '.html'); } } $this->copyFiles($target); } private function copyFiles($target) { $dir = $this->getDirectory($target . '.css'); file_put_contents( $dir . 'bootstrap.min.css', str_replace( 'url(../fonts/', 'url(../.fonts/', file_get_contents($this->templatePath . 'css/bootstrap.min.css') ) ); copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); copy($this->templatePath . 'css/style.css', $dir . 'style.css'); $dir = $this->getDirectory($target . '.fonts'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.eot', $dir . 'glyphicons-halflings-regular.eot'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.svg', $dir . 'glyphicons-halflings-regular.svg'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.ttf', $dir . 'glyphicons-halflings-regular.ttf'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff', $dir . 'glyphicons-halflings-regular.woff'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff2', $dir . 'glyphicons-halflings-regular.woff2'); $dir = $this->getDirectory($target . '.js'); copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js'); copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js'); copy($this->templatePath . 'js/holder.min.js', $dir . 'holder.min.js'); copy($this->templatePath . 'js/html5shiv.min.js', $dir . 'html5shiv.min.js'); copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js'); copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js'); copy($this->templatePath . 'js/respond.min.js', $dir . 'respond.min.js'); copy($this->templatePath . 'js/file.js', $dir . 'file.js'); } private function getDirectory($directory) { if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) { $directory .= DIRECTORY_SEPARATOR; } if (is_dir($directory)) { return $directory; } if (@mkdir($directory, 0777, true)) { return $directory; } throw new RuntimeException( sprintf( 'Directory "%s" does not exist.', $directory ) ); } } threshold = $threshold; } public function process(CodeCoverage $coverage, $target = null, $name = null) { $document = new \DOMDocument('1.0', 'UTF-8'); $document->formatOutput = true; $root = $document->createElement('crap_result'); $document->appendChild($root); $project = $document->createElement('project', is_string($name) ? $name : ''); $root->appendChild($project); $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s', (int) $_SERVER['REQUEST_TIME']))); $stats = $document->createElement('stats'); $methodsNode = $document->createElement('methods'); $report = $coverage->getReport(); unset($coverage); $fullMethodCount = 0; $fullCrapMethodCount = 0; $fullCrapLoad = 0; $fullCrap = 0; foreach ($report as $item) { $namespace = 'global'; if (!$item instanceof File) { continue; } $file = $document->createElement('file'); $file->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { $crapLoad = $this->getCrapLoad($method['crap'], $method['ccn'], $method['coverage']); $fullCrap += $method['crap']; $fullCrapLoad += $crapLoad; $fullMethodCount++; if ($method['crap'] >= $this->threshold) { $fullCrapMethodCount++; } $methodNode = $document->createElement('method'); if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $methodNode->appendChild($document->createElement('package', $namespace)); $methodNode->appendChild($document->createElement('className', $className)); $methodNode->appendChild($document->createElement('methodName', $methodName)); $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('crap', $this->roundValue($method['crap']))); $methodNode->appendChild($document->createElement('complexity', $method['ccn'])); $methodNode->appendChild($document->createElement('coverage', $this->roundValue($method['coverage']))); $methodNode->appendChild($document->createElement('crapLoad', round($crapLoad))); $methodsNode->appendChild($methodNode); } } } $stats->appendChild($document->createElement('name', 'Method Crap Stats')); $stats->appendChild($document->createElement('methodCount', $fullMethodCount)); $stats->appendChild($document->createElement('crapMethodCount', $fullCrapMethodCount)); $stats->appendChild($document->createElement('crapLoad', round($fullCrapLoad))); $stats->appendChild($document->createElement('totalCrap', $fullCrap)); if ($fullMethodCount > 0) { $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount); } else { $crapMethodPercent = 0; } $stats->appendChild($document->createElement('crapMethodPercent', $crapMethodPercent)); $root->appendChild($stats); $root->appendChild($methodsNode); $buffer = $document->saveXML(); if ($target !== null) { if (!is_dir(dirname($target))) { mkdir(dirname($target), 0777, true); } file_put_contents($target, $buffer); } return $buffer; } private function getCrapLoad($crapValue, $cyclomaticComplexity, $coveragePercent) { $crapLoad = 0; if ($crapValue >= $this->threshold) { $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); $crapLoad += $cyclomaticComplexity / $this->threshold; } return $crapLoad; } private function roundValue($value) { return round($value, 2); } } selectDriver(); } if ($filter === null) { $filter = new Filter; } $this->driver = $driver; $this->filter = $filter; $this->wizard = new Wizard; } public function getReport() { if ($this->report === null) { $builder = new Builder; $this->report = $builder->build($this); } return $this->report; } public function clear() { $this->isInitialized = false; $this->currentId = null; $this->data = []; $this->tests = []; $this->report = null; } public function filter() { return $this->filter; } public function getData($raw = false) { if (!$raw && $this->addUncoveredFilesFromWhitelist) { $this->addUncoveredFilesFromWhitelist(); } return $this->data; } public function setData(array $data) { $this->data = $data; $this->report = null; } public function getTests() { return $this->tests; } public function setTests(array $tests) { $this->tests = $tests; } public function start($id, $clear = false) { if (!is_bool($clear)) { throw InvalidArgumentException::create( 1, 'boolean' ); } if ($clear) { $this->clear(); } if ($this->isInitialized === false) { $this->initializeData(); } $this->currentId = $id; $this->driver->start($this->shouldCheckForDeadAndUnused); } public function stop($append = true, $linesToBeCovered = [], array $linesToBeUsed = []) { if (!is_bool($append)) { throw InvalidArgumentException::create( 1, 'boolean' ); } if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) { throw InvalidArgumentException::create( 2, 'array or false' ); } $data = $this->driver->stop(); $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); $this->currentId = null; return $data; } public function append(array $data, $id = null, $append = true, $linesToBeCovered = [], array $linesToBeUsed = []) { if ($id === null) { $id = $this->currentId; } if ($id === null) { throw new RuntimeException; } $this->applyListsFilter($data); $this->applyIgnoredLinesFilter($data); $this->initializeFilesThatAreSeenTheFirstTime($data); if (!$append) { return; } if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') { $this->applyCoversAnnotationFilter( $data, $linesToBeCovered, $linesToBeUsed ); } if (empty($data)) { return; } $size = 'unknown'; $status = null; if ($id instanceof TestCase) { $_size = $id->getSize(); if ($_size == \PHPUnit\Util\Test::SMALL) { $size = 'small'; } elseif ($_size == \PHPUnit\Util\Test::MEDIUM) { $size = 'medium'; } elseif ($_size == \PHPUnit\Util\Test::LARGE) { $size = 'large'; } $status = $id->getStatus(); $id = get_class($id) . '::' . $id->getName(); } elseif ($id instanceof PhptTestCase) { $size = 'large'; $id = $id->getName(); } $this->tests[$id] = ['size' => $size, 'status' => $status]; foreach ($data as $file => $lines) { if (!$this->filter->isFile($file)) { continue; } foreach ($lines as $k => $v) { if ($v == Driver::LINE_EXECUTED) { if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) { $this->data[$file][$k][] = $id; } } } } $this->report = null; } public function merge(CodeCoverage $that) { $this->filter->setWhitelistedFiles( array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) ); foreach ($that->data as $file => $lines) { if (!isset($this->data[$file])) { if (!$this->filter->isFiltered($file)) { $this->data[$file] = $lines; } continue; } foreach ($lines as $line => $data) { if ($data !== null) { if (!isset($this->data[$file][$line])) { $this->data[$file][$line] = $data; } else { $this->data[$file][$line] = array_unique( array_merge($this->data[$file][$line], $data) ); } } } } $this->tests = array_merge($this->tests, $that->getTests()); $this->report = null; } public function setCacheTokens($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->cacheTokens = $flag; } public function getCacheTokens() { return $this->cacheTokens; } public function setCheckForUnintentionallyCoveredCode($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->checkForUnintentionallyCoveredCode = $flag; } public function setForceCoversAnnotation($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->forceCoversAnnotation = $flag; } public function setCheckForMissingCoversAnnotation($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->checkForMissingCoversAnnotation = $flag; } public function setCheckForUnexecutedCoveredCode($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->checkForUnexecutedCoveredCode = $flag; } public function setMapTestClassNameToCoveredClassName($flag) { } public function setAddUncoveredFilesFromWhitelist($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->addUncoveredFilesFromWhitelist = $flag; } public function setProcessUncoveredFilesFromWhitelist($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->processUncoveredFilesFromWhitelist = $flag; } public function setDisableIgnoredLines($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->disableIgnoredLines = $flag; } public function setIgnoreDeprecatedCode($flag) { if (!is_bool($flag)) { throw InvalidArgumentException::create( 1, 'boolean' ); } $this->ignoreDeprecatedCode = $flag; } public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist) { $this->unintentionallyCoveredSubclassesWhitelist = $whitelist; } private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed) { if ($linesToBeCovered === false || ($this->forceCoversAnnotation && empty($linesToBeCovered))) { if ($this->checkForMissingCoversAnnotation) { throw new MissingCoversAnnotationException; } $data = []; return; } if (empty($linesToBeCovered)) { return; } if ($this->checkForUnintentionallyCoveredCode && (!$this->currentId instanceof TestCase || (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) { $this->performUnintentionallyCoveredCodeCheck( $data, $linesToBeCovered, $linesToBeUsed ); } if ($this->checkForUnexecutedCoveredCode) { $this->performUnexecutedCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed); } $data = array_intersect_key($data, $linesToBeCovered); foreach (array_keys($data) as $filename) { $_linesToBeCovered = array_flip($linesToBeCovered[$filename]); $data[$filename] = array_intersect_key( $data[$filename], $_linesToBeCovered ); } } private function applyListsFilter(array &$data) { foreach (array_keys($data) as $filename) { if ($this->filter->isFiltered($filename)) { unset($data[$filename]); } } } private function applyIgnoredLinesFilter(array &$data) { foreach (array_keys($data) as $filename) { if (!$this->filter->isFile($filename)) { continue; } foreach ($this->getLinesToBeIgnored($filename) as $line) { unset($data[$filename][$line]); } } } private function initializeFilesThatAreSeenTheFirstTime(array $data) { foreach ($data as $file => $lines) { if ($this->filter->isFile($file) && !isset($this->data[$file])) { $this->data[$file] = []; foreach ($lines as $k => $v) { $this->data[$file][$k] = $v == -2 ? null : []; } } } } private function addUncoveredFilesFromWhitelist() { $data = []; $uncoveredFiles = array_diff( $this->filter->getWhitelist(), array_keys($this->data) ); foreach ($uncoveredFiles as $uncoveredFile) { if (!file_exists($uncoveredFile)) { continue; } if (!$this->processUncoveredFilesFromWhitelist) { $data[$uncoveredFile] = []; $lines = count(file($uncoveredFile)); for ($i = 1; $i <= $lines; $i++) { $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED; } } } $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); } private function getLinesToBeIgnored($filename) { if (!is_string($filename)) { throw InvalidArgumentException::create( 1, 'string' ); } if (!isset($this->ignoredLines[$filename])) { $this->ignoredLines[$filename] = []; if ($this->disableIgnoredLines) { return $this->ignoredLines[$filename]; } $ignore = false; $stop = false; $lines = file($filename); $numLines = count($lines); foreach ($lines as $index => $line) { if (!trim($line)) { $this->ignoredLines[$filename][] = $index + 1; } } if ($this->cacheTokens) { $tokens = \PHP_Token_Stream_CachingFactory::get($filename); } else { $tokens = new \PHP_Token_Stream($filename); } $classes = array_merge($tokens->getClasses(), $tokens->getTraits()); $tokens = $tokens->tokens(); foreach ($tokens as $token) { switch (get_class($token)) { case \PHP_Token_COMMENT::class: case \PHP_Token_DOC_COMMENT::class: $_token = trim($token); $_line = trim($lines[$token->getLine() - 1]); if ($_token == '// @codeCoverageIgnore' || $_token == '//@codeCoverageIgnore') { $ignore = true; $stop = true; } elseif ($_token == '// @codeCoverageIgnoreStart' || $_token == '//@codeCoverageIgnoreStart') { $ignore = true; } elseif ($_token == '// @codeCoverageIgnoreEnd' || $_token == '//@codeCoverageIgnoreEnd') { $stop = true; } if (!$ignore) { $start = $token->getLine(); $end = $start + substr_count($token, "\n"); if (0 !== strpos($_token, $_line)) { $start++; } for ($i = $start; $i < $end; $i++) { $this->ignoredLines[$filename][] = $i; } if (isset($lines[$i - 1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i - 1]), -2)) { $this->ignoredLines[$filename][] = $i; } } break; case \PHP_Token_INTERFACE::class: case \PHP_Token_TRAIT::class: case \PHP_Token_CLASS::class: case \PHP_Token_FUNCTION::class: $docblock = $token->getDocblock(); $this->ignoredLines[$filename][] = $token->getLine(); if (strpos($docblock, '@codeCoverageIgnore') || ($this->ignoreDeprecatedCode && strpos($docblock, '@deprecated'))) { $endLine = $token->getEndLine(); for ($i = $token->getLine(); $i <= $endLine; $i++) { $this->ignoredLines[$filename][] = $i; } } elseif ($token instanceof \PHP_Token_INTERFACE || $token instanceof \PHP_Token_TRAIT || $token instanceof \PHP_Token_CLASS) { if (empty($classes[$token->getName()]['methods'])) { for ($i = $token->getLine(); $i <= $token->getEndLine(); $i++) { $this->ignoredLines[$filename][] = $i; } } else { $firstMethod = array_shift( $classes[$token->getName()]['methods'] ); do { $lastMethod = array_pop( $classes[$token->getName()]['methods'] ); } while ($lastMethod !== null && substr($lastMethod['signature'], 0, 18) == 'anonymous function'); if ($lastMethod === null) { $lastMethod = $firstMethod; } for ($i = $token->getLine(); $i < $firstMethod['startLine']; $i++) { $this->ignoredLines[$filename][] = $i; } for ($i = $token->getEndLine(); $i > $lastMethod['endLine']; $i--) { $this->ignoredLines[$filename][] = $i; } } } break; case \PHP_Token_ENUM::class: $this->ignoredLines[$filename][] = $token->getLine(); break; case \PHP_Token_NAMESPACE::class: $this->ignoredLines[$filename][] = $token->getEndLine(); case \PHP_Token_DECLARE::class: case \PHP_Token_OPEN_TAG::class: case \PHP_Token_CLOSE_TAG::class: case \PHP_Token_USE::class: $this->ignoredLines[$filename][] = $token->getLine(); break; } if ($ignore) { $this->ignoredLines[$filename][] = $token->getLine(); if ($stop) { $ignore = false; $stop = false; } } } $this->ignoredLines[$filename][] = $numLines + 1; $this->ignoredLines[$filename] = array_unique( $this->ignoredLines[$filename] ); sort($this->ignoredLines[$filename]); } return $this->ignoredLines[$filename]; } private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed) { $allowedLines = $this->getAllowedLines( $linesToBeCovered, $linesToBeUsed ); $unintentionallyCoveredUnits = []; foreach ($data as $file => $_data) { foreach ($_data as $line => $flag) { if ($flag == 1 && !isset($allowedLines[$file][$line])) { $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line); } } } $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits); if (!empty($unintentionallyCoveredUnits)) { throw new UnintentionallyCoveredCodeException( $unintentionallyCoveredUnits ); } } private function performUnexecutedCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed) { $executedCodeUnits = $this->coverageToCodeUnits($data); $message = ''; foreach ($this->linesToCodeUnits($linesToBeCovered) as $codeUnit) { if (!in_array($codeUnit, $executedCodeUnits)) { $message .= sprintf( '- %s is expected to be executed (@covers) but was not executed' . "\n", $codeUnit ); } } foreach ($this->linesToCodeUnits($linesToBeUsed) as $codeUnit) { if (!in_array($codeUnit, $executedCodeUnits)) { $message .= sprintf( '- %s is expected to be executed (@uses) but was not executed' . "\n", $codeUnit ); } } if (!empty($message)) { throw new CoveredCodeNotExecutedException($message); } } private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed) { $allowedLines = []; foreach (array_keys($linesToBeCovered) as $file) { if (!isset($allowedLines[$file])) { $allowedLines[$file] = []; } $allowedLines[$file] = array_merge( $allowedLines[$file], $linesToBeCovered[$file] ); } foreach (array_keys($linesToBeUsed) as $file) { if (!isset($allowedLines[$file])) { $allowedLines[$file] = []; } $allowedLines[$file] = array_merge( $allowedLines[$file], $linesToBeUsed[$file] ); } foreach (array_keys($allowedLines) as $file) { $allowedLines[$file] = array_flip( array_unique($allowedLines[$file]) ); } return $allowedLines; } private function selectDriver() { $runtime = new Runtime; if (!$runtime->canCollectCodeCoverage()) { throw new RuntimeException('No code coverage driver available'); } if ($runtime->isHHVM()) { return new HHVM; } elseif ($runtime->isPHPDBG()) { return new PHPDBG; } else { return new Xdebug; } } private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits) { $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits); sort($unintentionallyCoveredUnits); foreach (array_keys($unintentionallyCoveredUnits) as $k => $v) { $unit = explode('::', $unintentionallyCoveredUnits[$k]); if (count($unit) != 2) { continue; } $class = new \ReflectionClass($unit[0]); foreach ($this->unintentionallyCoveredSubclassesWhitelist as $whitelisted) { if ($class->isSubclassOf($whitelisted)) { unset($unintentionallyCoveredUnits[$k]); break; } } } return array_values($unintentionallyCoveredUnits); } protected function initializeData() { $this->isInitialized = true; if ($this->processUncoveredFilesFromWhitelist) { $this->shouldCheckForDeadAndUnused = false; $this->driver->start(true); foreach ($this->filter->getWhitelist() as $file) { if ($this->filter->isFile($file)) { include_once($file); } } $data = []; $coverage = $this->driver->stop(); foreach ($coverage as $file => $fileCoverage) { if ($this->filter->isFiltered($file)) { continue; } foreach (array_keys($fileCoverage) as $key) { if ($fileCoverage[$key] == Driver::LINE_EXECUTED) { $fileCoverage[$key] = Driver::LINE_NOT_EXECUTED; } } $data[$file] = $fileCoverage; } $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); } } private function coverageToCodeUnits(array $data) { $codeUnits = []; foreach ($data as $filename => $lines) { foreach ($lines as $line => $flag) { if ($flag == 1) { $codeUnits[] = $this->wizard->lookup($filename, $line); } } } return array_unique($codeUnits); } private function linesToCodeUnits(array $data) { $codeUnits = []; foreach ($data as $filename => $lines) { foreach ($lines as $line) { $codeUnits[] = $this->wizard->lookup($filename, $line); } } return array_unique($codeUnits); } } 0) { $percent = ($a / $b) * 100; } if ($asString) { $format = $fixedWidth ? '%6.2F%%' : '%01.2F%%'; return sprintf($format, $percent); } return $percent; } } unintentionallyCoveredUnits = $unintentionallyCoveredUnits; parent::__construct($this->toString()); } public function getUnintentionallyCoveredUnits() { return $this->unintentionallyCoveredUnits; } private function toString() { $message = ''; foreach ($this->unintentionallyCoveredUnits as $unit) { $message .= '- ' . $unit . "\n"; } return $message; } } =') && !ini_get('xdebug.coverage_enable')) { throw new RuntimeException( 'xdebug.coverage_enable=On has to be set in php.ini' ); } } public function start($determineUnusedAndDead = true) { if ($determineUnusedAndDead) { xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); } else { xdebug_start_code_coverage(); } } public function stop() { $data = xdebug_get_code_coverage(); xdebug_stop_code_coverage(); return $this->cleanup($data); } private function cleanup(array $data) { foreach (array_keys($data) as $file) { unset($data[$file][0]); if (strpos($file, 'xdebug://debug-eval') !== 0 && file_exists($file)) { $numLines = $this->getNumberOfLinesInFile($file); foreach (array_keys($data[$file]) as $line) { if ($line > $numLines) { unset($data[$file][$line]); } } } } return $data; } private function getNumberOfLinesInFile($file) { if (!isset($this->cacheNumLines[$file])) { $buffer = file_get_contents($file); $lines = substr_count($buffer, "\n"); if (substr($buffer, -1) !== "\n") { $lines++; } $this->cacheNumLines[$file] = $lines; } return $this->cacheNumLines[$file]; } } $newFiles] ); } else { $sourceLines = []; } } foreach ($sourceLines as $file => $lines) { foreach ($lines as $lineNo => $numExecuted) { $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; } } $fetchedLines = array_merge($fetchedLines, $sourceLines); return $this->detectExecutedLines($fetchedLines, $dbgData); } private function detectExecutedLines(array $sourceLines, array $dbgData) { foreach ($dbgData as $file => $coveredLines) { foreach ($coveredLines as $lineNo => $numExecuted) { if (isset($sourceLines[$file][$lineNo])) { $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; } } } return $sourceLines; } } getVersion(); } return self::$version; } } {modifier} function {reference}{method_name}({arguments_decl}){return_delim}{return_type} { $arguments = array({arguments_call}); $count = func_num_args(); if ($count > {arguments_count}) { $_arguments = func_get_args(); for ($i = {arguments_count}; $i < $count; $i++) { $arguments[] = $_arguments[$i]; } } $this->__phpunit_getInvocationMocker()->invoke( new PHPUnit_Framework_MockObject_Invocation_Object( '{class_name}', '{method_name}', $arguments, '{return_type}', $this, {clone_arguments} ) ); return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $arguments); } {modifier} function {reference}{method_name}({arguments_decl}){return_delim}{return_type} {{deprecation} $arguments = array({arguments_call}); $count = func_num_args(); if ($count > {arguments_count}) { $_arguments = func_get_args(); for ($i = {arguments_count}; $i < $count; $i++) { $arguments[] = $_arguments[$i]; } } $this->__phpunit_getInvocationMocker()->invoke( new PHPUnit_Framework_MockObject_Invocation_Object( '{class_name}', '{method_name}', $arguments, '{return_type}', $this, {clone_arguments} ) ); } {modifier} function {reference}{method_name}({arguments_decl}){return_delim}{return_type} { $arguments = array({arguments_call}); $count = func_num_args(); if ($count > {arguments_count}) { $_arguments = func_get_args(); for ($i = {arguments_count}; $i < $count; $i++) { $arguments[] = $_arguments[$i]; } } $this->__phpunit_getInvocationMocker()->invoke( new PHPUnit_Framework_MockObject_Invocation_Object( '{class_name}', '{method_name}', $arguments, '{return_type}', $this, {clone_arguments} ) ); call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $arguments); } {prologue}class {class_name} { use {trait_name}; } {modifier} function {reference}{method_name}({arguments_decl}){return_delim}{return_type} { throw new PHPUnit_Framework_MockObject_BadMethodCallException('Static method "{method_name}" cannot be invoked on mock object'); } public function __clone() { $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationMocker(); } {prologue}{class_declaration} { private $__phpunit_invocationMocker; private $__phpunit_originalObject; private $__phpunit_configurable = {configurable}; {clone}{mocked_methods} public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) { return $this->__phpunit_getInvocationMocker()->expects($matcher); } {method} public function __phpunit_setOriginalObject($originalObject) { $this->__phpunit_originalObject = $originalObject; } public function __phpunit_getInvocationMocker() { if ($this->__phpunit_invocationMocker === null) { $this->__phpunit_invocationMocker = new PHPUnit_Framework_MockObject_InvocationMocker($this->__phpunit_configurable); } return $this->__phpunit_invocationMocker; } public function __phpunit_hasMatchers() { return $this->__phpunit_getInvocationMocker()->hasMatchers(); } public function __phpunit_verify($unsetInvocationMocker = true) { $this->__phpunit_getInvocationMocker()->verify(); if ($unsetInvocationMocker) { $this->__phpunit_invocationMocker = null; } } }{epilogue} public function method() { $any = new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount; $expects = $this->expects($any); return call_user_func_array(array($expects, 'method'), func_get_args()); } {modifier} function {reference}{method_name}({arguments_decl}){return_delim}{return_type} {{deprecation} $arguments = array({arguments_call}); $count = func_num_args(); if ($count > {arguments_count}) { $_arguments = func_get_args(); for ($i = {arguments_count}; $i < $count; $i++) { $arguments[] = $_arguments[$i]; } } $result = $this->__phpunit_getInvocationMocker()->invoke( new PHPUnit_Framework_MockObject_Invocation_Object( '{class_name}', '{method_name}', $arguments, '{return_type}', $this, {clone_arguments} ) ); return $result; } public function {method_name}({arguments}) { } @trigger_error({deprecation}, E_USER_DEPRECATED); {namespace}class {class_name} extends \SoapClient { public function __construct($wsdl, array $options) { parent::__construct('{wsdl}', $options); } {methods}} public function __clone() { $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationMocker(); parent::__clone(); } collection = $collection; $this->matcher = new PHPUnit_Framework_MockObject_Matcher( $invocationMatcher ); $this->collection->addMatcher($this->matcher); $this->configurableMethods = $configurableMethods; } public function getMatcher() { return $this->matcher; } public function id($id) { $this->collection->registerId($id, $this); return $this; } public function will(PHPUnit_Framework_MockObject_Stub $stub) { $this->matcher->stub = $stub; return $this; } public function willReturn($value, ...$nextValues) { $stub = count($nextValues) === 0 ? new PHPUnit_Framework_MockObject_Stub_Return($value) : new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls( array_merge([$value], $nextValues) ); return $this->will($stub); } public function willReturnReference(&$reference) { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnReference($reference); return $this->will($stub); } public function willReturnMap(array $valueMap) { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnValueMap( $valueMap ); return $this->will($stub); } public function willReturnArgument($argumentIndex) { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnArgument( $argumentIndex ); return $this->will($stub); } public function willReturnCallback($callback) { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnCallback( $callback ); return $this->will($stub); } public function willReturnSelf() { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnSelf; return $this->will($stub); } public function willReturnOnConsecutiveCalls(...$values) { $stub = new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($values); return $this->will($stub); } public function willThrowException(Exception $exception) { $stub = new PHPUnit_Framework_MockObject_Stub_Exception($exception); return $this->will($stub); } public function after($id) { $this->matcher->afterMatchBuilderId = $id; return $this; } private function canDefineParameters() { if ($this->matcher->methodNameMatcher === null) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'Method name matcher is not defined, cannot define parameter ' . 'matcher without one' ); } if ($this->matcher->parametersMatcher !== null) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'Parameter matcher is already defined, cannot redefine' ); } } public function with(...$arguments) { $this->canDefineParameters(); $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_Parameters($arguments); return $this; } public function withConsecutive(...$arguments) { $this->canDefineParameters(); $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters($arguments); return $this; } public function withAnyParameters() { $this->canDefineParameters(); $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters; return $this; } public function method($constraint) { if ($this->matcher->methodNameMatcher !== null) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'Method name matcher is already defined, cannot redefine' ); } if (is_string($constraint) && !in_array(strtolower($constraint), $this->configurableMethods)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static', $constraint ) ); } $this->matcher->methodNameMatcher = new PHPUnit_Framework_MockObject_Matcher_MethodName($constraint); return $this; } } invocationMatcher = $invocationMatcher; } public function toString() { $list = []; if ($this->invocationMatcher !== null) { $list[] = $this->invocationMatcher->toString(); } if ($this->methodNameMatcher !== null) { $list[] = 'where ' . $this->methodNameMatcher->toString(); } if ($this->parametersMatcher !== null) { $list[] = 'and ' . $this->parametersMatcher->toString(); } if ($this->afterMatchBuilderId !== null) { $list[] = 'after ' . $this->afterMatchBuilderId; } if ($this->stub !== null) { $list[] = 'will ' . $this->stub->toString(); } return implode(' ', $list); } public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { if ($this->invocationMatcher === null) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'No invocation matcher is set' ); } if ($this->methodNameMatcher === null) { throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set'); } if ($this->afterMatchBuilderId !== null) { $builder = $invocation->object ->__phpunit_getInvocationMocker() ->lookupId($this->afterMatchBuilderId); if (!$builder) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'No builder found for match builder identification <%s>', $this->afterMatchBuilderId ) ); } $matcher = $builder->getMatcher(); if ($matcher && $matcher->invocationMatcher->hasBeenInvoked()) { $this->afterMatchBuilderIsInvoked = true; } } $this->invocationMatcher->invoked($invocation); try { if ($this->parametersMatcher !== null && !$this->parametersMatcher->matches($invocation)) { $this->parametersMatcher->verify(); } } catch (ExpectationFailedException $e) { throw new ExpectationFailedException( sprintf( "Expectation failed for %s when %s\n%s", $this->methodNameMatcher->toString(), $this->invocationMatcher->toString(), $e->getMessage() ), $e->getComparisonFailure() ); } if ($this->stub) { return $this->stub->invoke($invocation); } return $invocation->generateReturnValue(); } public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { if ($this->afterMatchBuilderId !== null) { $builder = $invocation->object ->__phpunit_getInvocationMocker() ->lookupId($this->afterMatchBuilderId); if (!$builder) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'No builder found for match builder identification <%s>', $this->afterMatchBuilderId ) ); } $matcher = $builder->getMatcher(); if (!$matcher) { return false; } if (!$matcher->invocationMatcher->hasBeenInvoked()) { return false; } } if ($this->invocationMatcher === null) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'No invocation matcher is set' ); } if ($this->methodNameMatcher === null) { throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set'); } if (!$this->invocationMatcher->matches($invocation)) { return false; } try { if (!$this->methodNameMatcher->matches($invocation)) { return false; } } catch (ExpectationFailedException $e) { throw new ExpectationFailedException( sprintf( "Expectation failed for %s when %s\n%s", $this->methodNameMatcher->toString(), $this->invocationMatcher->toString(), $e->getMessage() ), $e->getComparisonFailure() ); } return true; } public function verify() { if ($this->invocationMatcher === null) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'No invocation matcher is set' ); } if ($this->methodNameMatcher === null) { throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set'); } try { $this->invocationMatcher->verify(); if ($this->parametersMatcher === null) { $this->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters; } $invocationIsAny = $this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount; $invocationIsNever = $this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_InvokedCount && $this->invocationMatcher->isNever(); if (!$invocationIsAny && !$invocationIsNever) { $this->parametersMatcher->verify(); } } catch (ExpectationFailedException $e) { throw new ExpectationFailedException( sprintf( "Expectation failed for %s when %s.\n%s", $this->methodNameMatcher->toString(), $this->invocationMatcher->toString(), TestFailure::exceptionToString($e) ) ); } } public function hasMatchers() { if ($this->invocationMatcher !== null && !$this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) { return true; } return false; } } configurableMethods = $configurableMethods; } public function addMatcher(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) { $this->matchers[] = $matcher; } public function hasMatchers() { foreach ($this->matchers as $matcher) { if ($matcher->hasMatchers()) { return true; } } return false; } public function lookupId($id) { if (isset($this->builderMap[$id])) { return $this->builderMap[$id]; } return; } public function registerId($id, PHPUnit_Framework_MockObject_Builder_Match $builder) { if (isset($this->builderMap[$id])) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'Match builder with id <' . $id . '> is already registered.' ); } $this->builderMap[$id] = $builder; } public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) { return new PHPUnit_Framework_MockObject_Builder_InvocationMocker( $this, $matcher, $this->configurableMethods ); } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { $exception = null; $hasReturnValue = false; $returnValue = null; foreach ($this->matchers as $match) { try { if ($match->matches($invocation)) { $value = $match->invoked($invocation); if (!$hasReturnValue) { $returnValue = $value; $hasReturnValue = true; } } } catch (Exception $e) { $exception = $e; } } if ($exception !== null) { throw $exception; } if ($hasReturnValue) { return $returnValue; } elseif (strtolower($invocation->methodName) == '__tostring') { return ''; } return $invocation->generateReturnValue(); } public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { foreach ($this->matchers as $matcher) { if (!$matcher->matches($invocation)) { return false; } } return true; } public function verify() { foreach ($this->matchers as $matcher) { $matcher->verify(); } } } requiredInvocations = $requiredInvocations; } public function toString() { return 'invoked at least ' . $this->requiredInvocations . ' times'; } public function verify() { $count = $this->getInvocationCount(); if ($count < $this->requiredInvocations) { throw new ExpectationFailedException( 'Expected invocation at least ' . $this->requiredInvocations . ' times but it occurred ' . $count . ' time(s).' ); } } } expectedCount = $expectedCount; } public function isNever() { return $this->expectedCount == 0; } public function toString() { return 'invoked ' . $this->expectedCount . ' time(s)'; } public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { parent::invoked($invocation); $count = $this->getInvocationCount(); if ($count > $this->expectedCount) { $message = $invocation->toString() . ' '; switch ($this->expectedCount) { case 0: { $message .= 'was not expected to be called.'; } break; case 1: { $message .= 'was not expected to be called more than once.'; } break; default: { $message .= sprintf( 'was not expected to be called more than %d times.', $this->expectedCount ); } } throw new ExpectationFailedException($message); } } public function verify() { $count = $this->getInvocationCount(); if ($count !== $this->expectedCount) { throw new ExpectationFailedException( sprintf( 'Method was expected to be called %d times, ' . 'actually called %d times.', $this->expectedCount, $count ) ); } } } constraint = $constraint; } public function toString() { return 'method name ' . $this->constraint->toString(); } public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { return $this->constraint->evaluate($invocation->methodName, '', true); } } invocations); } public function getInvocations() { return $this->invocations; } public function hasBeenInvoked() { return count($this->invocations) > 0; } public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->invocations[] = $invocation; } public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { return true; } } allowedInvocations = $allowedInvocations; } public function toString() { return 'invoked at most ' . $this->allowedInvocations . ' times'; } public function verify() { $count = $this->getInvocationCount(); if ($count > $this->allowedInvocations) { throw new ExpectationFailedException( 'Expected invocation at most ' . $this->allowedInvocations . ' times but it occurred ' . $count . ' time(s).' ); } } } $parameters) { foreach ($parameters as $parameter) { if (!$parameter instanceof Constraint) { $parameter = new IsEqual($parameter); } $this->parameterGroups[$index][] = $parameter; } } } public function toString() { $text = 'with consecutive parameters'; return $text; } public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->invocations[] = $invocation; $callIndex = count($this->invocations) - 1; $this->verifyInvocation($invocation, $callIndex); return false; } public function verify() { foreach ($this->invocations as $callIndex => $invocation) { $this->verifyInvocation($invocation, $callIndex); } } private function verifyInvocation(PHPUnit_Framework_MockObject_Invocation $invocation, $callIndex) { if (isset($this->parameterGroups[$callIndex])) { $parameters = $this->parameterGroups[$callIndex]; } else { return; } if ($invocation === null) { throw new ExpectationFailedException( 'Mocked method does not exist.' ); } if (count($invocation->parameters) < count($parameters)) { throw new ExpectationFailedException( sprintf( 'Parameter count for invocation %s is too low.', $invocation->toString() ) ); } foreach ($parameters as $i => $parameter) { $parameter->evaluate( $invocation->parameters[$i], sprintf( 'Parameter %s for invocation #%d %s does not match expected ' . 'value.', $i, $callIndex, $invocation->toString() ) ); } } } getInvocationCount(); if ($count < 1) { throw new ExpectationFailedException( 'Expected invocation at least once but it never occurred.' ); } } } parameters[] = $parameter; } } public function toString() { $text = 'with parameter'; foreach ($this->parameters as $index => $parameter) { if ($index > 0) { $text .= ' and'; } $text .= ' ' . $index . ' ' . $parameter->toString(); } return $text; } public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->invocation = $invocation; $this->parameterVerificationResult = null; try { $this->parameterVerificationResult = $this->verify(); return $this->parameterVerificationResult; } catch (ExpectationFailedException $e) { $this->parameterVerificationResult = $e; throw $this->parameterVerificationResult; } } public function verify() { if (isset($this->parameterVerificationResult)) { return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); } if ($this->invocation === null) { throw new ExpectationFailedException( 'Mocked method does not exist.' ); } if (count($this->invocation->parameters) < count($this->parameters)) { $message = 'Parameter count for invocation %s is too low.'; if (count($this->parameters) === 1 && get_class($this->parameters[0]) === IsAnything::class) { $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; } throw new ExpectationFailedException( sprintf($message, $this->invocation->toString()) ); } foreach ($this->parameters as $i => $parameter) { $parameter->evaluate( $this->invocation->parameters[$i], sprintf( 'Parameter %s for invocation %s does not match expected ' . 'value.', $i, $this->invocation->toString() ) ); } return true; } private function guardAgainstDuplicateEvaluationOfParameterConstraints() { if ($this->parameterVerificationResult instanceof Exception) { throw $this->parameterVerificationResult; } return (bool) $this->parameterVerificationResult; } } sequenceIndex = $sequenceIndex; } public function toString() { return 'invoked at sequence index ' . $this->sequenceIndex; } public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->currentIndex++; return $this->currentIndex == $this->sequenceIndex; } public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { } public function verify() { if ($this->currentIndex < $this->sequenceIndex) { throw new ExpectationFailedException( sprintf( 'The expected invocation at index %s was never reached.', $this->sequenceIndex ) ); } } } testCase = $testCase; $this->type = $type; $this->generator = new PHPUnit_Framework_MockObject_Generator; } public function getMock() { $object = $this->generator->getMock( $this->type, $this->methods, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->cloneArguments, $this->callOriginalMethods, $this->proxyTarget, $this->allowMockingUnknownTypes ); $this->testCase->registerMockObject($object); return $object; } public function getMockForAbstractClass() { $object = $this->generator->getMockForAbstractClass( $this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments ); $this->testCase->registerMockObject($object); return $object; } public function getMockForTrait() { $object = $this->generator->getMockForTrait( $this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments ); $this->testCase->registerMockObject($object); return $object; } public function setMethods(array $methods = null) { $this->methods = $methods; return $this; } public function setMethodsExcept(array $methods = []) { $this->methodsExcept = $methods; $this->setMethods( array_diff( $this->generator->getClassMethods($this->type), $this->methodsExcept ) ); return $this; } public function setConstructorArgs(array $args) { $this->constructorArgs = $args; return $this; } public function setMockClassName($name) { $this->mockClassName = $name; return $this; } public function disableOriginalConstructor() { $this->originalConstructor = false; return $this; } public function enableOriginalConstructor() { $this->originalConstructor = true; return $this; } public function disableOriginalClone() { $this->originalClone = false; return $this; } public function enableOriginalClone() { $this->originalClone = true; return $this; } public function disableAutoload() { $this->autoload = false; return $this; } public function enableAutoload() { $this->autoload = true; return $this; } public function disableArgumentCloning() { $this->cloneArguments = false; return $this; } public function enableArgumentCloning() { $this->cloneArguments = true; return $this; } public function enableProxyingToOriginalMethods() { $this->callOriginalMethods = true; return $this; } public function disableProxyingToOriginalMethods() { $this->callOriginalMethods = false; $this->proxyTarget = null; return $this; } public function setProxyTarget($object) { $this->proxyTarget = $object; return $this; } public function allowMockingUnknownTypes() { $this->allowMockingUnknownTypes = true; return $this; } public function disallowMockingUnknownTypes() { $this->allowMockingUnknownTypes = false; return $this; } } true, '__DIR__' => true, '__FILE__' => true, '__FUNCTION__' => true, '__LINE__' => true, '__METHOD__' => true, '__NAMESPACE__' => true, '__TRAIT__' => true, '__clone' => true, '__halt_compiler' => true, ]; public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true) { if (!is_array($type) && !is_string($type)) { throw InvalidArgumentHelper::factory(1, 'array or string'); } if (!is_string($mockClassName)) { throw InvalidArgumentHelper::factory(4, 'string'); } if (!is_array($methods) && !is_null($methods)) { throw InvalidArgumentHelper::factory(2, 'array', $methods); } if ($type === 'Traversable' || $type === '\\Traversable') { $type = 'Iterator'; } if (is_array($type)) { $type = array_unique( array_map( function ($type) { if ($type === 'Traversable' || $type === '\\Traversable' || $type === '\\Iterator') { return 'Iterator'; } return $type; }, $type ) ); } if (!$allowMockingUnknownTypes) { if (is_array($type)) { foreach ($type as $_type) { if (!class_exists($_type, $callAutoload) && !interface_exists($_type, $callAutoload)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Cannot stub or mock class or interface "%s" which does not exist', $_type ) ); } } } else { if (!class_exists($type, $callAutoload) && !interface_exists($type, $callAutoload) ) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Cannot stub or mock class or interface "%s" which does not exist', $type ) ); } } } if (null !== $methods) { foreach ($methods as $method) { if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Cannot stub or mock method with invalid name "%s"', $method ) ); } } if ($methods != array_unique($methods)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")', implode(', ', $methods), implode(', ', array_unique(array_diff_assoc($methods, array_unique($methods)))) ) ); } } if ($mockClassName != '' && class_exists($mockClassName, false)) { $reflect = new ReflectionClass($mockClassName); if (!$reflect->implementsInterface('PHPUnit_Framework_MockObject_MockObject')) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Class "%s" already exists.', $mockClassName ) ); } } if ($callOriginalConstructor === false && $callOriginalMethods === true) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'Proxying to original methods requires invoking the original constructor' ); } $mock = $this->generate( $type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); return $this->getObject( $mock['code'], $mock['mockClassName'], $type, $callOriginalConstructor, $callAutoload, $arguments, $callOriginalMethods, $proxyTarget ); } private function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = [], $callOriginalMethods = false, $proxyTarget = null) { $this->evalClass($code, $className); if ($callOriginalConstructor && is_string($type) && !interface_exists($type, $callAutoload)) { if (count($arguments) == 0) { $object = new $className; } else { $class = new ReflectionClass($className); $object = $class->newInstanceArgs($arguments); } } else { try { $instantiator = new Instantiator; $object = $instantiator->instantiate($className); } catch (InstantiatorUnexpectedValueException $exception) { if ($exception->getPrevious()) { $exception = $exception->getPrevious(); } throw new PHPUnit_Framework_MockObject_RuntimeException( $exception->getMessage() ); } catch (InstantiatorInvalidArgumentException $exception) { throw new PHPUnit_Framework_MockObject_RuntimeException( $exception->getMessage() ); } } if ($callOriginalMethods) { if (!is_object($proxyTarget)) { if (count($arguments) == 0) { $proxyTarget = new $type; } else { $class = new ReflectionClass($type); $proxyTarget = $class->newInstanceArgs($arguments); } } $object->__phpunit_setOriginalObject($proxyTarget); } return $object; } private function evalClass($code, $className) { if (!class_exists($className, false)) { eval($code); } } public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) { if (!is_string($originalClassName)) { throw InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($mockClassName)) { throw InvalidArgumentHelper::factory(3, 'string'); } if (class_exists($originalClassName, $callAutoload) || interface_exists($originalClassName, $callAutoload)) { $reflector = new ReflectionClass($originalClassName); $methods = $mockedMethods; foreach ($reflector->getMethods() as $method) { if ($method->isAbstract() && !in_array($method->getName(), $methods)) { $methods[] = $method->getName(); } } if (empty($methods)) { $methods = null; } return $this->getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments ); } else { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf('Class "%s" does not exist.', $originalClassName) ); } } public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) { if (!is_string($traitName)) { throw InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($mockClassName)) { throw InvalidArgumentHelper::factory(3, 'string'); } if (!trait_exists($traitName, $callAutoload)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Trait "%s" does not exist.', $traitName ) ); } $className = $this->generateClassName( $traitName, '', 'Trait_' ); $classTemplate = $this->getTemplate('trait_class.tpl'); $classTemplate->setVar( [ 'prologue' => 'abstract ', 'class_name' => $className['className'], 'trait_name' => $traitName ] ); $this->evalClass( $classTemplate->render(), $className['className'] ); return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); } public function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) { if (!is_string($traitName)) { throw InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($traitClassName)) { throw InvalidArgumentHelper::factory(3, 'string'); } if (!trait_exists($traitName, $callAutoload)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Trait "%s" does not exist.', $traitName ) ); } $className = $this->generateClassName( $traitName, $traitClassName, 'Trait_' ); $classTemplate = $this->getTemplate('trait_class.tpl'); $classTemplate->setVar( [ 'prologue' => '', 'class_name' => $className['className'], 'trait_name' => $traitName ] ); return $this->getObject( $classTemplate->render(), $className['className'] ); } public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) { if (is_array($type)) { sort($type); } if ($mockClassName == '') { $key = md5( is_array($type) ? implode('_', $type) : $type . serialize($methods) . serialize($callOriginalClone) . serialize($cloneArguments) . serialize($callOriginalMethods) ); if (isset(self::$cache[$key])) { return self::$cache[$key]; } } $mock = $this->generateMock( $type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); if (isset($key)) { self::$cache[$key] = $mock; } return $mock; } public function generateClassFromWsdl($wsdlFile, $className, array $methods = [], array $options = []) { if (!extension_loaded('soap')) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'The SOAP extension is required to generate a mock object from WSDL.' ); } $options = array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); $client = new SoapClient($wsdlFile, $options); $_methods = array_unique($client->__getFunctions()); unset($client); sort($_methods); $methodTemplate = $this->getTemplate('wsdl_method.tpl'); $methodsBuffer = ''; foreach ($_methods as $method) { $nameStart = strpos($method, ' ') + 1; $nameEnd = strpos($method, '('); $name = substr($method, $nameStart, $nameEnd - $nameStart); if (empty($methods) || in_array($name, $methods)) { $args = explode( ',', substr( $method, $nameEnd + 1, strpos($method, ')') - $nameEnd - 1 ) ); $numArgs = count($args); for ($i = 0; $i < $numArgs; $i++) { $args[$i] = substr($args[$i], strpos($args[$i], '$')); } $methodTemplate->setVar( [ 'method_name' => $name, 'arguments' => implode(', ', $args) ] ); $methodsBuffer .= $methodTemplate->render(); } } $optionsBuffer = 'array('; foreach ($options as $key => $value) { $optionsBuffer .= $key . ' => ' . $value; } $optionsBuffer .= ')'; $classTemplate = $this->getTemplate('wsdl_class.tpl'); $namespace = ''; if (strpos($className, '\\') !== false) { $parts = explode('\\', $className); $className = array_pop($parts); $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; } $classTemplate->setVar( [ 'namespace' => $namespace, 'class_name' => $className, 'wsdl' => $wsdlFile, 'options' => $optionsBuffer, 'methods' => $methodsBuffer ] ); return $classTemplate->render(); } private function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) { $methodReflections = []; $classTemplate = $this->getTemplate('mocked_class.tpl'); $additionalInterfaces = []; $cloneTemplate = ''; $isClass = false; $isInterface = false; $isMultipleInterfaces = false; if (is_array($type)) { foreach ($type as $_type) { if (!interface_exists($_type, $callAutoload)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Interface "%s" does not exist.', $_type ) ); } $isMultipleInterfaces = true; $additionalInterfaces[] = $_type; $typeClass = new ReflectionClass($this->generateClassName( $_type, $mockClassName, 'Mock_' )['fullClassName'] ); foreach ($this->getClassMethods($_type) as $method) { if (in_array($method, $methods)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Duplicate method "%s" not allowed.', $method ) ); } $methodReflections[$method] = $typeClass->getMethod($method); $methods[] = $method; } } } $mockClassName = $this->generateClassName( $type, $mockClassName, 'Mock_' ); if (class_exists($mockClassName['fullClassName'], $callAutoload)) { $isClass = true; } elseif (interface_exists($mockClassName['fullClassName'], $callAutoload)) { $isInterface = true; } if (!$isClass && !$isInterface) { $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; if (!empty($mockClassName['namespaceName'])) { $prologue = 'namespace ' . $mockClassName['namespaceName'] . " {\n\n" . $prologue . "}\n\n" . "namespace {\n\n"; $epilogue = "\n\n}"; } $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); } else { $class = new ReflectionClass($mockClassName['fullClassName']); if ($class->isFinal()) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Class "%s" is declared "final" and cannot be mocked.', $mockClassName['fullClassName'] ) ); } if ($class->hasMethod('__clone')) { $cloneMethod = $class->getMethod('__clone'); if (!$cloneMethod->isFinal()) { if ($callOriginalClone && !$isInterface) { $cloneTemplate = $this->getTemplate('unmocked_clone.tpl'); } else { $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); } } } else { $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); } } if (is_object($cloneTemplate)) { $cloneTemplate = $cloneTemplate->render(); } if (is_array($methods) && empty($methods) && ($isClass || $isInterface)) { $methods = $this->getClassMethods($mockClassName['fullClassName']); } if (!is_array($methods)) { $methods = []; } $mockedMethods = ''; $configurable = []; foreach ($methods as $methodName) { if ($methodName != '__construct' && $methodName != '__clone') { $configurable[] = strtolower($methodName); } } if (isset($class)) { if ($isInterface && $class->implementsInterface(Traversable::class) && !$class->implementsInterface(Iterator::class) && !$class->implementsInterface(IteratorAggregate::class)) { $additionalInterfaces[] = Iterator::class; $methods = array_merge($methods, $this->getClassMethods(Iterator::class)); } foreach ($methods as $methodName) { try { $method = $class->getMethod($methodName); if ($this->canMockMethod($method)) { $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( $method, $cloneArguments, $callOriginalMethods ); } } catch (ReflectionException $e) { $mockedMethods .= $this->generateMockedMethodDefinition( $mockClassName['fullClassName'], $methodName, $cloneArguments ); } } } elseif ($isMultipleInterfaces) { foreach ($methods as $methodName) { if ($this->canMockMethod($methodReflections[$methodName])) { $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( $methodReflections[$methodName], $cloneArguments, $callOriginalMethods ); } } } else { foreach ($methods as $methodName) { $mockedMethods .= $this->generateMockedMethodDefinition( $mockClassName['fullClassName'], $methodName, $cloneArguments ); } } $method = ''; if (!in_array('method', $methods) && (!isset($class) || !$class->hasMethod('method'))) { $methodTemplate = $this->getTemplate('mocked_class_method.tpl'); $method = $methodTemplate->render(); } $classTemplate->setVar( [ 'prologue' => isset($prologue) ? $prologue : '', 'epilogue' => isset($epilogue) ? $epilogue : '', 'class_declaration' => $this->generateMockClassDeclaration( $mockClassName, $isInterface, $additionalInterfaces ), 'clone' => $cloneTemplate, 'mock_class_name' => $mockClassName['className'], 'mocked_methods' => $mockedMethods, 'method' => $method, 'configurable' => '[' . implode(', ', array_map(function ($m) { return '\'' . $m . '\''; }, $configurable)) . ']' ] ); return [ 'code' => $classTemplate->render(), 'mockClassName' => $mockClassName['className'] ]; } private function generateClassName($type, $className, $prefix) { if (is_array($type)) { $type = implode('_', $type); } if ($type[0] == '\\') { $type = substr($type, 1); } $classNameParts = explode('\\', $type); if (count($classNameParts) > 1) { $type = array_pop($classNameParts); $namespaceName = implode('\\', $classNameParts); $fullClassName = $namespaceName . '\\' . $type; } else { $namespaceName = ''; $fullClassName = $type; } if ($className == '') { do { $className = $prefix . $type . '_' . substr(md5(microtime()), 0, 8); } while (class_exists($className, false)); } return [ 'className' => $className, 'originalClassName' => $type, 'fullClassName' => $fullClassName, 'namespaceName' => $namespaceName ]; } private function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = []) { $buffer = 'class '; $additionalInterfaces[] = 'PHPUnit_Framework_MockObject_MockObject'; $interfaces = implode(', ', $additionalInterfaces); if ($isInterface) { $buffer .= sprintf( '%s implements %s', $mockClassName['className'], $interfaces ); if (!in_array($mockClassName['originalClassName'], $additionalInterfaces)) { $buffer .= ', '; if (!empty($mockClassName['namespaceName'])) { $buffer .= $mockClassName['namespaceName'] . '\\'; } $buffer .= $mockClassName['originalClassName']; } } else { $buffer .= sprintf( '%s extends %s%s implements %s', $mockClassName['className'], !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', $mockClassName['originalClassName'], $interfaces ); } return $buffer; } private function generateMockedMethodDefinitionFromExisting(ReflectionMethod $method, $cloneArguments, $callOriginalMethods) { if ($method->isPrivate()) { $modifier = 'private'; } elseif ($method->isProtected()) { $modifier = 'protected'; } else { $modifier = 'public'; } if ($method->isStatic()) { $modifier .= ' static'; } if ($method->returnsReference()) { $reference = '&'; } else { $reference = ''; } if ($method->hasReturnType()) { $returnType = (string) $method->getReturnType(); } else { $returnType = ''; } if (preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $method->getDocComment(), $deprecation)) { $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); } else { $deprecation = false; } return $this->generateMockedMethodDefinition( $method->getDeclaringClass()->getName(), $method->getName(), $cloneArguments, $modifier, $this->getMethodParameters($method), $this->getMethodParameters($method, true), $returnType, $reference, $callOriginalMethods, $method->isStatic(), $deprecation, $method->hasReturnType() && $method->getReturnType()->allowsNull() ); } private function generateMockedMethodDefinition($className, $methodName, $cloneArguments = true, $modifier = 'public', $argumentsForDeclaration = '', $argumentsForCall = '', $returnType = '', $reference = '', $callOriginalMethods = false, $static = false, $deprecation = false, $allowsReturnNull = false) { if ($static) { $templateFile = 'mocked_static_method.tpl'; } else { if ($returnType === 'void') { $templateFile = sprintf( '%s_method_void.tpl', $callOriginalMethods ? 'proxied' : 'mocked' ); } else { $templateFile = sprintf( '%s_method.tpl', $callOriginalMethods ? 'proxied' : 'mocked' ); } } if ($returnType === 'self') { $returnType = $className; } if (false !== $deprecation) { $deprecation = "The $className::$methodName method is deprecated ($deprecation)."; $deprecationTemplate = $this->getTemplate('deprecation.tpl'); $deprecationTemplate->setVar( [ 'deprecation' => var_export($deprecation, true), ] ); $deprecation = $deprecationTemplate->render(); } $template = $this->getTemplate($templateFile); $template->setVar( [ 'arguments_decl' => $argumentsForDeclaration, 'arguments_call' => $argumentsForCall, 'return_delim' => $returnType ? ': ' : '', 'return_type' => $allowsReturnNull ? '?' . $returnType : $returnType, 'arguments_count' => !empty($argumentsForCall) ? count(explode(',', $argumentsForCall)) : 0, 'class_name' => $className, 'method_name' => $methodName, 'modifier' => $modifier, 'reference' => $reference, 'clone_arguments' => $cloneArguments ? 'true' : 'false', 'deprecation' => $deprecation ] ); return $template->render(); } private function canMockMethod(ReflectionMethod $method) { if ($method->isConstructor() || $method->isFinal() || $method->isPrivate() || $this->isMethodNameBlacklisted($method->getName())) { return false; } return true; } private function isMethodNameBlacklisted($name) { return isset($this->blacklistedMethodNames[$name]); } private function getMethodParameters(ReflectionMethod $method, $forCall = false) { $parameters = []; foreach ($method->getParameters() as $i => $parameter) { $name = '$' . $parameter->getName(); if ($name === '$' || $name === '$...') { $name = '$arg' . $i; } if ($parameter->isVariadic()) { if ($forCall) { continue; } else { $name = '...' . $name; } } $nullable = ''; $default = ''; $reference = ''; $typeDeclaration = ''; if (!$forCall) { if ($parameter->hasType() && (string) $parameter->getType() !== 'self') { if (version_compare(PHP_VERSION, '7.1', '>=') && $parameter->allowsNull() && !$parameter->isVariadic()) { $nullable = '?'; } $typeDeclaration = (string) $parameter->getType() . ' '; } elseif ($parameter->isArray()) { $typeDeclaration = 'array '; } elseif ($parameter->isCallable()) { $typeDeclaration = 'callable '; } else { try { $class = $parameter->getClass(); } catch (ReflectionException $e) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Cannot mock %s::%s() because a class or ' . 'interface used in the signature is not loaded', $method->getDeclaringClass()->getName(), $method->getName() ), 0, $e ); } if ($class !== null) { $typeDeclaration = $class->getName() . ' '; } } if (!$parameter->isVariadic()) { if ($parameter->isDefaultValueAvailable()) { $value = $parameter->getDefaultValue(); $default = ' = ' . var_export($value, true); } elseif ($parameter->isOptional()) { $default = ' = null'; } } } if ($parameter->isPassedByReference()) { $reference = '&'; } $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; } return implode(', ', $parameters); } public function getClassMethods($className) { $class = new ReflectionClass($className); $methods = []; foreach ($class->getMethods() as $method) { if ($method->isPublic() || $method->isAbstract()) { $methods[] = $method->getName(); } } return $methods; } private function getTemplate($template) { $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; if (!isset(self::$templates[$filename])) { self::$templates[$filename] = new Text_Template($filename); } return self::$templates[$filename]; } } value = &$value; } } callback = $callback; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { return call_user_func_array($this->callback, $invocation->parameters); } public function toString() { if (is_array($this->callback)) { if (is_object($this->callback[0])) { $class = get_class($this->callback[0]); $type = '->'; } else { $class = $this->callback[0]; $type = '::'; } return sprintf( 'return result of user defined callback %s%s%s() with the ' . 'passed arguments', $class, $type, $this->callback[1] ); } else { return 'return result of user defined callback ' . $this->callback . ' with the passed arguments'; } } } value = $value; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { return $this->value; } public function toString() { $exporter = new Exporter; return sprintf( 'return user-specified value %s', $exporter->export($this->value) ); } } argumentIndex = $argumentIndex; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { if (isset($invocation->parameters[$this->argumentIndex])) { return $invocation->parameters[$this->argumentIndex]; } else { return; } } public function toString() { return sprintf('return argument #%d', $this->argumentIndex); } } object; } public function toString() { return 'return the current object'; } } valueMap = $valueMap; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { $parameterCount = count($invocation->parameters); foreach ($this->valueMap as $map) { if (!is_array($map) || $parameterCount != count($map) - 1) { continue; } $return = array_pop($map); if ($invocation->parameters === $map) { return $return; } } return; } public function toString() { return 'return value from a map'; } } stack = $stack; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->value = array_shift($this->stack); if ($this->value instanceof PHPUnit_Framework_MockObject_Stub) { $this->value = $this->value->invoke($invocation); } return $this->value; } public function toString() { $exporter = new Exporter; return sprintf( 'return user-specified value %s', $exporter->export($this->value) ); } } exception = $exception; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { throw $this->exception; } public function toString() { $exporter = new Exporter; return sprintf( 'raise user-specified exception %s', $exporter->export($this->exception) ); } } true, 'SQLite' => true, 'sqlite3' => true, 'tidy' => true, 'xmlwriter' => true, 'xsl' => true ]; protected static $uncloneableClasses = [ 'Closure', 'COMPersistHelper', 'IteratorIterator', 'RecursiveIteratorIterator', 'SplFileObject', 'PDORow', 'ZipArchive' ]; public $className; public $methodName; public $parameters; public $returnType; public $returnTypeNullable = false; public function __construct($className, $methodName, array $parameters, $returnType, $cloneObjects = false) { $this->className = $className; $this->methodName = $methodName; $this->parameters = $parameters; if (strpos($returnType, '?') === 0) { $returnType = substr($returnType, 1); $this->returnTypeNullable = true; } $this->returnType = $returnType; if (!$cloneObjects) { return; } foreach ($this->parameters as $key => $value) { if (is_object($value)) { $this->parameters[$key] = $this->cloneObject($value); } } } public function toString() { $exporter = new Exporter; return sprintf( '%s::%s(%s)%s', $this->className, $this->methodName, implode( ', ', array_map( [$exporter, 'shortenedExport'], $this->parameters ) ), $this->returnType ? sprintf(': %s', $this->returnType) : '' ); } public function generateReturnValue() { switch ($this->returnType) { case '': return; case 'string': return $this->returnTypeNullable ? null : ''; case 'float': return $this->returnTypeNullable ? null : 0.0; case 'int': return $this->returnTypeNullable ? null : 0; case 'bool': return $this->returnTypeNullable ? null : false; case 'array': return $this->returnTypeNullable ? null : []; case 'void': return; case 'callable': case 'Closure': return function () { }; case 'Traversable': case 'Generator': $generator = function () { yield; }; return $generator(); default: if ($this->returnTypeNullable) { return; } $generator = new PHPUnit_Framework_MockObject_Generator; return $generator->getMock($this->returnType, [], [], '', false); } } protected function cloneObject($original) { $cloneable = null; $object = new ReflectionObject($original); if ($object->isInternal() && isset(self::$uncloneableExtensions[$object->getExtensionName()])) { $cloneable = false; } if ($cloneable === null) { foreach (self::$uncloneableClasses as $class) { if ($original instanceof $class) { $cloneable = false; break; } } } if ($cloneable === null && method_exists($object, 'isCloneable')) { $cloneable = $object->isCloneable(); } if ($cloneable === null && $object->hasMethod('__clone')) { $method = $object->getMethod('__clone'); $cloneable = $method->isPublic(); } if ($cloneable === null) { $cloneable = true; } if ($cloneable) { try { return clone $original; } catch (Exception $e) { return $original; } } else { return $original; } } } object = $object; } } text = $text; $this->line = $line; $this->tokenStream = $tokenStream; $this->id = $id; } public function __toString() { return $this->text; } public function getLine() { return $this->line; } } abstract class PHP_TokenWithScope extends PHP_Token { protected $endTokenId; public function getDocblock() { $tokens = $this->tokenStream->tokens(); $currentLineNumber = $tokens[$this->id]->getLine(); $prevLineNumber = $currentLineNumber - 1; for ($i = $this->id - 1; $i; $i--) { if (!isset($tokens[$i])) { return; } if ($tokens[$i] instanceof PHP_Token_FUNCTION || $tokens[$i] instanceof PHP_Token_CLASS || $tokens[$i] instanceof PHP_Token_TRAIT) { break; } $line = $tokens[$i]->getLine(); if ($line == $currentLineNumber || ($line == $prevLineNumber && $tokens[$i] instanceof PHP_Token_WHITESPACE)) { continue; } if ($line < $currentLineNumber && !$tokens[$i] instanceof PHP_Token_DOC_COMMENT) { break; } return (string)$tokens[$i]; } } public function getEndTokenId() { $block = 0; $i = $this->id; $tokens = $this->tokenStream->tokens(); while ($this->endTokenId === null && isset($tokens[$i])) { if ($tokens[$i] instanceof PHP_Token_OPEN_CURLY || $tokens[$i] instanceof PHP_Token_CURLY_OPEN) { $block++; } elseif ($tokens[$i] instanceof PHP_Token_CLOSE_CURLY) { $block--; if ($block === 0) { $this->endTokenId = $i; } } elseif (($this instanceof PHP_Token_FUNCTION || $this instanceof PHP_Token_NAMESPACE) && $tokens[$i] instanceof PHP_Token_SEMICOLON) { if ($block === 0) { $this->endTokenId = $i; } } $i++; } if ($this->endTokenId === null) { $this->endTokenId = $this->id; } return $this->endTokenId; } public function getEndLine() { return $this->tokenStream[$this->getEndTokenId()]->getLine(); } } abstract class PHP_TokenWithScopeAndVisibility extends PHP_TokenWithScope { public function getVisibility() { $tokens = $this->tokenStream->tokens(); for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_PRIVATE || $tokens[$i] instanceof PHP_Token_PROTECTED || $tokens[$i] instanceof PHP_Token_PUBLIC)) { return strtolower( str_replace('PHP_Token_', '', get_class($tokens[$i])) ); } if (isset($tokens[$i]) && !($tokens[$i] instanceof PHP_Token_STATIC || $tokens[$i] instanceof PHP_Token_FINAL || $tokens[$i] instanceof PHP_Token_ABSTRACT)) { break; } } } public function getKeywords() { $keywords = array(); $tokens = $this->tokenStream->tokens(); for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_PRIVATE || $tokens[$i] instanceof PHP_Token_PROTECTED || $tokens[$i] instanceof PHP_Token_PUBLIC)) { continue; } if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_STATIC || $tokens[$i] instanceof PHP_Token_FINAL || $tokens[$i] instanceof PHP_Token_ABSTRACT)) { $keywords[] = strtolower( str_replace('PHP_Token_', '', get_class($tokens[$i])) ); } } return implode(',', $keywords); } } abstract class PHP_Token_Includes extends PHP_Token { protected $name; protected $type; public function getName() { if ($this->name === null) { $this->process(); } return $this->name; } public function getType() { if ($this->type === null) { $this->process(); } return $this->type; } private function process() { $tokens = $this->tokenStream->tokens(); if ($tokens[$this->id+2] instanceof PHP_Token_CONSTANT_ENCAPSED_STRING) { $this->name = trim($tokens[$this->id+2], "'\""); $this->type = strtolower( str_replace('PHP_Token_', '', get_class($tokens[$this->id])) ); } } } class PHP_Token_FUNCTION extends PHP_TokenWithScopeAndVisibility { protected $arguments; protected $ccn; protected $name; protected $signature; public function getArguments() { if ($this->arguments !== null) { return $this->arguments; } $this->arguments = array(); $tokens = $this->tokenStream->tokens(); $typeDeclaration = null; $i = $this->id + 2; while (!$tokens[$i-1] instanceof PHP_Token_OPEN_BRACKET) { $i++; } while (!$tokens[$i] instanceof PHP_Token_CLOSE_BRACKET) { if ($tokens[$i] instanceof PHP_Token_STRING) { $typeDeclaration = (string)$tokens[$i]; } elseif ($tokens[$i] instanceof PHP_Token_VARIABLE) { $this->arguments[(string)$tokens[$i]] = $typeDeclaration; $typeDeclaration = null; } $i++; } return $this->arguments; } public function getName() { if ($this->name !== null) { return $this->name; } $tokens = $this->tokenStream->tokens(); for ($i = $this->id + 1; $i < count($tokens); $i++) { if ($tokens[$i] instanceof PHP_Token_STRING) { $this->name = (string)$tokens[$i]; break; } elseif ($tokens[$i] instanceof PHP_Token_AMPERSAND && $tokens[$i+1] instanceof PHP_Token_STRING) { $this->name = (string)$tokens[$i+1]; break; } elseif ($tokens[$i] instanceof PHP_Token_OPEN_BRACKET) { $this->name = 'anonymous function'; break; } } if ($this->name != 'anonymous function') { for ($i = $this->id; $i; --$i) { if ($tokens[$i] instanceof PHP_Token_NAMESPACE) { $this->name = $tokens[$i]->getName() . '\\' . $this->name; break; } if ($tokens[$i] instanceof PHP_Token_INTERFACE) { break; } } } return $this->name; } public function getCCN() { if ($this->ccn !== null) { return $this->ccn; } $this->ccn = 1; $end = $this->getEndTokenId(); $tokens = $this->tokenStream->tokens(); for ($i = $this->id; $i <= $end; $i++) { switch (get_class($tokens[$i])) { case 'PHP_Token_IF': case 'PHP_Token_ELSEIF': case 'PHP_Token_FOR': case 'PHP_Token_FOREACH': case 'PHP_Token_WHILE': case 'PHP_Token_CASE': case 'PHP_Token_CATCH': case 'PHP_Token_BOOLEAN_AND': case 'PHP_Token_LOGICAL_AND': case 'PHP_Token_BOOLEAN_OR': case 'PHP_Token_LOGICAL_OR': case 'PHP_Token_QUESTION_MARK': $this->ccn++; break; } } return $this->ccn; } public function getSignature() { if ($this->signature !== null) { return $this->signature; } if ($this->getName() == 'anonymous function') { $this->signature = 'anonymous function'; $i = $this->id + 1; } else { $this->signature = ''; $i = $this->id + 2; } $tokens = $this->tokenStream->tokens(); while (isset($tokens[$i]) && !$tokens[$i] instanceof PHP_Token_OPEN_CURLY && !$tokens[$i] instanceof PHP_Token_SEMICOLON) { $this->signature .= $tokens[$i++]; } $this->signature = trim($this->signature); return $this->signature; } } class PHP_Token_INTERFACE extends PHP_TokenWithScopeAndVisibility { protected $interfaces; public function getName() { return (string)$this->tokenStream[$this->id + 2]; } public function hasParent() { return $this->tokenStream[$this->id + 4] instanceof PHP_Token_EXTENDS; } public function getPackage() { $className = $this->getName(); $docComment = $this->getDocblock(); $result = array( 'namespace' => '', 'fullPackage' => '', 'category' => '', 'package' => '', 'subpackage' => '' ); for ($i = $this->id; $i; --$i) { if ($this->tokenStream[$i] instanceof PHP_Token_NAMESPACE) { $result['namespace'] = $this->tokenStream[$i]->getName(); break; } } if (preg_match('/@category[\s]+([\.\w]+)/', $docComment, $matches)) { $result['category'] = $matches[1]; } if (preg_match('/@package[\s]+([\.\w]+)/', $docComment, $matches)) { $result['package'] = $matches[1]; $result['fullPackage'] = $matches[1]; } if (preg_match('/@subpackage[\s]+([\.\w]+)/', $docComment, $matches)) { $result['subpackage'] = $matches[1]; $result['fullPackage'] .= '.' . $matches[1]; } if (empty($result['fullPackage'])) { $result['fullPackage'] = $this->arrayToName( explode('_', str_replace('\\', '_', $className)), '.' ); } return $result; } protected function arrayToName(array $parts, $join = '\\') { $result = ''; if (count($parts) > 1) { array_pop($parts); $result = join($join, $parts); } return $result; } public function getParent() { if (!$this->hasParent()) { return false; } $i = $this->id + 6; $tokens = $this->tokenStream->tokens(); $className = (string)$tokens[$i]; while (isset($tokens[$i+1]) && !$tokens[$i+1] instanceof PHP_Token_WHITESPACE) { $className .= (string)$tokens[++$i]; } return $className; } public function hasInterfaces() { return (isset($this->tokenStream[$this->id + 4]) && $this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) || (isset($this->tokenStream[$this->id + 8]) && $this->tokenStream[$this->id + 8] instanceof PHP_Token_IMPLEMENTS); } public function getInterfaces() { if ($this->interfaces !== null) { return $this->interfaces; } if (!$this->hasInterfaces()) { return ($this->interfaces = false); } if ($this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) { $i = $this->id + 3; } else { $i = $this->id + 7; } $tokens = $this->tokenStream->tokens(); while (!$tokens[$i+1] instanceof PHP_Token_OPEN_CURLY) { $i++; if ($tokens[$i] instanceof PHP_Token_STRING) { $this->interfaces[] = (string)$tokens[$i]; } } return $this->interfaces; } } class PHP_Token_ABSTRACT extends PHP_Token {} class PHP_Token_AMPERSAND extends PHP_Token {} class PHP_Token_AND_EQUAL extends PHP_Token {} class PHP_Token_ARRAY extends PHP_Token {} class PHP_Token_ARRAY_CAST extends PHP_Token {} class PHP_Token_AS extends PHP_Token {} class PHP_Token_AT extends PHP_Token {} class PHP_Token_BACKTICK extends PHP_Token {} class PHP_Token_BAD_CHARACTER extends PHP_Token {} class PHP_Token_BOOLEAN_AND extends PHP_Token {} class PHP_Token_BOOLEAN_OR extends PHP_Token {} class PHP_Token_BOOL_CAST extends PHP_Token {} class PHP_Token_BREAK extends PHP_Token {} class PHP_Token_CARET extends PHP_Token {} class PHP_Token_CASE extends PHP_Token {} class PHP_Token_CATCH extends PHP_Token {} class PHP_Token_CHARACTER extends PHP_Token {} class PHP_Token_CLASS extends PHP_Token_INTERFACE { public function getName() { $next = $this->tokenStream[$this->id + 1]; if ($next instanceof PHP_Token_WHITESPACE) { $next = $this->tokenStream[$this->id + 2]; } if ($next instanceof PHP_Token_STRING) { return (string) $next; } if ($next instanceof PHP_Token_OPEN_CURLY || $next instanceof PHP_Token_EXTENDS || $next instanceof PHP_Token_IMPLEMENTS) { return 'anonymous class'; } } } class PHP_Token_CLASS_C extends PHP_Token {} class PHP_Token_CLASS_NAME_CONSTANT extends PHP_Token {} class PHP_Token_CLONE extends PHP_Token {} class PHP_Token_CLOSE_BRACKET extends PHP_Token {} class PHP_Token_CLOSE_CURLY extends PHP_Token {} class PHP_Token_CLOSE_SQUARE extends PHP_Token {} class PHP_Token_CLOSE_TAG extends PHP_Token {} class PHP_Token_COLON extends PHP_Token {} class PHP_Token_COMMA extends PHP_Token {} class PHP_Token_COMMENT extends PHP_Token {} class PHP_Token_CONCAT_EQUAL extends PHP_Token {} class PHP_Token_CONST extends PHP_Token {} class PHP_Token_CONSTANT_ENCAPSED_STRING extends PHP_Token {} class PHP_Token_CONTINUE extends PHP_Token {} class PHP_Token_CURLY_OPEN extends PHP_Token {} class PHP_Token_DEC extends PHP_Token {} class PHP_Token_DECLARE extends PHP_Token {} class PHP_Token_DEFAULT extends PHP_Token {} class PHP_Token_DIV extends PHP_Token {} class PHP_Token_DIV_EQUAL extends PHP_Token {} class PHP_Token_DNUMBER extends PHP_Token {} class PHP_Token_DO extends PHP_Token {} class PHP_Token_DOC_COMMENT extends PHP_Token {} class PHP_Token_DOLLAR extends PHP_Token {} class PHP_Token_DOLLAR_OPEN_CURLY_BRACES extends PHP_Token {} class PHP_Token_DOT extends PHP_Token {} class PHP_Token_DOUBLE_ARROW extends PHP_Token {} class PHP_Token_DOUBLE_CAST extends PHP_Token {} class PHP_Token_DOUBLE_COLON extends PHP_Token {} class PHP_Token_DOUBLE_QUOTES extends PHP_Token {} class PHP_Token_ECHO extends PHP_Token {} class PHP_Token_ELSE extends PHP_Token {} class PHP_Token_ELSEIF extends PHP_Token {} class PHP_Token_EMPTY extends PHP_Token {} class PHP_Token_ENCAPSED_AND_WHITESPACE extends PHP_Token {} class PHP_Token_ENDDECLARE extends PHP_Token {} class PHP_Token_ENDFOR extends PHP_Token {} class PHP_Token_ENDFOREACH extends PHP_Token {} class PHP_Token_ENDIF extends PHP_Token {} class PHP_Token_ENDSWITCH extends PHP_Token {} class PHP_Token_ENDWHILE extends PHP_Token {} class PHP_Token_END_HEREDOC extends PHP_Token {} class PHP_Token_EQUAL extends PHP_Token {} class PHP_Token_EVAL extends PHP_Token {} class PHP_Token_EXCLAMATION_MARK extends PHP_Token {} class PHP_Token_EXIT extends PHP_Token {} class PHP_Token_EXTENDS extends PHP_Token {} class PHP_Token_FILE extends PHP_Token {} class PHP_Token_FINAL extends PHP_Token {} class PHP_Token_FOR extends PHP_Token {} class PHP_Token_FOREACH extends PHP_Token {} class PHP_Token_FUNC_C extends PHP_Token {} class PHP_Token_GLOBAL extends PHP_Token {} class PHP_Token_GT extends PHP_Token {} class PHP_Token_IF extends PHP_Token {} class PHP_Token_IMPLEMENTS extends PHP_Token {} class PHP_Token_INC extends PHP_Token {} class PHP_Token_INCLUDE extends PHP_Token_Includes {} class PHP_Token_INCLUDE_ONCE extends PHP_Token_Includes {} class PHP_Token_INLINE_HTML extends PHP_Token {} class PHP_Token_INSTANCEOF extends PHP_Token {} class PHP_Token_INT_CAST extends PHP_Token {} class PHP_Token_ISSET extends PHP_Token {} class PHP_Token_IS_EQUAL extends PHP_Token {} class PHP_Token_IS_GREATER_OR_EQUAL extends PHP_Token {} class PHP_Token_IS_IDENTICAL extends PHP_Token {} class PHP_Token_IS_NOT_EQUAL extends PHP_Token {} class PHP_Token_IS_NOT_IDENTICAL extends PHP_Token {} class PHP_Token_IS_SMALLER_OR_EQUAL extends PHP_Token {} class PHP_Token_LINE extends PHP_Token {} class PHP_Token_LIST extends PHP_Token {} class PHP_Token_LNUMBER extends PHP_Token {} class PHP_Token_LOGICAL_AND extends PHP_Token {} class PHP_Token_LOGICAL_OR extends PHP_Token {} class PHP_Token_LOGICAL_XOR extends PHP_Token {} class PHP_Token_LT extends PHP_Token {} class PHP_Token_METHOD_C extends PHP_Token {} class PHP_Token_MINUS extends PHP_Token {} class PHP_Token_MINUS_EQUAL extends PHP_Token {} class PHP_Token_MOD_EQUAL extends PHP_Token {} class PHP_Token_MULT extends PHP_Token {} class PHP_Token_MUL_EQUAL extends PHP_Token {} class PHP_Token_NEW extends PHP_Token {} class PHP_Token_NUM_STRING extends PHP_Token {} class PHP_Token_OBJECT_CAST extends PHP_Token {} class PHP_Token_OBJECT_OPERATOR extends PHP_Token {} class PHP_Token_OPEN_BRACKET extends PHP_Token {} class PHP_Token_OPEN_CURLY extends PHP_Token {} class PHP_Token_OPEN_SQUARE extends PHP_Token {} class PHP_Token_OPEN_TAG extends PHP_Token {} class PHP_Token_OPEN_TAG_WITH_ECHO extends PHP_Token {} class PHP_Token_OR_EQUAL extends PHP_Token {} class PHP_Token_PAAMAYIM_NEKUDOTAYIM extends PHP_Token {} class PHP_Token_PERCENT extends PHP_Token {} class PHP_Token_PIPE extends PHP_Token {} class PHP_Token_PLUS extends PHP_Token {} class PHP_Token_PLUS_EQUAL extends PHP_Token {} class PHP_Token_PRINT extends PHP_Token {} class PHP_Token_PRIVATE extends PHP_Token {} class PHP_Token_PROTECTED extends PHP_Token {} class PHP_Token_PUBLIC extends PHP_Token {} class PHP_Token_QUESTION_MARK extends PHP_Token {} class PHP_Token_REQUIRE extends PHP_Token_Includes {} class PHP_Token_REQUIRE_ONCE extends PHP_Token_Includes {} class PHP_Token_RETURN extends PHP_Token {} class PHP_Token_SEMICOLON extends PHP_Token {} class PHP_Token_SL extends PHP_Token {} class PHP_Token_SL_EQUAL extends PHP_Token {} class PHP_Token_SR extends PHP_Token {} class PHP_Token_SR_EQUAL extends PHP_Token {} class PHP_Token_START_HEREDOC extends PHP_Token {} class PHP_Token_STATIC extends PHP_Token {} class PHP_Token_STRING extends PHP_Token {} class PHP_Token_STRING_CAST extends PHP_Token {} class PHP_Token_STRING_VARNAME extends PHP_Token {} class PHP_Token_SWITCH extends PHP_Token {} class PHP_Token_THROW extends PHP_Token {} class PHP_Token_TILDE extends PHP_Token {} class PHP_Token_TRY extends PHP_Token {} class PHP_Token_UNSET extends PHP_Token {} class PHP_Token_UNSET_CAST extends PHP_Token {} class PHP_Token_USE extends PHP_Token {} class PHP_Token_USE_FUNCTION extends PHP_Token {} class PHP_Token_VAR extends PHP_Token {} class PHP_Token_VARIABLE extends PHP_Token {} class PHP_Token_WHILE extends PHP_Token {} class PHP_Token_WHITESPACE extends PHP_Token {} class PHP_Token_XOR_EQUAL extends PHP_Token {} class PHP_Token_HALT_COMPILER extends PHP_Token {} class PHP_Token_DIR extends PHP_Token {} class PHP_Token_GOTO extends PHP_Token {} class PHP_Token_NAMESPACE extends PHP_TokenWithScope { public function getName() { $tokens = $this->tokenStream->tokens(); $namespace = (string)$tokens[$this->id+2]; for ($i = $this->id + 3;; $i += 2) { if (isset($tokens[$i]) && $tokens[$i] instanceof PHP_Token_NS_SEPARATOR) { $namespace .= '\\' . $tokens[$i+1]; } else { break; } } return $namespace; } } class PHP_Token_NS_C extends PHP_Token {} class PHP_Token_NS_SEPARATOR extends PHP_Token {} class PHP_Token_CALLABLE extends PHP_Token {} class PHP_Token_INSTEADOF extends PHP_Token {} class PHP_Token_TRAIT extends PHP_Token_INTERFACE {} class PHP_Token_TRAIT_C extends PHP_Token {} class PHP_Token_FINALLY extends PHP_Token {} class PHP_Token_YIELD extends PHP_Token {} class PHP_Token_ELLIPSIS extends PHP_Token {} class PHP_Token_POW extends PHP_Token {} class PHP_Token_POW_EQUAL extends PHP_Token {} class PHP_Token_COALESCE extends PHP_Token {} class PHP_Token_SPACESHIP extends PHP_Token {} class PHP_Token_YIELD_FROM extends PHP_Token {} class PHP_Token_ASYNC extends PHP_Token {} class PHP_Token_AWAIT extends PHP_Token {} class PHP_Token_COMPILER_HALT_OFFSET extends PHP_Token {} class PHP_Token_ENUM extends PHP_Token {} class PHP_Token_EQUALS extends PHP_Token {} class PHP_Token_IN extends PHP_Token {} class PHP_Token_JOIN extends PHP_Token {} class PHP_Token_LAMBDA_ARROW extends PHP_Token {} class PHP_Token_LAMBDA_CP extends PHP_Token {} class PHP_Token_LAMBDA_OP extends PHP_Token {} class PHP_Token_ONUMBER extends PHP_Token {} class PHP_Token_NULLSAFE_OBJECT_OPERATOR extends PHP_Token {} class PHP_Token_SHAPE extends PHP_Token {} class PHP_Token_SUPER extends PHP_Token {} class PHP_Token_TYPE extends PHP_Token {} class PHP_Token_TYPELIST_GT extends PHP_Token {} class PHP_Token_TYPELIST_LT extends PHP_Token {} class PHP_Token_WHERE extends PHP_Token {} class PHP_Token_XHP_ATTRIBUTE extends PHP_Token {} class PHP_Token_XHP_CATEGORY extends PHP_Token {} class PHP_Token_XHP_CATEGORY_LABEL extends PHP_Token {} class PHP_Token_XHP_CHILDREN extends PHP_Token {} class PHP_Token_XHP_LABEL extends PHP_Token {} class PHP_Token_XHP_REQUIRED extends PHP_Token {} class PHP_Token_XHP_TAG_GT extends PHP_Token {} class PHP_Token_XHP_TAG_LT extends PHP_Token {} class PHP_Token_XHP_TEXT extends PHP_Token {} 'PHP_Token_OPEN_BRACKET', ')' => 'PHP_Token_CLOSE_BRACKET', '[' => 'PHP_Token_OPEN_SQUARE', ']' => 'PHP_Token_CLOSE_SQUARE', '{' => 'PHP_Token_OPEN_CURLY', '}' => 'PHP_Token_CLOSE_CURLY', ';' => 'PHP_Token_SEMICOLON', '.' => 'PHP_Token_DOT', ',' => 'PHP_Token_COMMA', '=' => 'PHP_Token_EQUAL', '<' => 'PHP_Token_LT', '>' => 'PHP_Token_GT', '+' => 'PHP_Token_PLUS', '-' => 'PHP_Token_MINUS', '*' => 'PHP_Token_MULT', '/' => 'PHP_Token_DIV', '?' => 'PHP_Token_QUESTION_MARK', '!' => 'PHP_Token_EXCLAMATION_MARK', ':' => 'PHP_Token_COLON', '"' => 'PHP_Token_DOUBLE_QUOTES', '@' => 'PHP_Token_AT', '&' => 'PHP_Token_AMPERSAND', '%' => 'PHP_Token_PERCENT', '|' => 'PHP_Token_PIPE', '$' => 'PHP_Token_DOLLAR', '^' => 'PHP_Token_CARET', '~' => 'PHP_Token_TILDE', '`' => 'PHP_Token_BACKTICK' ); protected $filename; protected $tokens = array(); protected $position = 0; protected $linesOfCode = array('loc' => 0, 'cloc' => 0, 'ncloc' => 0); protected $classes; protected $functions; protected $includes; protected $interfaces; protected $traits; protected $lineToFunctionMap = array(); public function __construct($sourceCode) { if (is_file($sourceCode)) { $this->filename = $sourceCode; $sourceCode = file_get_contents($sourceCode); } $this->scan($sourceCode); } public function __destruct() { $this->tokens = array(); } public function __toString() { $buffer = ''; foreach ($this as $token) { $buffer .= $token; } return $buffer; } public function getFilename() { return $this->filename; } protected function scan($sourceCode) { $id = 0; $line = 1; $tokens = token_get_all($sourceCode); $numTokens = count($tokens); $lastNonWhitespaceTokenWasDoubleColon = false; for ($i = 0; $i < $numTokens; ++$i) { $token = $tokens[$i]; $skip = 0; if (is_array($token)) { $name = substr(token_name($token[0]), 2); $text = $token[1]; if ($lastNonWhitespaceTokenWasDoubleColon && $name == 'CLASS') { $name = 'CLASS_NAME_CONSTANT'; } elseif ($name == 'USE' && isset($tokens[$i+2][0]) && $tokens[$i+2][0] == T_FUNCTION) { $name = 'USE_FUNCTION'; $skip = 2; } $tokenClass = 'PHP_Token_' . $name; } else { $text = $token; $tokenClass = self::$customTokens[$token]; } $this->tokens[] = new $tokenClass($text, $line, $this, $id++); $lines = substr_count($text, "\n"); $line += $lines; if ($tokenClass == 'PHP_Token_HALT_COMPILER') { break; } elseif ($tokenClass == 'PHP_Token_COMMENT' || $tokenClass == 'PHP_Token_DOC_COMMENT') { $this->linesOfCode['cloc'] += $lines + 1; } if ($name == 'DOUBLE_COLON') { $lastNonWhitespaceTokenWasDoubleColon = true; } elseif ($name != 'WHITESPACE') { $lastNonWhitespaceTokenWasDoubleColon = false; } $i += $skip; } $this->linesOfCode['loc'] = substr_count($sourceCode, "\n"); $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] - $this->linesOfCode['cloc']; } public function count() { return count($this->tokens); } public function tokens() { return $this->tokens; } public function getClasses() { if ($this->classes !== null) { return $this->classes; } $this->parse(); return $this->classes; } public function getFunctions() { if ($this->functions !== null) { return $this->functions; } $this->parse(); return $this->functions; } public function getInterfaces() { if ($this->interfaces !== null) { return $this->interfaces; } $this->parse(); return $this->interfaces; } public function getTraits() { if ($this->traits !== null) { return $this->traits; } $this->parse(); return $this->traits; } public function getIncludes($categorize = false, $category = null) { if ($this->includes === null) { $this->includes = array( 'require_once' => array(), 'require' => array(), 'include_once' => array(), 'include' => array() ); foreach ($this->tokens as $token) { switch (get_class($token)) { case 'PHP_Token_REQUIRE_ONCE': case 'PHP_Token_REQUIRE': case 'PHP_Token_INCLUDE_ONCE': case 'PHP_Token_INCLUDE': $this->includes[$token->getType()][] = $token->getName(); break; } } } if (isset($this->includes[$category])) { $includes = $this->includes[$category]; } elseif ($categorize === false) { $includes = array_merge( $this->includes['require_once'], $this->includes['require'], $this->includes['include_once'], $this->includes['include'] ); } else { $includes = $this->includes; } return $includes; } public function getFunctionForLine($line) { $this->parse(); if (isset($this->lineToFunctionMap[$line])) { return $this->lineToFunctionMap[$line]; } } protected function parse() { $this->interfaces = array(); $this->classes = array(); $this->traits = array(); $this->functions = array(); $class = array(); $classEndLine = array(); $trait = false; $traitEndLine = false; $interface = false; $interfaceEndLine = false; foreach ($this->tokens as $token) { switch (get_class($token)) { case 'PHP_Token_HALT_COMPILER': return; case 'PHP_Token_INTERFACE': $interface = $token->getName(); $interfaceEndLine = $token->getEndLine(); $this->interfaces[$interface] = array( 'methods' => array(), 'parent' => $token->getParent(), 'keywords' => $token->getKeywords(), 'docblock' => $token->getDocblock(), 'startLine' => $token->getLine(), 'endLine' => $interfaceEndLine, 'package' => $token->getPackage(), 'file' => $this->filename ); break; case 'PHP_Token_CLASS': case 'PHP_Token_TRAIT': $tmp = array( 'methods' => array(), 'parent' => $token->getParent(), 'interfaces'=> $token->getInterfaces(), 'keywords' => $token->getKeywords(), 'docblock' => $token->getDocblock(), 'startLine' => $token->getLine(), 'endLine' => $token->getEndLine(), 'package' => $token->getPackage(), 'file' => $this->filename ); if ($token instanceof PHP_Token_CLASS) { $class[] = $token->getName(); $classEndLine[] = $token->getEndLine(); if ($class[count($class)-1] != 'anonymous class') { $this->classes[$class[count($class)-1]] = $tmp; } } else { $trait = $token->getName(); $traitEndLine = $token->getEndLine(); $this->traits[$trait] = $tmp; } break; case 'PHP_Token_FUNCTION': $name = $token->getName(); $tmp = array( 'docblock' => $token->getDocblock(), 'keywords' => $token->getKeywords(), 'visibility'=> $token->getVisibility(), 'signature' => $token->getSignature(), 'startLine' => $token->getLine(), 'endLine' => $token->getEndLine(), 'ccn' => $token->getCCN(), 'file' => $this->filename ); if (empty($class) && $trait === false && $interface === false) { $this->functions[$name] = $tmp; $this->addFunctionToMap( $name, $tmp['startLine'], $tmp['endLine'] ); } elseif (!empty($class) && $class[count($class)-1] != 'anonymous class') { $this->classes[$class[count($class)-1]]['methods'][$name] = $tmp; $this->addFunctionToMap( $class[count($class)-1] . '::' . $name, $tmp['startLine'], $tmp['endLine'] ); } elseif ($trait !== false) { $this->traits[$trait]['methods'][$name] = $tmp; $this->addFunctionToMap( $trait . '::' . $name, $tmp['startLine'], $tmp['endLine'] ); } else { $this->interfaces[$interface]['methods'][$name] = $tmp; } break; case 'PHP_Token_CLOSE_CURLY': if (!empty($classEndLine) && $classEndLine[count($classEndLine)-1] == $token->getLine()) { array_pop($classEndLine); array_pop($class); } elseif ($traitEndLine !== false && $traitEndLine == $token->getLine()) { $trait = false; $traitEndLine = false; } elseif ($interfaceEndLine !== false && $interfaceEndLine == $token->getLine()) { $interface = false; $interfaceEndLine = false; } break; } } } public function getLinesOfCode() { return $this->linesOfCode; } public function rewind() { $this->position = 0; } public function valid() { return isset($this->tokens[$this->position]); } public function key() { return $this->position; } public function current() { return $this->tokens[$this->position]; } public function next() { $this->position++; } public function offsetExists($offset) { return isset($this->tokens[$offset]); } public function offsetGet($offset) { if (!$this->offsetExists($offset)) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $offset ) ); } return $this->tokens[$offset]; } public function offsetSet($offset, $value) { $this->tokens[$offset] = $value; } public function offsetUnset($offset) { if (!$this->offsetExists($offset)) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $offset ) ); } unset($this->tokens[$offset]); } public function seek($position) { $this->position = $position; if (!$this->valid()) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $this->position ) ); } } private function addFunctionToMap($name, $startLine, $endLine) { for ($line = $startLine; $line <= $endLine; $line++) { $this->lineToFunctionMap[$line] = $name; } } } setFile($file); $this->openDelimiter = $openDelimiter; $this->closeDelimiter = $closeDelimiter; } public function setFile($file) { $distFile = $file . '.dist'; if (file_exists($file)) { $this->template = file_get_contents($file); } else if (file_exists($distFile)) { $this->template = file_get_contents($distFile); } else { throw new InvalidArgumentException( 'Template file could not be loaded.' ); } } public function setVar(array $values, $merge = TRUE) { if (!$merge || empty($this->values)) { $this->values = $values; } else { $this->values = array_merge($this->values, $values); } } public function render() { $keys = array(); foreach ($this->values as $key => $value) { $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; } return str_replace($keys, $this->values, $this->template); } public function renderTo($target) { $fp = @fopen($target, 'wt'); if ($fp) { fwrite($fp, $this->render()); fclose($fp); } else { $error = error_get_last(); throw new RuntimeException( sprintf( 'Could not write to %s: %s', $target, substr( $error['message'], strpos($error['message'], ':') + 2 ) ) ); } } } variables = $variables; $this->loader = $loader; $this->assertCallback( function ($value) { return $value !== null; }, 'is missing' ); } public function notEmpty() { return $this->assertCallback( function ($value) { return strlen(trim($value)) > 0; }, 'is empty' ); } public function isInteger() { return $this->assertCallback( function ($value) { return ctype_digit($value); }, 'is not an integer' ); } public function allowedValues(array $choices) { return $this->assertCallback( function ($value) use ($choices) { return in_array($value, $choices); }, 'is not an allowed value' ); } protected function assertCallback($callback, $message = 'failed callback assertion') { if (!is_callable($callback)) { throw new InvalidCallbackException('The provided callback must be callable.'); } $variablesFailingAssertion = array(); foreach ($this->variables as $variableName) { $variableValue = $this->loader->getEnvironmentVariable($variableName); if (call_user_func($callback, $variableValue) === false) { $variablesFailingAssertion[] = $variableName." $message"; } } if (count($variablesFailingAssertion) > 0) { throw new ValidationException(sprintf( 'One or more environment variables failed assertions: %s.', implode(', ', $variablesFailingAssertion) )); } return $this; } } filePath = $this->getFilePath($path, $file); $this->loader = new Loader($this->filePath, true); } public function load() { return $this->loadData(); } public function overload() { return $this->loadData(true); } protected function getFilePath($path, $file) { if (!is_string($file)) { $file = '.env'; } $filePath = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$file; return $filePath; } protected function loadData($overload = false) { $this->loader = new Loader($this->filePath, !$overload); return $this->loader->load(); } public function required($variable) { return new Validator((array) $variable, $this->loader); } } filePath = $filePath; $this->immutable = $immutable; } public function load() { $this->ensureFileIsReadable(); $filePath = $this->filePath; $lines = $this->readLinesFromFile($filePath); foreach ($lines as $line) { if (!$this->isComment($line) && $this->looksLikeSetter($line)) { $this->setEnvironmentVariable($line); } } return $lines; } protected function ensureFileIsReadable() { if (!is_readable($this->filePath) || !is_file($this->filePath)) { throw new InvalidPathException(sprintf('Unable to read the environment file at %s.', $this->filePath)); } } protected function normaliseEnvironmentVariable($name, $value) { list($name, $value) = $this->splitCompoundStringIntoParts($name, $value); list($name, $value) = $this->sanitiseVariableName($name, $value); list($name, $value) = $this->sanitiseVariableValue($name, $value); $value = $this->resolveNestedVariables($value); return array($name, $value); } public function processFilters($name, $value) { list($name, $value) = $this->splitCompoundStringIntoParts($name, $value); list($name, $value) = $this->sanitiseVariableName($name, $value); list($name, $value) = $this->sanitiseVariableValue($name, $value); return array($name, $value); } protected function readLinesFromFile($filePath) { $autodetect = ini_get('auto_detect_line_endings'); ini_set('auto_detect_line_endings', '1'); $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); ini_set('auto_detect_line_endings', $autodetect); return $lines; } protected function isComment($line) { return strpos(ltrim($line), '#') === 0; } protected function looksLikeSetter($line) { return strpos($line, '=') !== false; } protected function splitCompoundStringIntoParts($name, $value) { if (strpos($name, '=') !== false) { list($name, $value) = array_map('trim', explode('=', $name, 2)); } return array($name, $value); } protected function sanitiseVariableValue($name, $value) { $value = trim($value); if (!$value) { return array($name, $value); } if ($this->beginsWithAQuote($value)) { $quote = $value[0]; $regexPattern = sprintf( '/^ %1$s # match a quote at the start of the value ( # capturing sub-pattern used (?: # we do not need to capture this [^%1$s\\\\] # any character other than a quote or backslash |\\\\\\\\ # or two backslashes together |\\\\%1$s # or an escaped quote e.g \" )* # as many characters that match the previous rules ) # end of the capturing sub-pattern %1$s # and the closing quote .*$ # and discard any string after the closing quote /mx', $quote ); $value = preg_replace($regexPattern, '$1', $value); $value = str_replace("\\$quote", $quote, $value); $value = str_replace('\\\\', '\\', $value); } else { $parts = explode(' #', $value, 2); $value = trim($parts[0]); if (preg_match('/\s+/', $value) > 0) { throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.'); } } return array($name, trim($value)); } protected function resolveNestedVariables($value) { if (strpos($value, '$') !== false) { $loader = $this; $value = preg_replace_callback( '/\${([a-zA-Z0-9_]+)}/', function ($matchedPatterns) use ($loader) { $nestedVariable = $loader->getEnvironmentVariable($matchedPatterns[1]); if ($nestedVariable === null) { return $matchedPatterns[0]; } else { return $nestedVariable; } }, $value ); } return $value; } protected function sanitiseVariableName($name, $value) { $name = trim(str_replace(array('export ', '\'', '"'), '', $name)); return array($name, $value); } protected function beginsWithAQuote($value) { return strpbrk($value[0], '"\'') !== false; } public function getEnvironmentVariable($name) { switch (true) { case array_key_exists($name, $_ENV): return $_ENV[$name]; case array_key_exists($name, $_SERVER): return $_SERVER[$name]; default: $value = getenv($name); return $value === false ? null : $value; } } public function setEnvironmentVariable($name, $value = null) { list($name, $value) = $this->normaliseEnvironmentVariable($name, $value); if ($this->immutable && $this->getEnvironmentVariable($name) !== null) { return; } if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) { apache_setenv($name, $value); } if (function_exists('putenv')) { putenv("$name=$value"); } $_ENV[$name] = $value; $_SERVER[$name] = $value; } public function clearEnvironmentVariable($name) { if ($this->immutable) { return; } if (function_exists('putenv')) { putenv($name); } unset($_ENV[$name], $_SERVER[$name]); } } serializeToString($manifest) ); } public function serializeToString(Manifest $manifest) { $this->startDocument(); $this->addContains($manifest->getName(), $manifest->getVersion(), $manifest->getType()); $this->addCopyright($manifest->getCopyrightInformation()); $this->addRequirements($manifest->getRequirements()); $this->addBundles($manifest->getBundledComponents()); return $this->finishDocument(); } private function startDocument() { $xmlWriter = new XMLWriter(); $xmlWriter->openMemory(); $xmlWriter->setIndent(true); $xmlWriter->setIndentString(str_repeat(' ', 4)); $xmlWriter->startDocument('1.0', 'UTF-8'); $xmlWriter->startElement('phar'); $xmlWriter->writeAttribute('xmlns', 'https://phar.io/xml/manifest/1.0'); $this->xmlWriter = $xmlWriter; } private function finishDocument() { $this->xmlWriter->endElement(); $this->xmlWriter->endDocument(); return $this->xmlWriter->outputMemory(); } private function addContains($name, Version $version, Type $type) { $this->xmlWriter->startElement('contains'); $this->xmlWriter->writeAttribute('name', $name); $this->xmlWriter->writeAttribute('version', $version->getVersionString()); switch (true) { case $type->isApplication(): { $this->xmlWriter->writeAttribute('type', 'application'); break; } case $type->isLibrary(): { $this->xmlWriter->writeAttribute('type', 'library'); break; } case $type->isExtension(): { $this->xmlWriter->writeAttribute('type', 'extension'); $this->addExtension($type->getApplicationName(), $type->getVersionConstraint()); break; } default: { $this->xmlWriter->writeAttribute('type', 'custom'); } } $this->xmlWriter->endElement(); } private function addCopyright(CopyrightInformation $copyrightInformation) { $this->xmlWriter->startElement('copyright'); foreach($copyrightInformation->getAuthors() as $author) { $this->xmlWriter->startElement('author'); $this->xmlWriter->writeAttribute('name', $author->getName()); $this->xmlWriter->writeAttribute('email', (string) $author->getEmail()); $this->xmlWriter->endElement(); } $license = $copyrightInformation->getLicense(); $this->xmlWriter->startElement('license'); $this->xmlWriter->writeAttribute('type', $license->getName()); $this->xmlWriter->writeAttribute('url', $license->getUrl()); $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); } private function addRequirements(RequirementCollection $requirementCollection) { $phpRequirement = new AnyVersionConstraint(); $extensions = []; foreach($requirementCollection as $requirement) { if ($requirement instanceof PhpVersionRequirement) { $phpRequirement = $requirement->getVersionConstraint(); continue; } if ($requirement instanceof PhpExtensionRequirement) { $extensions[] = (string) $requirement; } } $this->xmlWriter->startElement('requires'); $this->xmlWriter->startElement('php'); $this->xmlWriter->writeAttribute('version', $phpRequirement->asString()); foreach($extensions as $extension) { $this->xmlWriter->startElement('ext'); $this->xmlWriter->writeAttribute('name', $extension); $this->xmlWriter->endElement(); } $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); } private function addBundles(BundledComponentCollection $bundledComponentCollection) { if (count($bundledComponentCollection) === 0) { return; } $this->xmlWriter->startElement('bundles'); foreach($bundledComponentCollection as $bundledComponent) { $this->xmlWriter->startElement('component'); $this->xmlWriter->writeAttribute('name', $bundledComponent->getName()); $this->xmlWriter->writeAttribute('version', $bundledComponent->getVersion()->getVersionString()); $this->xmlWriter->endElement(); } $this->xmlWriter->endElement(); } private function addExtension($application, VersionConstraint $versionConstraint) { $this->xmlWriter->startElement('extension'); $this->xmlWriter->writeAttribute('for', $application); $this->xmlWriter->writeAttribute('compatible', $versionConstraint->asString()); $this->xmlWriter->endElement(); } } map( ManifestDocument::fromFile($filename) ); } catch (Exception $e) { throw new ManifestLoaderException( sprintf('Loading %s failed.', $filename), $e->getCode(), $e ); } } public static function fromPhar($filename) { return self::fromFile('phar://' . $filename . '/manifest.xml'); } public static function fromString($manifest) { try { return (new ManifestDocumentMapper())->map( ManifestDocument::fromString($manifest) ); } catch (Exception $e) { throw new ManifestLoaderException( 'Processing string failed', $e->getCode(), $e ); } } } application = $application; $this->versionConstraint = $versionConstraint; } public function getApplicationName() { return $this->application; } public function getVersionConstraint() { return $this->versionConstraint; } public function isExtension() { return true; } public function isExtensionFor(ApplicationName $name) { return $this->application->isEqual($name); } public function isCompatibleWith(ApplicationName $name, Version $version) { return $this->isExtensionFor($name) && $this->versionConstraint->complies($version); } } versionConstraint = $versionConstraint; } public function getVersionConstraint() { return $this->versionConstraint; } } ensureUrlIsValid($url); $this->url = $url; } public function __toString() { return $this->url; } private function ensureUrlIsValid($url) { if (filter_var($url, \FILTER_VALIDATE_URL) === false) { throw new InvalidUrlException; } } } name = $name; $this->email = $email; } public function getName() { return $this->name; } public function getEmail() { return $this->email; } public function __toString() { return sprintf( '%s <%s>', $this->name, $this->email ); } } name = $name; $this->url = $url; } public function getName() { return $this->name; } public function getUrl() { return $this->url; } } bundledComponents[] = $bundledComponent; } public function getBundledComponents() { return $this->bundledComponents; } public function count() { return count($this->bundledComponents); } public function getIterator() { return new BundledComponentCollectionIterator($this); } } authors = $authors; $this->license = $license; } public function getAuthors() { return $this->authors; } public function getLicense() { return $this->license; } } name = $name; $this->version = $version; $this->type = $type; $this->copyrightInformation = $copyrightInformation; $this->requirements = $requirements; $this->bundledComponents = $bundledComponents; } public function getName() { return $this->name; } public function getVersion() { return $this->version; } public function getType() { return $this->type; } public function getCopyrightInformation() { return $this->copyrightInformation; } public function getRequirements() { return $this->requirements; } public function getBundledComponents() { return $this->bundledComponents; } public function isApplication() { return $this->type->isApplication(); } public function isLibrary() { return $this->type->isLibrary(); } public function isExtension() { return $this->type->isExtension(); } public function isExtensionFor(ApplicationName $application, Version $version = null) { if (!$this->isExtension()) { return false; } $type = $this->type; if ($version !== null) { return $type->isCompatibleWith($application, $version); } return $type->isExtensionFor($application); } } requirements = $requirements->getRequirements(); } public function rewind() { $this->position = 0; } public function valid() { return $this->position < count($this->requirements); } public function key() { return $this->position; } public function current() { return $this->requirements[$this->position]; } public function next() { $this->position++; } } ensureIsString($name); $this->ensureValidFormat($name); $this->name = $name; } public function __toString() { return $this->name; } public function isEqual(ApplicationName $name) { return $this->name === $name->name; } private function ensureValidFormat($name) { if (!preg_match('#\w/\w#', $name)) { throw new InvalidApplicationNameException( sprintf('Format of name "%s" is not valid - expected: vendor/packagename', $name), InvalidApplicationNameException::InvalidFormat ); } } private function ensureIsString($name) { if (!is_string($name)) { throw new InvalidApplicationNameException( 'Name must be a string', InvalidApplicationNameException::NotAString ); } } } authors = $authors->getAuthors(); } public function rewind() { $this->position = 0; } public function valid() { return $this->position < count($this->authors); } public function key() { return $this->position; } public function current() { return $this->authors[$this->position]; } public function next() { $this->position++; } } ensureEmailIsValid($email); $this->email = $email; } public function __toString() { return $this->email; } private function ensureEmailIsValid($url) { if (filter_var($url, \FILTER_VALIDATE_EMAIL) === false) { throw new InvalidEmailException; } } } extension = $extension; } public function __toString() { return $this->extension; } } bundledComponents = $bundledComponents->getBundledComponents(); } public function rewind() { $this->position = 0; } public function valid() { return $this->position < count($this->bundledComponents); } public function key() { return $this->position; } public function current() { return $this->bundledComponents[$this->position]; } public function next() { $this->position++; } } requirements[] = $requirement; } public function getRequirements() { return $this->requirements; } public function count() { return count($this->requirements); } public function getIterator() { return new RequirementCollectionIterator($this); } } authors[] = $author; } public function getAuthors() { return $this->authors; } public function count() { return count($this->authors); } public function getIterator() { return new AuthorCollectionIterator($this); } } name = $name; $this->version = $version; } public function getName() { return $this->name; } public function getVersion() { return $this->version; } } getAttributeValue('name'); } public function getEmail() { return $this->getAttributeValue('email'); } } getChildByName('php') ); } } getCurrentElement() ); } } ensureCorrectDocumentType($dom); $this->dom = $dom; } public static function fromFile($filename) { if (!file_exists($filename)) { throw new ManifestDocumentException( sprintf('File "%s" not found', $filename) ); } return self::fromString( file_get_contents($filename) ); } public static function fromString($xmlString) { $prev = libxml_use_internal_errors(true); libxml_clear_errors(); $dom = new DOMDocument(); $dom->loadXML($xmlString); $errors = libxml_get_errors(); libxml_use_internal_errors($prev); if (count($errors) !== 0) { throw new ManifestDocumentLoadingException($errors); } return new self($dom); } public function getContainsElement() { return new ContainsElement( $this->fetchElementByName('contains') ); } public function getCopyrightElement() { return new CopyrightElement( $this->fetchElementByName('copyright') ); } public function getRequiresElement() { return new RequiresElement( $this->fetchElementByName('requires') ); } public function hasBundlesElement() { return $this->dom->getElementsByTagNameNS(self::XMLNS, 'bundles')->length === 1; } public function getBundlesElement() { return new BundlesElement( $this->fetchElementByName('bundles') ); } private function ensureCorrectDocumentType(DOMDocument $dom) { $root = $dom->documentElement; if ($root->localName !== 'phar' || $root->namespaceURI !== self::XMLNS) { throw new ManifestDocumentException('Not a phar.io manifest document'); } } private function fetchElementByName($elementName) { $element = $this->dom->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); if (!$element instanceof DOMElement) { throw new ManifestDocumentException( sprintf('Element %s missing', $elementName) ); } return $element; } } libxmlErrors = $libxmlErrors; $first = $this->libxmlErrors[0]; parent::__construct( sprintf( '%s (Line: %d / Column: %d / File: %s)', $first->message, $first->line, $first->column, $first->file ), $first->code ); } public function getLibxmlErrors() { return $this->libxmlErrors; } } getAttributeValue('type'); } public function getUrl() { return $this->getAttributeValue('url'); } } getAttributeValue('name'); } public function getVersion() { return $this->getAttributeValue('version'); } } getCurrentElement() ); } } getAttributeValue('for'); } public function getCompatible() { return $this->getAttributeValue('compatible'); } } getCurrentElement() ); } } nodeList = $nodeList; $this->position = 0; } abstract public function current(); protected function getCurrentElement() { return $this->nodeList->item($this->position); } public function next() { $this->position++; } public function key() { return $this->position; } public function valid() { return $this->position < $this->nodeList->length; } public function rewind() { $this->position = 0; } } getAttributeValue('version'); } public function hasExtElements() { return $this->hasChild('ext'); } public function getExtElements() { return new ExtElementCollection( $this->getChildrenByName('ext') ); } } getChildrenByName('author') ); } public function getLicenseElement() { return new LicenseElement( $this->getChildByName('license') ); } } element = $element; } protected function getAttributeValue($name) { if (!$this->element->hasAttribute($name)) { throw new ManifestElementException( sprintf( 'Attribute %s not set on element %s', $name, $this->element->localName ) ); } return $this->element->getAttribute($name); } protected function getChildByName($elementName) { $element = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->item(0); if (!$element instanceof DOMElement) { throw new ManifestElementException( sprintf('Element %s missing', $elementName) ); } return $element; } protected function getChildrenByName($elementName) { $elementList = $this->element->getElementsByTagNameNS(self::XMLNS, $elementName); if ($elementList->length === 0) { throw new ManifestElementException( sprintf('Element(s) %s missing', $elementName) ); } return $elementList; } protected function hasChild($elementName) { return $this->element->getElementsByTagNameNS(self::XMLNS, $elementName)->length !== 0; } } getAttributeValue('name'); } } getAttributeValue('name'); } public function getVersion() { return $this->getAttributeValue('version'); } public function getType() { return $this->getAttributeValue('type'); } public function getExtensionElement() { return new ExtensionElement( $this->getChildByName('extension') ); } } getChildrenByName('component') ); } } getContainsElement(); $type = $this->mapType($contains); $copyright = $this->mapCopyright($document->getCopyrightElement()); $requirements = $this->mapRequirements($document->getRequiresElement()); $bundledComponents = $this->mapBundledComponents($document); return new Manifest( new ApplicationName($contains->getName()), new Version($contains->getVersion()), $type, $copyright, $requirements, $bundledComponents ); } catch (VersionException $e) { throw new ManifestDocumentMapperException($e->getMessage(), $e->getCode(), $e); } catch (Exception $e) { throw new ManifestDocumentMapperException($e->getMessage(), $e->getCode(), $e); } } private function mapType(ContainsElement $contains) { switch ($contains->getType()) { case 'application': return Type::application(); case 'library': return Type::library(); case 'extension': return $this->mapExtension($contains->getExtensionElement()); } throw new ManifestDocumentMapperException( sprintf('Unsupported type %s', $contains->getType()) ); } private function mapCopyright(CopyrightElement $copyright) { $authors = new AuthorCollection(); foreach($copyright->getAuthorElements() as $authorElement) { $authors->add( new Author( $authorElement->getName(), new Email($authorElement->getEmail()) ) ); } $licenseElement = $copyright->getLicenseElement(); $license = new License( $licenseElement->getType(), new Url($licenseElement->getUrl()) ); return new CopyrightInformation( $authors, $license ); } private function mapRequirements(RequiresElement $requires) { $collection = new RequirementCollection(); $phpElement = $requires->getPHPElement(); $parser = new VersionConstraintParser; try { $versionConstraint = $parser->parse($phpElement->getVersion()); } catch (VersionException $e) { throw new ManifestDocumentMapperException( sprintf('Unsupported version constraint - %s', $e->getMessage()), $e->getCode(), $e ); } $collection->add( new PhpVersionRequirement( $versionConstraint ) ); if (!$phpElement->hasExtElements()) { return $collection; } foreach($phpElement->getExtElements() as $extElement) { $collection->add( new PhpExtensionRequirement($extElement->getName()) ); } return $collection; } private function mapBundledComponents(ManifestDocument $document) { $collection = new BundledComponentCollection(); if (!$document->hasBundlesElement()) { return $collection; } foreach($document->getBundlesElement()->getComponentElements() as $componentElement) { $collection->add( new BundledComponent( $componentElement->getName(), new Version( $componentElement->getVersion() ) ) ); } return $collection; } private function mapExtension(ExtensionElement $extension) { try { $parser = new VersionConstraintParser; $versionConstraint = $parser->parse($extension->getCompatible()); return Type::extension( new ApplicationName($extension->getFor()), $versionConstraint ); } catch (VersionException $e) { throw new ManifestDocumentMapperException( sprintf('Unsupported version constraint - %s', $e->getMessage()), $e->getCode(), $e ); } } } getName(), $manifest->getVersion()->getVersionString() ); echo (new ManifestSerializer)->serializeToString($manifest); value = $value; $this->number = $number; } public function getValue() { return $this->value; } public function getNumber() { return $this->number; } } value = $value; } } public function isAny() { return $this->value === null; } public function getValue() { return $this->value; } } major = $major; } public function complies(Version $version) { return $version->getMajor()->getValue() == $this->major; } } originalValue = $originalValue; } public function asString() { return $this->originalValue; } } asString() == $version->getVersionString(); } } major = $major; $this->minor = $minor; } public function complies(Version $version) { if ($version->getMajor()->getValue() != $this->major) { return false; } return $version->getMinor()->getValue() == $this->minor; } } constraints = $constraints; } public function complies(Version $version) { foreach ($this->constraints as $constraint) { if ($constraint->complies($version)) { return true; } } return false; } } minimalVersion = $minimalVersion; } public function complies(Version $version) { return $version->getVersionString() == $this->minimalVersion->getVersionString() || $version->isGreaterThan($this->minimalVersion); } } versionString = $versionString; $this->parseVersion($versionString); } private function parseVersion($versionString) { $this->extractBuildMetaData($versionString); $this->extractLabel($versionString); $versionSegments = explode('.', $versionString); $this->major = new VersionNumber($versionSegments[0]); $minorValue = isset($versionSegments[1]) ? $versionSegments[1] : null; $patchValue = isset($versionSegments[2]) ? $versionSegments[2] : null; $this->minor = new VersionNumber($minorValue); $this->patch = new VersionNumber($patchValue); } private function extractBuildMetaData(&$versionString) { if (preg_match('/\+(.*)/', $versionString, $matches) == 1) { $this->buildMetaData = $matches[1]; $versionString = str_replace($matches[0], '', $versionString); } } private function extractLabel(&$versionString) { if (preg_match('/\-(.*)/', $versionString, $matches) == 1) { $this->label = $matches[1]; $versionString = str_replace($matches[0], '', $versionString); } } public function getLabel() { return $this->label; } public function getBuildMetaData() { return $this->buildMetaData; } public function getVersionString() { return $this->versionString; } public function getMajor() { return $this->major; } public function getMinor() { return $this->minor; } public function getPatch() { return $this->patch; } } constraints = $constraints; } public function complies(Version $version) { foreach ($this->constraints as $constraint) { if (!$constraint->complies($version)) { return false; } } return true; } } handleOrGroup($value); } if (!preg_match('/^[\^~\*]?[\d.\*]+$/', $value)) { throw new UnsupportedVersionConstraintException( sprintf('Version constraint %s is not supported.', $value) ); } switch ($value[0]) { case '~': return $this->handleTildeOperator($value); case '^': return $this->handleCaretOperator($value); } $version = new VersionConstraintValue($value); if ($version->getMajor()->isAny()) { return new AnyVersionConstraint(); } if ($version->getMinor()->isAny()) { return new SpecificMajorVersionConstraint( $value, $version->getMajor()->getValue() ); } if ($version->getPatch()->isAny()) { return new SpecificMajorAndMinorVersionConstraint( $value, $version->getMajor()->getValue(), $version->getMinor()->getValue() ); } return new ExactVersionConstraint($value); } private function handleOrGroup($value) { $constraints = []; foreach (explode('||', $value) as $groupSegment) { $constraints[] = $this->parse(trim($groupSegment)); } return new OrVersionConstraintGroup($value, $constraints); } private function handleTildeOperator($value) { $version = new Version(substr($value, 1)); $constraints = [ new GreaterThanOrEqualToVersionConstraint($value, $version) ]; if ($version->getPatch()->isAny()) { $constraints[] = new SpecificMajorVersionConstraint( $value, $version->getMajor()->getValue() ); } else { $constraints[] = new SpecificMajorAndMinorVersionConstraint( $value, $version->getMajor()->getValue(), $version->getMinor()->getValue() ); } return new AndVersionConstraintGroup($value, $constraints); } private function handleCaretOperator($value) { $version = new Version(substr($value, 1)); return new AndVersionConstraintGroup( $value, [ new GreaterThanOrEqualToVersionConstraint($value, $version), new SpecificMajorVersionConstraint($value, $version->getMajor()->getValue()) ] ); } } ensureVersionStringIsValid($versionString); $this->versionString = $versionString; } private function parseVersion(array $matches) { $this->major = new VersionNumber($matches['Major']); $this->minor = new VersionNumber($matches['Minor']); $this->patch = isset($matches['Patch']) ? new VersionNumber($matches['Patch']) : new VersionNumber(null); if (isset($matches['ReleaseType'])) { $preReleaseNumber = isset($matches['ReleaseTypeCount']) ? (int) $matches['ReleaseTypeCount'] : null; $this->preReleaseSuffix = new PreReleaseSuffix($matches['ReleaseType'], $preReleaseNumber); } } public function getPreReleaseSuffix() { return $this->preReleaseSuffix; } public function getVersionString() { return $this->versionString; } public function isGreaterThan(Version $version) { if ($version->getMajor()->getValue() > $this->getMajor()->getValue()) { return false; } if ($version->getMajor()->getValue() < $this->getMajor()->getValue()) { return true; } if ($version->getMinor()->getValue() > $this->getMinor()->getValue()) { return false; } if ($version->getMinor()->getValue() < $this->getMinor()->getValue()) { return true; } if ($version->getPatch()->getValue() >= $this->getPatch()->getValue()) { return false; } if ($version->getPatch()->getValue() < $this->getPatch()->getValue()) { return true; } return false; } public function getMajor() { return $this->major; } public function getMinor() { return $this->minor; } public function getPatch() { return $this->patch; } private function ensureVersionStringIsValid($version) { $regex = '/^v? (?(0|(?:[1-9][0-9]*))) \\. (?(0|(?:[1-9][0-9]*))) (\\. (?(0|(?:[1-9][0-9]*))) )? (?: - (?(?:(dev|beta|b|RC|alpha|a|patch|p))) (?: (?[0-9]) )? )? $/x'; if (preg_match($regex, $version, $matches) !== 1) { throw new InvalidVersionException( sprintf("Version string '%s' does not follow SemVer semantics", $version) ); } $this->parseVersion($matches); } } $arguments) { foreach ($arguments as $argument) { if ($argument == 'resource') { $resourceFunctions[] = $function; } } } $resourceFunctions = array_unique($resourceFunctions); sort($resourceFunctions); $buffer = << * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\ResourceOperations; class ResourceOperations { /** * @return string[] */ public static function getFunctions() { return [ EOT; foreach ($resourceFunctions as $function) { $buffer .= sprintf(" '%s',\n", $function); } $buffer .= <<< EOT ]; } } EOT; file_put_contents(__DIR__ . '/../src/ResourceOperations.php', $buffer); start = (int) $start; $this->startRange = (int) $startRange; $this->end = (int) $end; $this->endRange = (int) $endRange; $this->lines = $lines; } public function getStart() { return $this->start; } public function getStartRange() { return $this->startRange; } public function getEnd() { return $this->end; } public function getEndRange() { return $this->endRange; } public function getLines() { return $this->lines; } public function setLines(array $lines) { $this->lines = $lines; } } length($fromStart, $to); $llE = $this->length(\array_reverse($fromEnd), \array_reverse($to)); $jMax = 0; $max = 0; for ($j = 0; $j <= $cTo; $j++) { $m = $llB[$j] + $llE[$cTo - $j]; if ($m >= $max) { $max = $m; $jMax = $j; } } $toStart = \array_slice($to, 0, $jMax); $toEnd = \array_slice($to, $jMax); return \array_merge( $this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd) ); } private function length(array $from, array $to) { $current = \array_fill(0, \count($to) + 1, 0); $cFrom = \count($from); $cTo = \count($to); for ($i = 0; $i < $cFrom; $i++) { $prev = $current; for ($j = 0; $j < $cTo; $j++) { if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { $current[$j + 1] = \max($current[$j], $prev[$j + 1]); } } } return $current; } } 0 && $j > 0) { if ($from[$i - 1] === $to[$j - 1]) { $common[] = $from[$i - 1]; --$i; --$j; } else { $o = ($j * $width) + $i; if ($matrix[$o - $width] > $matrix[$o - 1]) { --$j; } else { --$i; } } } return \array_reverse($common); } } type = $type; $this->content = $content; } public function getContent() { return $this->content; } public function getType() { return $this->type; } } header = $header; $this->showNonDiffLines = $showNonDiffLines; } public function diff($from, $to, LongestCommonSubsequence $lcs = null) { $from = $this->validateDiffInput($from); $to = $this->validateDiffInput($to); $diff = $this->diffToArray($from, $to, $lcs); $old = $this->checkIfDiffInOld($diff); $start = isset($old[0]) ? $old[0] : 0; $end = \count($diff); if ($tmp = \array_search($end, $old)) { $end = $tmp; } return $this->getBuffer($diff, $old, $start, $end); } private function validateDiffInput($input) { if (!\is_array($input) && !\is_string($input)) { return (string) $input; } return $input; } private function checkIfDiffInOld(array $diff) { $inOld = false; $i = 0; $old = array(); foreach ($diff as $line) { if ($line[1] === 0 ) { if ($inOld === false) { $inOld = $i; } } elseif ($inOld !== false) { if (($i - $inOld) > 5) { $old[$inOld] = $i - 1; } $inOld = false; } ++$i; } return $old; } private function getBuffer(array $diff, array $old, $start, $end) { $buffer = $this->header; if (!isset($old[$start])) { $buffer = $this->getDiffBufferElementNew($diff, $buffer, $start); ++$start; } for ($i = $start; $i < $end; $i++) { if (isset($old[$i])) { $i = $old[$i]; $buffer = $this->getDiffBufferElementNew($diff, $buffer, $i); } else { $buffer = $this->getDiffBufferElement($diff, $buffer, $i); } } return $buffer; } private function getDiffBufferElement(array $diff, $buffer, $diffIndex) { if ($diff[$diffIndex][1] === 1 ) { $buffer .= '+' . $diff[$diffIndex][0] . "\n"; } elseif ($diff[$diffIndex][1] === 2 ) { $buffer .= '-' . $diff[$diffIndex][0] . "\n"; } elseif ($this->showNonDiffLines === true) { $buffer .= ' ' . $diff[$diffIndex][0] . "\n"; } return $buffer; } private function getDiffBufferElementNew(array $diff, $buffer, $diffIndex) { if ($this->showNonDiffLines === true) { $buffer .= "@@ @@\n"; } return $this->getDiffBufferElement($diff, $buffer, $diffIndex); } public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null) { if (\is_string($from)) { $fromMatches = $this->getNewLineMatches($from); $from = $this->splitStringByLines($from); } elseif (\is_array($from)) { $fromMatches = array(); } else { throw new \InvalidArgumentException('"from" must be an array or string.'); } if (\is_string($to)) { $toMatches = $this->getNewLineMatches($to); $to = $this->splitStringByLines($to); } elseif (\is_array($to)) { $toMatches = array(); } else { throw new \InvalidArgumentException('"to" must be an array or string.'); } list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to); if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(\array_values($from), \array_values($to)); $diff = array(); if ($this->detectUnmatchedLineEndings($fromMatches, $toMatches)) { $diff[] = array( '#Warning: Strings contain different line endings!', 0 ); } foreach ($start as $token) { $diff[] = array($token, 0 ); } \reset($from); \reset($to); foreach ($common as $token) { while (($fromToken = \reset($from)) !== $token) { $diff[] = array(\array_shift($from), 2 ); } while (($toToken = \reset($to)) !== $token) { $diff[] = array(\array_shift($to), 1 ); } $diff[] = array($token, 0 ); \array_shift($from); \array_shift($to); } while (($token = \array_shift($from)) !== null) { $diff[] = array($token, 2 ); } while (($token = \array_shift($to)) !== null) { $diff[] = array($token, 1 ); } foreach ($end as $token) { $diff[] = array($token, 0 ); } return $diff; } private function getNewLineMatches($string) { \preg_match_all('(\r\n|\r|\n)', $string, $stringMatches); return $stringMatches; } private function splitStringByLines($input) { return \preg_split('(\r\n|\r|\n)', $input); } private function selectLcsImplementation(array $from, array $to) { $memoryLimit = 100 * 1024 * 1024; if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { return new MemoryEfficientImplementation; } return new TimeEfficientImplementation; } private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; return $itemSize * \pow(\min(\count($from), \count($to)), 2); } private function detectUnmatchedLineEndings(array $fromMatches, array $toMatches) { return isset($fromMatches[0], $toMatches[0]) && \count($fromMatches[0]) === \count($toMatches[0]) && $fromMatches[0] !== $toMatches[0]; } private static function getArrayDiffParted(array &$from, array &$to) { $start = array(); $end = array(); \reset($to); foreach ($from as $k => $v) { $toK = \key($to); if ($toK === $k && $v === $to[$k]) { $start[$k] = $v; unset($from[$k], $to[$k]); } else { break; } } \end($from); \end($to); do { $fromK = \key($from); $toK = \key($to); if (null === $fromK || null === $toK || \current($from) !== \current($to)) { break; } \prev($from); \prev($to); $end = array($fromK => $from[$fromK]) + $end; unset($from[$fromK], $to[$toK]); } while (true); return array($from, $to, $start, $end); } } \\S+))', $lines[$i], $fromMatch) && \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; $collected = array(); } $diff = new Diff($fromMatch['file'], $toMatch['file']); ++$i; } else { if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } $collected[] = $lines[$i]; } } if ($diff !== null && \count($collected)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; } return $diffs; } private function parseFileDiff(Diff $diff, array $lines) { $chunks = array(); $chunk = null; foreach ($lines as $line) { if (\preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( $match['start'], isset($match['startrange']) ? \max(1, $match['startrange']) : 1, $match['end'], isset($match['endrange']) ? \max(1, $match['endrange']) : 1 ); $chunks[] = $chunk; $diffLines = array(); continue; } if (\preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] === '+') { $type = Line::ADDED; } elseif ($match['type'] === '-') { $type = Line::REMOVED; } $diffLines[] = new Line($type, $match['line']); if (null !== $chunk) { $chunk->setLines($diffLines); } } } $diff->setChunks($chunks); } } from = $from; $this->to = $to; $this->chunks = $chunks; } public function getFrom() { return $this->from; } public function getTo() { return $this->to; } public function getChunks() { return $this->chunks; } public function setChunks(array $chunks) { $this->chunks = $chunks; } } register(new TypeComparator); $this->register(new ScalarComparator); $this->register(new NumericComparator); $this->register(new DoubleComparator); $this->register(new ArrayComparator); $this->register(new ResourceComparator); $this->register(new ObjectComparator); $this->register(new ExceptionComparator); $this->register(new SplObjectStorageComparator); $this->register(new DOMNodeComparator); $this->register(new MockObjectComparator); $this->register(new DateTimeComparator); } public static function getInstance() { if (self::$instance === null) { self::$instance = new self; } return self::$instance; } public function getComparatorFor($expected, $actual) { foreach ($this->comparators as $comparator) { if ($comparator->accepts($expected, $actual)) { return $comparator; } } } public function register(Comparator $comparator) { array_unshift($this->comparators, $comparator); $comparator->setFactory($this); } public function unregister(Comparator $comparator) { foreach ($this->comparators as $key => $_comparator) { if ($comparator === $_comparator) { unset($this->comparators[$key]); } } } } $delta) { throw new ComparisonFailure( $expected, $actual, '', '', false, sprintf( 'Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected) ) ); } } } exporter->shortenedExport($actual), gettype($expected) ) ); } } } exporter = new Exporter; } public function setFactory(Factory $factory) { $this->factory = $factory; } abstract public function accepts($expected, $actual); abstract public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false); } $value) { unset($remaining[$key]); if (!array_key_exists($key, $actual)) { $expString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value) ); $equal = false; continue; } try { $comparator = $this->factory->getComparatorFor($value, $actual[$key]); $comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed); $expString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value) ); $actString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($actual[$key]) ); } catch (ComparisonFailure $e) { $expString .= sprintf( " %s => %s\n", $this->exporter->export($key), $e->getExpectedAsString() ? $this->indent($e->getExpectedAsString()) : $this->exporter->shortenedExport($e->getExpected()) ); $actString .= sprintf( " %s => %s\n", $this->exporter->export($key), $e->getActualAsString() ? $this->indent($e->getActualAsString()) : $this->exporter->shortenedExport($e->getActual()) ); $equal = false; } } foreach ($remaining as $key => $value) { $actString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value) ); $equal = false; } $expString .= ')'; $actString .= ')'; if (!$equal) { throw new ComparisonFailure( $expected, $actual, $expString, $actString, false, 'Failed asserting that two arrays are equal.' ); } } protected function indent($lines) { return trim(str_replace("\n", "\n ", $lines)); } } exporter->export($expected), $this->exporter->export($actual), false, 'Failed asserting that two strings are equal.' ); } throw new ComparisonFailure( $expected, $actual, '', '', false, sprintf( 'Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected) ) ); } } } exporter->export($expected), $this->exporter->export($actual), false, sprintf( '%s is not instance of expected class "%s".', $this->exporter->export($actual), get_class($expected) ) ); } if (in_array([$actual, $expected], $processed, true) || in_array([$expected, $actual], $processed, true)) { return; } $processed[] = [$actual, $expected]; if ($actual !== $expected) { try { parent::assertEquals( $this->toArray($expected), $this->toArray($actual), $delta, $canonicalize, $ignoreCase, $processed ); } catch (ComparisonFailure $e) { throw new ComparisonFailure( $expected, $actual, substr_replace($e->getExpectedAsString(), get_class($expected) . ' Object', 0, 5), substr_replace($e->getActualAsString(), get_class($actual) . ' Object', 0, 5), false, 'Failed asserting that two objects are equal.' ); } } } protected function toArray($object) { return $this->exporter->toArray($object); } } expected = $expected; $this->actual = $actual; $this->expectedAsString = $expectedAsString; $this->actualAsString = $actualAsString; $this->message = $message; } public function getActual() { return $this->actual; } public function getExpected() { return $this->expected; } public function getActualAsString() { return $this->actualAsString; } public function getExpectedAsString() { return $this->expectedAsString; } public function getDiff() { if (!$this->actualAsString && !$this->expectedAsString) { return ''; } $differ = new Differ("\n--- Expected\n+++ Actual\n"); return $differ->diff($this->expectedAsString, $this->actualAsString); } public function toString() { return $this->message . $this->getDiff(); } } sub($delta) || $actual > $expectedUpper->add($delta)) { throw new ComparisonFailure( $expected, $actual, $this->dateTimeToString($expected), $this->dateTimeToString($actual), false, 'Failed asserting that two DateTime objects are equal.' ); } } private function dateTimeToString($datetime) { $string = $datetime->format('Y-m-d\TH:i:s.uO'); return $string ? $string : 'Invalid DateTimeInterface object'; } } exporter->export($expected), $this->exporter->export($actual) ); } } } contains($object)) { throw new ComparisonFailure( $expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), false, 'Failed asserting that two objects are equal.' ); } } foreach ($expected as $object) { if (!$actual->contains($object)) { throw new ComparisonFailure( $expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), false, 'Failed asserting that two objects are equal.' ); } } } } nodeToText($expected, true, $ignoreCase); $actualAsString = $this->nodeToText($actual, true, $ignoreCase); if ($expectedAsString !== $actualAsString) { if ($expected instanceof DOMDocument) { $type = 'documents'; } else { $type = 'nodes'; } throw new ComparisonFailure( $expected, $actual, $expectedAsString, $actualAsString, false, sprintf("Failed asserting that two DOM %s are equal.\n", $type) ); } } private function nodeToText(DOMNode $node, $canonicalize, $ignoreCase) { if ($canonicalize) { $document = new DOMDocument; $document->loadXML($node->C14N()); $node = $document; } if ($node instanceof DOMDocument) { $document = $node; } else { $document = $node->ownerDocument; } $document->formatOutput = true; $document->normalizeDocument(); if ($node instanceof DOMDocument) { $text = $node->saveXML(); } else { $text = $document->saveXML($node); } if ($ignoreCase) { $text = strtolower($text); } return $text; } } contains($variable)) { return $objects; } $array = $variable; $processed->add($variable); if (is_array($variable)) { foreach ($array as $element) { if (!is_array($element) && !is_object($element)) { continue; } $objects = array_merge( $objects, $this->enumerate($element, $processed) ); } } else { $objects[] = $variable; $reflector = new ObjectReflector; foreach ($reflector->getAttributes($variable) as $value) { if (!is_array($value) && !is_object($value)) { continue; } $objects = array_merge( $objects, $this->enumerate($value, $processed) ); } } return $objects; } } arrays = array(); $this->objects = new \SplObjectStorage; } public function add(&$value) { if (is_array($value)) { return $this->addArray($value); } elseif (is_object($value)) { return $this->addObject($value); } throw new InvalidArgumentException( 'Only arrays and objects are supported' ); } public function contains(&$value) { if (is_array($value)) { return $this->containsArray($value); } elseif (is_object($value)) { return $this->containsObject($value); } throw new InvalidArgumentException( 'Only arrays and objects are supported' ); } private function addArray(array &$array) { $key = $this->containsArray($array); if ($key !== false) { return $key; } $key = count($this->arrays); $this->arrays[] = &$array; if (!isset($array[PHP_INT_MAX]) && !isset($array[PHP_INT_MAX - 1])) { $array[] = $key; $array[] = $this->objects; } else { do { $key = random_int(PHP_INT_MIN, PHP_INT_MAX); } while (isset($array[$key])); $array[$key] = $key; do { $key = random_int(PHP_INT_MIN, PHP_INT_MAX); } while (isset($array[$key])); $array[$key] = $this->objects; } return $key; } private function addObject($object) { if (!$this->objects->contains($object)) { $this->objects->attach($object); } return spl_object_hash($object); } private function containsArray(array &$array) { $end = array_slice($array, -2); return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false; } private function containsObject($value) { if ($this->objects->contains($value)) { return spl_object_hash($value); } return false; } public function __destruct() { foreach ($this->arrays as &$array) { if (is_array($array)) { array_pop($array); array_pop($array); } } } } isWindows()) { return false !== \getenv('ANSICON') || 'ON' === \getenv('ConEmuANSI') || 'xterm' === \getenv('TERM'); } if (!\defined('STDOUT')) { return false; } return $this->isInteractive(STDOUT); } public function getNumberOfColumns(): int { if ($this->isWindows()) { return $this->getNumberOfColumnsWindows(); } if (!$this->isInteractive(self::STDIN)) { return 80; } return $this->getNumberOfColumnsInteractive(); } public function isInteractive($fileDescriptor = self::STDOUT): bool { return \function_exists('posix_isatty') && @\posix_isatty($fileDescriptor); } private function isWindows(): bool { return DIRECTORY_SEPARATOR === '\\'; } private function getNumberOfColumnsInteractive(): int { if (\function_exists('shell_exec') && \preg_match('#\d+ (\d+)#', \shell_exec('stty size') ?? '', $match) === 1) { if ((int) $match[1] > 0) { return (int) $match[1]; } } if (\function_exists('shell_exec') && \preg_match('#columns = (\d+);#', \shell_exec('stty') ?? '', $match) === 1) { if ((int) $match[1] > 0) { return (int) $match[1]; } } return 80; } private function getNumberOfColumnsWindows(): int { $ansicon = \getenv('ANSICON'); $columns = 80; if (is_string($ansicon) && \preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', \trim($ansicon), $matches)) { $columns = $matches[1]; } elseif (\function_exists('proc_open')) { $process = \proc_open( 'mode CON', [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ], $pipes, null, null, ['suppress_errors' => true] ); if (\is_resource($process)) { $info = \stream_get_contents($pipes[1]); \fclose($pipes[1]); \fclose($pipes[2]); \proc_close($process); if (\preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { $columns = $matches[2]; } } } return $columns - 1; } } hasXdebug() || $this->hasPHPDBGCodeCoverage(); } public function discardsComments(): bool { if (\extension_loaded('Zend Optimizer+') && (\ini_get('zend_optimizerplus.save_comments') === '0' || \ini_get('opcache.save_comments') === '0')) { return true; } if (\extension_loaded('Zend OPcache') && \ini_get('opcache.save_comments') == 0) { return true; } return false; } public function getBinary(): string { if (self::$binary === null && $this->isHHVM()) { if ((self::$binary = \getenv('PHP_BINARY')) === false) { self::$binary = PHP_BINARY; } self::$binary = \escapeshellarg(self::$binary) . ' --php'; } if (PHP_BINARY !== '') { self::$binary = \escapeshellarg(PHP_BINARY); } if (self::$binary === null) { $possibleBinaryLocations = [ PHP_BINDIR . '/php', PHP_BINDIR . '/php-cli.exe', PHP_BINDIR . '/php.exe' ]; foreach ($possibleBinaryLocations as $binary) { if (\is_readable($binary)) { self::$binary = \escapeshellarg($binary); break; } } } if (self::$binary === null) { self::$binary = 'php'; } return self::$binary; } public function getNameWithVersion(): string { return $this->getName() . ' ' . $this->getVersion(); } public function getName(): string { if ($this->isHHVM()) { return 'HHVM'; } if ($this->isPHPDBG()) { return 'PHPDBG'; } return 'PHP'; } public function getVendorUrl(): string { if ($this->isHHVM()) { return 'http://hhvm.com/'; } return 'https://secure.php.net/'; } public function getVersion(): string { if ($this->isHHVM()) { return HHVM_VERSION; } return PHP_VERSION; } public function hasXdebug(): bool { return ($this->isPHP() || $this->isHHVM()) && \extension_loaded('xdebug'); } public function isHHVM(): bool { return \defined('HHVM_VERSION'); } public function isPHP(): bool { return !$this->isHHVM() && !$this->isPHPDBG(); } public function isPHPDBG(): bool { return PHP_SAPI === 'phpdbg' && !$this->isHHVM(); } public function hasPHPDBGCodeCoverage(): bool { return $this->isPHPDBG(); } } $value) { $name = explode("\0", (string) $name); if (count($name) === 1) { $name = $name[0]; } else { if ($name[1] !== $className) { $name = $name[1] . '::' . $name[2]; } else { $name = $name[2]; } } $attributes[$name] = $value; } return $attributes; } } recursiveExport($value, $indentation); } public function shortenedRecursiveExport(&$data, Context $context = null) { $result = []; $exporter = new self(); if (!$context) { $context = new Context; } $array = $data; $context->add($data); foreach ($array as $key => $value) { if (is_array($value)) { if ($context->contains($data[$key]) !== false) { $result[] = '*RECURSION*'; } else { $result[] = sprintf( 'array(%s)', $this->shortenedRecursiveExport($data[$key], $context) ); } } else { $result[] = $exporter->shortenedExport($value); } } return implode(', ', $result); } public function shortenedExport($value) { if (is_string($value)) { $string = str_replace("\n", '', $this->export($value)); if (function_exists('mb_strlen')) { if (mb_strlen($string) > 40) { $string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7); } } else { if (strlen($string) > 40) { $string = substr($string, 0, 30) . '...' . substr($string, -7); } } return $string; } if (is_object($value)) { return sprintf( '%s Object (%s)', get_class($value), count($this->toArray($value)) > 0 ? '...' : '' ); } if (is_array($value)) { return sprintf( 'Array (%s)', count($value) > 0 ? '...' : '' ); } return $this->export($value); } public function toArray($value) { if (!is_object($value)) { return (array) $value; } $array = []; foreach ((array) $value as $key => $val) { if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) { $key = $matches[1]; } if ($key === "\0gcdata") { continue; } $array[$key] = $val; } if ($value instanceof \SplObjectStorage) { if (property_exists('\SplObjectStorage', '__storage')) { unset($array['__storage']); } elseif (property_exists('\SplObjectStorage', 'storage')) { unset($array['storage']); } if (property_exists('\SplObjectStorage', '__key')) { unset($array['__key']); } foreach ($value as $key => $val) { $array[spl_object_hash($val)] = [ 'obj' => $val, 'inf' => $value->getInfo(), ]; } } return $array; } protected function recursiveExport(&$value, $indentation, $processed = null) { if ($value === null) { return 'null'; } if ($value === true) { return 'true'; } if ($value === false) { return 'false'; } if (is_float($value) && floatval(intval($value)) === $value) { return "$value.0"; } if (is_resource($value)) { return sprintf( 'resource(%d) of type (%s)', $value, get_resource_type($value) ); } if (is_string($value)) { if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) { return 'Binary String: 0x' . bin2hex($value); } return "'" . str_replace('', "\n", str_replace( ["\r\n", "\n\r", "\r", "\n"], ['\r\n', '\n\r', '\r', '\n'], $value ) ) . "'"; } $whitespace = str_repeat(' ', 4 * $indentation); if (!$processed) { $processed = new Context; } if (is_array($value)) { if (($key = $processed->contains($value)) !== false) { return 'Array &' . $key; } $array = $value; $key = $processed->add($value); $values = ''; if (count($array) > 0) { foreach ($array as $k => $v) { $values .= sprintf( '%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($value[$k], $indentation + 1, $processed) ); } $values = "\n" . $values . $whitespace; } return sprintf('Array &%s (%s)', $key, $values); } if (is_object($value)) { $class = get_class($value); if ($hash = $processed->contains($value)) { return sprintf('%s Object &%s', $class, $hash); } $hash = $processed->add($value); $values = ''; $array = $this->toArray($value); if (count($array) > 0) { foreach ($array as $k => $v) { $values .= sprintf( '%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($v, $indentation + 1, $processed) ); } $values = "\n" . $values . $whitespace; } return sprintf('%s Object &%s (%s)', $class, $hash, $values); } return var_export($value, true); } } blacklist = $blacklist; if ($includeConstants) { $this->snapshotConstants(); } if ($includeFunctions) { $this->snapshotFunctions(); } if ($includeClasses || $includeStaticAttributes) { $this->snapshotClasses(); } if ($includeInterfaces) { $this->snapshotInterfaces(); } if ($includeGlobalVariables) { $this->setupSuperGlobalArrays(); $this->snapshotGlobals(); } if ($includeStaticAttributes) { $this->snapshotStaticAttributes(); } if ($includeIniSettings) { $this->iniSettings = \ini_get_all(null, false); } if ($includeIncludedFiles) { $this->includedFiles = \get_included_files(); } $this->traits = \get_declared_traits(); } public function blacklist(): Blacklist { return $this->blacklist; } public function globalVariables(): array { return $this->globalVariables; } public function superGlobalVariables(): array { return $this->superGlobalVariables; } public function superGlobalArrays(): array { return $this->superGlobalArrays; } public function staticAttributes(): array { return $this->staticAttributes; } public function iniSettings(): array { return $this->iniSettings; } public function includedFiles(): array { return $this->includedFiles; } public function constants(): array { return $this->constants; } public function functions(): array { return $this->functions; } public function interfaces(): array { return $this->interfaces; } public function classes(): array { return $this->classes; } public function traits(): array { return $this->traits; } private function snapshotConstants() { $constants = \get_defined_constants(true); if (isset($constants['user'])) { $this->constants = $constants['user']; } } private function snapshotFunctions() { $functions = \get_defined_functions(); $this->functions = $functions['user']; } private function snapshotClasses() { foreach (\array_reverse(\get_declared_classes()) as $className) { $class = new ReflectionClass($className); if (!$class->isUserDefined()) { break; } $this->classes[] = $className; } $this->classes = \array_reverse($this->classes); } private function snapshotInterfaces() { foreach (\array_reverse(\get_declared_interfaces()) as $interfaceName) { $class = new ReflectionClass($interfaceName); if (!$class->isUserDefined()) { break; } $this->interfaces[] = $interfaceName; } $this->interfaces = \array_reverse($this->interfaces); } private function snapshotGlobals() { $superGlobalArrays = $this->superGlobalArrays(); foreach ($superGlobalArrays as $superGlobalArray) { $this->snapshotSuperGlobalArray($superGlobalArray); } foreach (\array_keys($GLOBALS) as $key) { if ($key != 'GLOBALS' && !\in_array($key, $superGlobalArrays) && $this->canBeSerialized($GLOBALS[$key]) && !$this->blacklist->isGlobalVariableBlacklisted($key)) { $this->globalVariables[$key] = \unserialize(\serialize($GLOBALS[$key])); } } } private function snapshotSuperGlobalArray(string $superGlobalArray) { $this->superGlobalVariables[$superGlobalArray] = []; if (isset($GLOBALS[$superGlobalArray]) && \is_array($GLOBALS[$superGlobalArray])) { foreach ($GLOBALS[$superGlobalArray] as $key => $value) { $this->superGlobalVariables[$superGlobalArray][$key] = \unserialize(\serialize($value)); } } } private function snapshotStaticAttributes() { foreach ($this->classes as $className) { $class = new ReflectionClass($className); $snapshot = []; foreach ($class->getProperties() as $attribute) { if ($attribute->isStatic()) { $name = $attribute->getName(); if ($this->blacklist->isStaticAttributeBlacklisted($className, $name)) { continue; } $attribute->setAccessible(true); $value = $attribute->getValue(); if ($this->canBeSerialized($value)) { $snapshot[$name] = \unserialize(\serialize($value)); } } } if (!empty($snapshot)) { $this->staticAttributes[$className] = $snapshot; } } } private function setupSuperGlobalArrays() { $this->superGlobalArrays = [ '_ENV', '_POST', '_GET', '_COOKIE', '_SERVER', '_FILES', '_REQUEST' ]; if (\ini_get('register_long_arrays') == '1') { $this->superGlobalArrays = \array_merge( $this->superGlobalArrays, [ 'HTTP_ENV_VARS', 'HTTP_POST_VARS', 'HTTP_GET_VARS', 'HTTP_COOKIE_VARS', 'HTTP_SERVER_VARS', 'HTTP_POST_FILES' ] ); } } private function canBeSerialized($variable): bool { if (!\is_object($variable)) { return !\is_resource($variable); } if ($variable instanceof \stdClass) { return true; } $class = new ReflectionClass($variable); do { if ($class->isInternal()) { return $variable instanceof Serializable; } } while ($class = $class->getParentClass()); return true; } } constants() as $name => $value) { $result .= \sprintf( 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, $this->exportVariable($value) ); } return $result; } public function globalVariables(Snapshot $snapshot): string { $result = '$GLOBALS = [];' . PHP_EOL; foreach ($snapshot->globalVariables() as $name => $value) { $result .= \sprintf( '$GLOBALS[%s] = %s;' . PHP_EOL, $this->exportVariable($name), $this->exportVariable($value) ); } return $result; } public function iniSettings(Snapshot $snapshot): string { $result = ''; foreach ($snapshot->iniSettings() as $key => $value) { $result .= \sprintf( '@ini_set(%s, %s);' . "\n", $this->exportVariable($key), $this->exportVariable($value) ); } return $result; } private function exportVariable($variable): string { if (\is_scalar($variable) || \is_null($variable) || (\is_array($variable) && $this->arrayOnlyContainsScalars($variable))) { return \var_export($variable, true); } return 'unserialize(' . \var_export(\serialize($variable), true) . ')'; } private function arrayOnlyContainsScalars(array $array): bool { $result = true; foreach ($array as $element) { if (\is_array($element)) { $result = self::arrayOnlyContainsScalars($element); } elseif (!\is_scalar($element) && !\is_null($element)) { $result = false; } if ($result === false) { break; } } return $result; } } functions()) as $function) { uopz_delete($function); } } public function restoreGlobalVariables(Snapshot $snapshot) { $superGlobalArrays = $snapshot->superGlobalArrays(); foreach ($superGlobalArrays as $superGlobalArray) { $this->restoreSuperGlobalArray($snapshot, $superGlobalArray); } $globalVariables = $snapshot->globalVariables(); foreach (\array_keys($GLOBALS) as $key) { if ($key != 'GLOBALS' && !\in_array($key, $superGlobalArrays) && !$snapshot->blacklist()->isGlobalVariableBlacklisted($key)) { if (\array_key_exists($key, $globalVariables)) { $GLOBALS[$key] = $globalVariables[$key]; } else { unset($GLOBALS[$key]); } } } } public function restoreStaticAttributes(Snapshot $snapshot) { $current = new Snapshot($snapshot->blacklist(), false, false, false, false, true, false, false, false, false); $newClasses = \array_diff($current->classes(), $snapshot->classes()); unset($current); foreach ($snapshot->staticAttributes() as $className => $staticAttributes) { foreach ($staticAttributes as $name => $value) { $reflector = new ReflectionProperty($className, $name); $reflector->setAccessible(true); $reflector->setValue($value); } } foreach ($newClasses as $className) { $class = new \ReflectionClass($className); $defaults = $class->getDefaultProperties(); foreach ($class->getProperties() as $attribute) { if (!$attribute->isStatic()) { continue; } $name = $attribute->getName(); if ($snapshot->blacklist()->isStaticAttributeBlacklisted($className, $name)) { continue; } if (!isset($defaults[$name])) { continue; } $attribute->setAccessible(true); $attribute->setValue($defaults[$name]); } } } private function restoreSuperGlobalArray(Snapshot $snapshot, string $superGlobalArray) { $superGlobalVariables = $snapshot->superGlobalVariables(); if (isset($GLOBALS[$superGlobalArray]) && \is_array($GLOBALS[$superGlobalArray]) && isset($superGlobalVariables[$superGlobalArray])) { $keys = \array_keys( \array_merge( $GLOBALS[$superGlobalArray], $superGlobalVariables[$superGlobalArray] ) ); foreach ($keys as $key) { if (isset($superGlobalVariables[$superGlobalArray][$key])) { $GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key]; } else { unset($GLOBALS[$superGlobalArray][$key]); } } } } } globalVariables[$variableName] = true; } public function addClass(string $className) { $this->classes[] = $className; } public function addSubclassesOf(string $className) { $this->parentClasses[] = $className; } public function addImplementorsOf(string $interfaceName) { $this->interfaces[] = $interfaceName; } public function addClassNamePrefix(string $classNamePrefix) { $this->classNamePrefixes[] = $classNamePrefix; } public function addStaticAttribute(string $className, string $attributeName) { if (!isset($this->staticAttributes[$className])) { $this->staticAttributes[$className] = []; } $this->staticAttributes[$className][$attributeName] = true; } public function isGlobalVariableBlacklisted(string $variableName): bool { return isset($this->globalVariables[$variableName]); } public function isStaticAttributeBlacklisted(string $className, string $attributeName): bool { if (\in_array($className, $this->classes)) { return true; } foreach ($this->classNamePrefixes as $prefix) { if (\strpos($className, $prefix) === 0) { return true; } } $class = new ReflectionClass($className); foreach ($this->parentClasses as $type) { if ($class->isSubclassOf($type)) { return true; } } foreach ($this->interfaces as $type) { if ($class->implementsInterface($type)) { return true; } } if (isset($this->staticAttributes[$className][$attributeName])) { return true; } return false; } } lookupTable[$filename][$lineNumber])) { $this->updateLookupTable(); } if (isset($this->lookupTable[$filename][$lineNumber])) { return $this->lookupTable[$filename][$lineNumber]; } else { return $filename . ':' . $lineNumber; } } private function updateLookupTable() { $this->processClassesAndTraits(); $this->processFunctions(); } private function processClassesAndTraits() { foreach (array_merge(get_declared_classes(), get_declared_traits()) as $classOrTrait) { if (isset($this->processedClasses[$classOrTrait])) { continue; } $reflector = new \ReflectionClass($classOrTrait); foreach ($reflector->getMethods() as $method) { $this->processFunctionOrMethod($method); } $this->processedClasses[$classOrTrait] = true; } } private function processFunctions() { foreach (get_defined_functions()['user'] as $function) { if (isset($this->processedFunctions[$function])) { continue; } $this->processFunctionOrMethod(new \ReflectionFunction($function)); $this->processedFunctions[$function] = true; } } private function processFunctionOrMethod(\ReflectionFunctionAbstract $functionOrMethod) { if ($functionOrMethod->isInternal()) { return; } $name = $functionOrMethod->getName(); if ($functionOrMethod instanceof \ReflectionMethod) { $name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name; } if (!isset($this->lookupTable[$functionOrMethod->getFileName()])) { $this->lookupTable[$functionOrMethod->getFileName()] = []; } foreach (range($functionOrMethod->getStartLine(), $functionOrMethod->getEndLine()) as $line) { $this->lookupTable[$functionOrMethod->getFileName()][$line] = $name; } } } release = $release; $this->path = $path; } public function getVersion() { if ($this->version === null) { if (count(explode('.', $this->release)) == 3) { $this->version = $this->release; } else { $this->version = $this->release . '-dev'; } $git = $this->getGitInformation($this->path); if ($git) { if (count(explode('.', $this->release)) == 3) { $this->version = $git; } else { $git = explode('-', $git); $this->version = $this->release . '-' . end($git); } } } return $this->version; } private function getGitInformation($path) { if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) { return false; } $process = proc_open( 'git describe --tags', [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ], $pipes, $path ); if (!is_resource($process)) { return false; } $result = trim(stream_get_contents($pipes[1])); fclose($pipes[1]); fclose($pipes[2]); $returnCode = proc_close($process); if ($returnCode !== 0) { return false; } return $result; } } buildAndCacheFromFactory($className); } private function buildAndCacheFromFactory($className) { $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); $instance = $factory(); if ($this->isSafeToClone(new ReflectionClass($instance))) { self::$cachedCloneables[$className] = clone $instance; } return $instance; } private function buildFactory($className) { $reflectionClass = $this->getReflectionClass($className); if ($this->isInstantiableViaReflection($reflectionClass)) { return function () use ($reflectionClass) { return $reflectionClass->newInstanceWithoutConstructor(); }; } $serializedString = sprintf( '%s:%d:"%s":0:{}', $this->getSerializationFormat($reflectionClass), strlen($className), $className ); $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); return function () use ($serializedString) { return unserialize($serializedString); }; } private function getReflectionClass($className) { if (! class_exists($className)) { throw InvalidArgumentException::fromNonExistingClass($className); } $reflection = new ReflectionClass($className); if ($reflection->isAbstract()) { throw InvalidArgumentException::fromAbstractClass($reflection); } return $reflection; } private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, $serializedString) { set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) { $error = UnexpectedValueException::fromUncleanUnSerialization( $reflectionClass, $message, $code, $file, $line ); }); $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); restore_error_handler(); if ($error) { throw $error; } } private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString) { try { unserialize($serializedString); } catch (Exception $exception) { restore_error_handler(); throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); } } private function isInstantiableViaReflection(ReflectionClass $reflectionClass) { if (\PHP_VERSION_ID >= 50600) { return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); } return \PHP_VERSION_ID >= 50400 && ! $this->hasInternalAncestors($reflectionClass); } private function hasInternalAncestors(ReflectionClass $reflectionClass) { do { if ($reflectionClass->isInternal()) { return true; } } while ($reflectionClass = $reflectionClass->getParentClass()); return false; } private function getSerializationFormat(ReflectionClass $reflectionClass) { if ($this->isPhpVersionWithBrokenSerializationFormat() && $reflectionClass->implementsInterface('Serializable') ) { return self::SERIALIZATION_FORMAT_USE_UNSERIALIZER; } return self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER; } private function isPhpVersionWithBrokenSerializationFormat() { return PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513; } private function isSafeToClone(ReflectionClass $reflection) { if (method_exists($reflection, 'isCloneable') && ! $reflection->isCloneable()) { return false; } return ! $reflection->hasMethod('__clone'); } } getName() ), 0, $exception ); } public static function fromUncleanUnSerialization( ReflectionClass $reflectionClass, $errorString, $errorCode, $errorFile, $errorLine ) { return new self( sprintf( 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' . 'in file "%s" at line "%d"', $reflectionClass->getName(), $errorFile, $errorLine ), 0, new Exception($errorString, $errorCode) ); } } = 50400 && trait_exists($className)) { return new self(sprintf('The provided type "%s" is a trait, and can not be instantiated', $className)); } return new self(sprintf('The provided class "%s" does not exist', $className)); } public static function fromAbstractClass(ReflectionClass $reflectionClass) { return new self(sprintf( 'The provided class "%s" is abstract, and can not be instantiated', $reflectionClass->getName() )); } } getProperties(); $propsArr = array(); foreach ($props as $prop) { $propertyName = $prop->getName(); $propsArr[$propertyName] = $prop; } if ($parentClass = $ref->getParentClass()) { $parentPropsArr = self::getProperties($parentClass); foreach ($propsArr as $key => $property) { $parentPropsArr[$key] = $property; } return $parentPropsArr; } return $propsArr; } } type = $type; } public function matches($element) { return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type; } } property = $property; } public function matches($object, $property) { return $property == $this->property; } } class = $class; $this->property = $property; } public function matches($object, $property) { return ($object instanceof $this->class) && ($property == $this->property); } } propertyType = $propertyType; } public function matches($object, $property) { $reflectionProperty = new ReflectionProperty($object, $property); $reflectionProperty->setAccessible(true); return $reflectionProperty->getValue($object) instanceof $this->propertyType; } } callback = $callable; } public function apply($element) { return call_user_func($this->callback, $element); } } deepCopy = $deepCopy; } public function apply($element) { $newElement = clone $element; if ($element instanceof \SplDoublyLinkedList) { for ($i = 1; $i <= $newElement->count(); $i++) { $newElement->push($this->deepCopy->copy($newElement->shift())); } } return $newElement; } } __load(); } } setAccessible(true); $reflectionProperty->setValue($object, new ArrayCollection()); } } setAccessible(true); $oldCollection = $reflectionProperty->getValue($object); $newCollection = $oldCollection->map( function ($item) use ($objectCopier) { return $objectCopier($item); } ); $reflectionProperty->setValue($object, $newCollection); } } callback = $callable; } public function apply($object, $property, $objectCopier) { $reflectionProperty = new \ReflectionProperty($object, $property); $reflectionProperty->setAccessible(true); $value = call_user_func($this->callback, $reflectionProperty->getValue($object)); $reflectionProperty->setValue($object, $value); } } setAccessible(true); $reflectionProperty->setValue($object, null); } } useCloneMethod = $useCloneMethod; $this->addTypeFilter(new SplDoublyLinkedList($this), new TypeMatcher('\SplDoublyLinkedList')); } public function skipUncloneable($skipUncloneable = true) { $this->skipUncloneable = $skipUncloneable; return $this; } public function copy($object) { $this->hashMap = []; return $this->recursiveCopy($object); } public function addFilter(Filter $filter, Matcher $matcher) { $this->filters[] = [ 'matcher' => $matcher, 'filter' => $filter, ]; } public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher) { $this->typeFilters[] = [ 'matcher' => $matcher, 'filter' => $filter, ]; } private function recursiveCopy($var) { if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) { return $filter->apply($var); } if (is_resource($var)) { return $var; } if (is_array($var)) { return $this->copyArray($var); } if (! is_object($var)) { return $var; } return $this->copyObject($var); } private function copyArray(array $array) { foreach ($array as $key => $value) { $array[$key] = $this->recursiveCopy($value); } return $array; } private function copyObject($object) { $objectHash = spl_object_hash($object); if (isset($this->hashMap[$objectHash])) { return $this->hashMap[$objectHash]; } $reflectedObject = new \ReflectionObject($object); if (false === $isCloneable = $reflectedObject->isCloneable() and $this->skipUncloneable) { $this->hashMap[$objectHash] = $object; return $object; } if (false === $isCloneable) { throw new CloneException(sprintf( 'Class "%s" is not cloneable.', $reflectedObject->getName() )); } $newObject = clone $object; $this->hashMap[$objectHash] = $newObject; if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) { return $object; } if ($newObject instanceof \DateTimeInterface) { return $newObject; } foreach (ReflectionHelper::getProperties($reflectedObject) as $property) { $this->copyObjectProperty($newObject, $property); } return $newObject; } private function copyObjectProperty($object, ReflectionProperty $property) { if ($property->isStatic()) { return; } foreach ($this->filters as $item) { $matcher = $item['matcher']; $filter = $item['filter']; if ($matcher->matches($object, $property->getName())) { $filter->apply( $object, $property->getName(), function ($object) { return $this->recursiveCopy($object); } ); return; } } $property->setAccessible(true); $propertyValue = $property->getValue($object); $property->setValue($object, $this->recursiveCopy($propertyValue)); } private function getFirstMatchedTypeFilter(array $filterRecords, $var) { $matched = $this->first( $filterRecords, function (array $record) use ($var) { $matcher = $record['matcher']; return $matcher->matches($var); } ); return isset($matched) ? $matched['filter'] : null; } private function first(array $elements, callable $predicate) { foreach ($elements as $element) { if (call_user_func($predicate, $element)) { return $element; } } return null; } } PNG  IHDR^:DsRGBgAMA a pHYsod*$IDATx^ɯee`~#b 82&BL8˜8@84!N1LҌT&0F#*^>{jWm~ȭ}Z9i *3|P Tg:'> @uO3|P Tg:'> @uO3|P Tg:'> @uO3|P Tg:'U}_z>jI|$(OܿoIMr:hOg}& Вu@XIUNt/rmjYu TO?T}F`+D3|R+-Yu TO>DD%\ ᓪ4\th: Wf*M'W4Z>+r$@Kb@4'Ui:Вu@XIU]裏$A4Z>JMr:hOtrE%\ ᓪ"Jh:d+D3|R+-Yu T>@D%\ ᓪ4\th: Wf*M'W4Z>+r׿$A4Z>JMr:hO?OIMr:hOtrE%\ ᓪtꪵN:i=r_MVߟ}ٵ38c5;?'"D3|RUWg9\ێ%-e\}c C_efMgpT8^{M2V|Ή=ZIU]{Fwyg{z( G?Ѣq~탟3h:m~ᵳ:k%]wQ]y)СCGm*D3|RU<Ӌh;U{8.C4ZʶXUr_JxŶ%CCjXIU]{wG~S)M߬Jy?/{4ZʶXEgO3]?3e}7>*[Lzť4]#NM2V߷3Q.}W>*Sl t4NRu{ ϩ>+rg?٢ WZ|7pyc@K[|2|*KLʃ.õ^{6>itN4Zʰ{hm>*ClG&%9>v?O4F 뀨+rғ̹{64SO]{nh:h˥gy憯s- n>*l6W\qņFdj,Nj@K뀖Z;];mTf観 j.--EZ3Y ᓪ"WȚ{Cx饗[΍7޸]v.S4Z\Ko+r͛3'`nR. [t8p`p,th)jТ du Tt6~(⋃+ˍg+-"-Ejkßa, ᓪf|ds;cjR:vO]fȕ7nΖo펗Cmh@ /0mh:ߗzn5!F?Q߳Efp Fq mɵ@K/ɜ>+rL)|K_Z4m&_sYomRu@^=NC ~ڱ ᓪZ79~4Z>+r @Kb@4'Ui:Вu@XIUNh:d+D3|RUW^}UIMr:hOtrE%\ ᓪ"wAIMr:hOtrE%\ ᓪ4\th: Wf8p@D%\ ᓪ4\th: WfȽ+ -Yu T䊦@Kb@4'Ui:Вu@XIU]{%A4Z>JMr:hOK/$ Вu@XIUNh:d+D3|R+-Yu T_|QD%\ ᓪ4\th: WfȽ -Yu T䊦@Kb@4'UuE矗th: Wf*M'W4Z>+r-sgq)g,?ПՎ@ C=vN>j'vI'޽{3lgܵk׎@ C=vJo:Yvb|^/%oɐ_ .ܦeSZz6R(Y.C۴ bZM?񢐗&S2Њuc>in+ϕW^(_ɘ~?MzNSzh饥:MƔ5K~O~2MXВ;|>={x}?{;?9Q`3J,=ul.:;Q&'VNyR\)cަw(]-ufdʙha9:VHgwNl*g ٭ Sedݻ^Kp?\s5G> 馛};v' P\@4Sed4pw_ NyPtk9]'1ÜeES{\3OFax|D35Oқ`=x9`S|;ǿ?b$5}_<KM~3Ws?t>I˥&u}x.sǍv+d$% hp W_&ᓔdr69?܂98>IǑ\L;2pv|,d$7o'0EN4G:dr)Yw0|#xΕPpx)p5cL yq1󼇝b, rn54nƨy-_Ef%+'aܧZiw >rgϞ=G>v10|{xL2cPu:S:sr& 4\=9c?XZ΀:n!3'Maȓm*p d6ĕPdb29+N;;L&Oq?b[<80ږ[qIKl<ٶ(nNIur vg21|Re7DrM<'I5eqG彼m7EOp-rk:{=QJwJ'Кᓕrfm<Z=`3D1|2 x8* @ =5hJ<=v<UrњsmQ8 v'DҒۡhD'WҌSj[y5>vLlV l3f?^NgAЂm) <1 z[4w޹ l#cUNt8;s1!pe5>gǦ~p<TN_ZYmVɖlih'CAݻwrr?/5>4OMei_ͪ>gp8(9?tjON4ndyt/|9=_eX8߽{|)'/8l;OB{٠oRǮLМH9 Q|}Ľlᓅ~!TSǾ0M;Ux+>6:PUǮ{.l)8KV>Yqp;~-8'`ʎO-g'?ls\.*8Z0^baJwa={lN&>!?DY 3e_*( 1|T$+f*اEfSsgb\ 0.Ox 35QCk:zSS>b3|Όт 1hj`sF<͎V\#hv>gVLi6e\xS(g?6Yfl!hɾC={|{>'4O%Kb9D4g>'k6lK$O/d`4s42>QjrL'-XΓsƓme_m6LY>><>'e dUxz@}eQ[|#91 ٕ}%:ze1adVn9!^Ȍtoad<>'400&:z6ȘF۷G2Dh6Q 3*a9} u{([ u(a9G9;Oؙ~uc.e17j3e_.q'l+H <>G ӓmgνL+g!G*Oؼ3L9B S߿Xj=]ϑqfsf\XNsDb.,r]}=cO׿3޳h aN\ 5=>̕S) \\39G|;-}’;Ga܎3\kG:ׁ0oC(C(̑𙘧|+g|m q2|&ld̍op4㌛3BOO\`Nrú2wg2;l G_`[o݁ 5⪀q1|&R^8.s 0en런q;x>qy lKn)r lq395e_r0v7w l+ lKnp l_wI \.;S:Խ:0V+P`k^C>9kQn\`lo_J[q0|r40F7z;ӿo<ȓ| A<V;  V*7ggh2pz.0ay>bP{s2|6ԿÓmP Zو1Ж#@F+<*W gxxooh:<g@_? J- \m1geμ@2pv {~x᳢}-vru@GOԂZ.g?<܇bݎɶVJ7xzu/6 + !P\0e^^%nk"{<sIP0.nvZ']`cܢ|wmx0>Coн',U gܢnZvP4tͥr\X끲0u ƭ׶X=uor뭷zLr)0X`'빻z`u []bSvHHa7Y0~W2Xs Kl#%lJ>(rVyw ʒ/| u [tu,NKp+_X!ttן)t;r{qi,u}ĥ62UW]Xj>7;#}n7eG-˥{8ki0cIw6KmN za=s@;gܤK.dإ)G;U^R]R߉e`g |$Maz{H(`0|nB}kׅV^w}7 :ַe="U/˓gk׮/~>@+CR^_.1Z%>'`aO hehDh2|ј'5 WК+5gIDh̓+jQԂ\Q hM ȕ`Q{`Ԁ\QZ@kj@Dր&租~* >Ԣ@ WZSr%>gyRrE Z+jY |$H<E-Ԁ\%rGcԀ\QZ@kj@D1Oj@DQ rE-55 W"k@?ј'5 WК+59Dh̓+jQԂ\Q hM ȕd裏$A"w4I 5(jAJd 0|(;@ WZSr%>gyRrE Z+jY ~$H<E-Ԁ\%rGcԀ\QZ@kj@Dր&| 1Oj@DQ rE-55 W"ksFј'5 WК+59Dh̓+jQԂ\Q hM ȕd׿% 1Oj@DQ rE-55 W"ksFј'5 WК+5S$rGcԀ\QZ@kj@D1Oj@DQ rE-55 W"kχzhNRN9唵_/s"w4i 5xꪫk@ɯ5(ug8{w ~NYZ}>{M?m|c3͘yC X3FYSϏDRɘyC xG@N;m^%s]x2O5}~Lu 4>{エy;y睷on ;N!:4mDhj@;S}oX5駟>@ZP>|xڰu]wvW^ym"kZ@k5v[m4o.S"w4i 5dlwze n%jQ"kA>߯,_J?KEZ%@d h2|is/~eG|7JG.;61Oc ̭ցn!jQ"kA>oo8R,5*jEր}~u >wД;,7r˫:[om&s-PY jv?vj*ZT hX"ky, ʡkvFD. 9Z@kQ5EcMrow}s]{~vmdJٔl8b<iٳgھ}Ȱ5tQY ~ݹKUɾ@d h2|뭳f)ڮ))GC˖(2t'wG\=wRRDւL}Kj}=g5kJ[M9}裏~݌јXJ)z袋Q_P?@knJQY fQ X끌}>K41-X#w4\bS.ٽ{gvM7o>ډ3Z*e]d@d h2|##YS`)+_?Sb҄JրrrSnJjrDa޺}oh>_ /nm[.?HD2lug)S߇ܮy+"k׮5ٶ0oݾ7_Nd8xEg@>TIzYN3g=ܳovCoaG[˒izʽc6?F\o\rvS@Z}\gy?fV-z CZ"ksMiyNƴyrt~Ik5+wQY "SO=v/># nUɾ@d 0|)wSO=u_.C"w4<_rpD|msZG-`ư@d h2|k^)7X7X/v1-RˏR_g5J3Dy'7ݼ@ZвkUɾc5æ|:?c1Ocg5si,u@ Jd-h_|vZd hR"k@᳜ΚktR.M);vY1OcO<Ć[R_bp QY ZvYZd hT"ksM/߰SCeI<,ˑ_|qp~.J i5(v_^f?:Y M^fHvRvƇ~xgJ<t)%jLY j~ڡZd RϏDwr^1Oc˹_9眳+ ~lQY jK'sY>?:Y tt֔7N#Y1OcJdK_?e@ZPϗKs?lʢ5Zd 8VVZ"k9%rGcԀ\QZ@kj@Dր&gZ1Oj@DQ rE-55 W"ksFј'5 WК+59Dh̓+jQԂ\Q hM ȕd,7J|"w4I 5(jAJd 0|(;@ WZSr%4><( 1Oj@DQ rE-55 W"ksFј'5 WК+59Dh̓+jQԂ\Q hM ȕd_z%IyRrE Z+jY 3J<E-Ԁ\%rGcԀ\QZ@kj@Dր&/( 1Oj@DQ rE-55 W"ksFј'5 WК+5 /HDh̓+jQԂ\Q hM ȕ`Q"w4I 5(jAJd h2|> ;@ WZSr%>gyRrE Z+jY +;4I|Z%>j `a"v4ih6J|Z$>59ꫯ>/*Ԁ|jA@Kj@`#hC  K ͇KѠ3J\ ֆCdO @uO3|P Tg:'> @uO3|P Tg:'> @uO3|P Tg:'> @uO3|P Tg:'> @uO*[[{-IENDB`PNG  IHDRN`sRGBgAMA a pHYsod6NIDATx^۫g}?)-aBK#B/ze(C;QA QBo4z5rXT nDD$XVѨbE$XV ,QPu@X +hTQPu@X +hTQPu@X +t>Ѩ(: Vhb4XѨ(: VhbJhTd+J1UhTd+J17[oum۶_WZF@IQo';w~~ӟfFhb4W^yeE{۶ "s?k53J:VX)&bZokE 'zvQPRu?\=Z +to*k]j.<#i!%E[̫9sfr%L7^ݾ}m~ӧOخThb5_^sUWM{iB4*JWߗ}ۥuTvE:VX)&RZoMTѭDHys3߳X +t1=䠕r-pzkّ~ "jg; 'OnX +DjTɹVp ~hTePGY?X +t1#BG@ʥw'zhTeP7^$]X +DiTIz5\3m(wu׊m}-Mg3Ѩ():fsrX +DhTMh\n?Lh4*Jgյ~RV 3g??5ӧOgK|}vQPRu@ꫧ?/n[2J1z2XBRxe mhTT{PGVShb˜.|YcH.ɱcDz?7Z4*J(eĻvZsz5b@+ SQ77|DjFkE뀒׿䢋.~owYqnZVݨ֓ЍhѨ(:d=+Za`09[`=n6wo>/.R4*J(ۧ\q'OfZa`j_.~7M./ҲKvQPRu@>;nZa`0rG̋/)=rIٳg'FF@I%{nX&Zhbj5lBGnZfFbhTTk裏.Za`Zj==f3{7zҨ(:`+X +t1;ZwY3vk˚V:{QPRuՍ&=gY{u0RLF{汕Asqm1F@I57tӲo4V87+t1]>)ҝ/x<_%z2.w嶭 Jӽw_LOgE:VX)tQPu@X +t1 "QPu@X +hTQPu@X +hTQPu@X +tԩS %YĊu0RF+%YĊu0RLWO<)QPu@X +hTQPu@X +hTQPu@X +tĉ %YĊu0RF+%YĊu0RLW?.QPu@X +hTQPu@X +hTQPu@X +tرc %YĊu0RF+%YĊu0RLW=*QPu@X +hTQPu@X +hTQPu@X +tȑ# %YĊu0RF+%YĊu0RLW>,QPu@X +hTQPu@X +t^Ѩ(: Vhb4XѨ(: VhbX2vܹ3kϚEG\Zt3RLH.:wd۶m;vd=Bҟ-oߞEG\ZtRome,__tJUvۊB9RH&BsϞ=mJJY: Ԯ^&BsKnh TFoo=MD@)uJU{7-&bofo|c!͢QPRm5^zjꭹm"&Y˿gYtʠ9sfzݻ~]vҟ=7|g G=뮛^}ճ_q32I'B7iha02jwT2imU:ڝ%NGZs4v4X{.!\BsС~@<0V@vca`%4GT >4v|_/f`%,XO`\I>?1tVBQ˙hdrW?L4Cc`%G 7}, Wl+*+鷛>UCCd`%,n+Z1Т7{X }};̙3+v}l++yb8c @ 2Vg'WX2+U985ie~&X& PXqw@-,Hq-2RMwSYl"rt_p,6-2R+˲( Sr-3R\O.AjvrObX)ʙ\@mn ǙmZd`E>P[sX)" F.C!kT۳hsV/tTkT{=UXʚVNA%AOL ,ǖήv?&0o-}tOlCTV6xr01':+ oUZ+ä^i_II2oVVpyĽ'MOx ng3<9`8מQFZ Z``eO~e+?S f`eʥ̃r [z[˘'+K z"0[;i-ΰ}) V.芊2)̛}y1\l#`̓KN[u ,BkTHf3 #)n`?{ "Y ֑@Jq@Lnݠ$g,RcJ *s P_:խYVX4,Ȥ FI=w}x}2HjPLM.?Y.jsv2Hנ<<9!HhPDϜ9s,D=:ڔXGߠ<\PVhE5N䂨ҙU^X4tCl:`ѥ}4)0iE"Kh^;r O+\8i .,:@!xy"-޿ٯu4(Z'lWhQTփBX#i+#n-'^e`GSi'lMVuӾ6ցp:Cxr0R/g`!UOf\-q8C8G*OX3,L?ڸT *!c`m3P +Iw rK {ڠU3)+PW]1SX'5K$:Ua`m#KdZr_qpX},Q>cӿE" 0Fk#< ~ǕX9+ G`96`l*4҇V=~ϓ1q;urv1Pg<V_}r `ۂEҐq0XJ62OUhx A@-ϭBe` #ؘ4/h]z}]wmA>H&k0>h9.mAy݉'~K`k҃ϐ_iδJk|AZ=9hM:`հt 8W) 5*/-_% ؚ}a'u<9h+`|Z3AiH`,Z,#@ \X+ߧ8 "s.k@YTsXtd`-}*P#@$޽YZ#;PGjPKu\kA@]iH횔w58xyL[ ߿p *PݻOCL+PuZ@(N\4){Nk+>W?`@L @ĔnJKW=e`]t_'A\%^@+@&,Z+ݰaoS>sb1.37I`6z/D`z"8x mqyL9C|vy:;h2c`݂ҁZY@{rޙArWGXc`݂nqvj0t )}CRa`5@Z:x mKϭ1nAʚ~C` fT|Ow@'繻rz<&u;eоw Vݿ;x 훽bz&utGRSҍ٩AtHtYwДXˀYziAߓX7=ܳTKB;>ǩu`Xr[ou끲 Нy?鲝8鲁tY!ІyMMLΚtq_2[/=~( _K:ڒoڕ>2ٳ&]إzоs<@M=cMÒ>CT?@b=`=Pu|[Z9ge@@=ozF }zb륗^:ܹs}__f@)}˃rb~(? #~~(?Ȋ_}H ;'âĊ@-jAJ 5_Js2,j@ԢĊZ@ij@P #Mqix≳[0j@ԢĊZ@ij@P_|Hv~`QԀXQE-ԀXiXGvNE 5ZԂXQ (M j@?iadXԀXQE-ԀXiXGvNE 5ZԂXQ (M juiadXԀXQE-ԀXiX?3 vNE 5ZԂXQ (M juiadXԀXQE-ԀXiX?S vNE 5ZԂXQ (M juiadXԀXQE-ԀXiXGvNE 5ZԂXQ (M j@O>iadXԀXQE-ԀXiXGvNE 5ZԂXQ (M j@?iadXԀXQE-ԀXiXGvNE 5ZԂXQ (M juiadXԀXQE-ԀXiX?# vNE 5ZԂXQ (M juiadXԀXQE-ԀXiX?C vNE 5ZԂXQ (M juiadXԀXQE-ԀXiX{y'۶mP?ɯ~ϋvNVnl Hя~Qf-(Nvܙ?OS:jլkeQ}>zhX?ݙ֓~nĴs2,-Ԁټ+6\eNĨRiPZ0EV@ 5VYJKCk ;'B g#k /ٟ)jԬ%f~UƤf g}:B (>aSOMwj[oe_m#aikv:e/8BPf-Xt?sK.Yw߽b}-ۦfP (f HYtoP lȏbhX{J#VX)z m2ҜSNe]-oˀ"5ZjւEBxԜyD-Z5DoP X5('z =꺞kfFRX׻;jժ%|uP|`M7:G~xWNxvr-O~&RZ95p:*gJjY '-TO_PmdK?t5]s_7;޽{{RKj5ZjւH}7ͲkJQu``6tis=I|i8MCjV뮻ni\}l(u]5Zjւ(}~vyQ (F ԁj4-윴#]. ޱcDze]6y\ jԬ"ojԀh}>RhXI9ݎ٤{[?.r~E$ )Qk@eRA1Lݾ/}ޚ\}~?ݶTJQ"hu``%t;N:-v~65.fEgak5fb}/_.:5|a5E-5 Jwjsx<ӝ+XzRn\?cCevNꫯ.ݻ{[1?NT@vC@-5kA>K۵kצQ (F ցj4ٝp_:-윴Kv~Jo_5on;R']tҽj>lvQ (V #ׁj4 .;v,]s_zRϧ\Yk@F2{;nW;jԬ|[Z@ij@>PyǦ;SjdV}ݷlgJhXiyG'NnK&픹aiˎnkHwܑ.BjY J"5]V-Ձj4niَ.m%- K 5 e9r$m?~6RCJG `}~v1}XMQ (f HYtoP lqL ;'B HIOݳgϲZ٣)靳/Vl)jԬ󳵣@Z@i5k@"|uP|`M/#"IځyϏvN%=!5V,ogתjլ][-k/[%hiadXZ{rO?YѢPKZ>?{Fc`e,jր̻ϷXZtkƥ~Z95 VjQ bE-45 VZ֑aQbE  VJSb``iZ95 VjQ bE-45 VZtCO ;'âĊ@-jAJ 5:Ҵs2,j@ԢĊZ@ij@P'OiadXԀXQE-ԀXiXGvNE 5ZԂXQ (M juiadXԀXQE-ԀXiXO8!ɰPZ+jB 04- +jP+-Ԁ%@Z95 VjQ bE-45 VZ֑aQbE  VJSb``iZ95 VjQ bE-45 VZcǎIs2,j@ԢĊZ@ij@P #M ;'âĊ@-jAJ 5zQ vNE 5ZԂXQ (M juiadXԀXQE-ԀXiXGvNE 5ZԂXQ (M j@ȑ# - +jP+-HɰPZ+jB (>>|XaQbE  VJSb``iZ95 VjQ bE-45 VZ^{MaQbE  VJSb``iZ95 VjQ bE-45 VZV;'Ò~J퇭fǎ]ve~"ydXrԏ@iٟd۶m??ܹ3MKQ (%ID֜nm(I ZgϞ5&ݮ@IUJRjh?^Vo~sZ d=/TX8 mdrԋ@-?''}ek!jC+9sf{e·~믿>l`<89$C6 0g޽{g[:g[6 j:˚t5}` gζ^veKpnV^}եa;ۺj A{(Ӯ]Y yX* X* X  xX 18X ` ` `l `, ` ` 8X+p1 WCa`  0`^ 0^ 0^ 02^ 0B^ 0b^Df`92+ph ,8@VV  + &+k +8@iV+p lW%X4 x(V+py307^̓ ` ,W[e``,+ExQV `# 8zX+ps1PW1PW9V  @8^$VB  @h^e` Ґ``*kX @U݀KX @UX1 b`%+Uug}&b`%+UXcJ$VO?TJ$V2ƊH Te`+X> +X+V"1PU7 } V"1P5V Db`*kX @U݀GIX @UX1?1 b`%+UEXoɶm۲я~駟Ϝg Db`n@W^ܹ3;P}/srYϾ{_*V"1PUu3k /ٟep͡J$VJ}ɵ^bhꪫ&o֊_~gZO>b۔Gms̙%\6b`%+UX۷lP\mP߿nvO=t? /dlWyH T H^̞-Mo6КC-wygzw/sugYs-*V"1PU5oW\qԩSJ_ڵkg{衇-*V"1PUu^g2gy֛ٟg`e T H͒9}3imwK.hs|vM?nm4V"1PU5Y\09zhv2{iɓ'cm۷O~_f[T Db`n@J–΋/h8O86G}t)wqDz_liso3X @U5֔ni: 3GnOLYms \"V"1PU5=wϞ=!jP./~bmx1)=lV}u=I3<]f/>WYÇg֢c`%+UEX{r\~Ǐgl;ng."V"1PU7 Ks$]{gʔ[O|XJ$V2ƊH T HH Te`+X$V D``ܰ$c`%+vm+& @8x s +X @HVB2 d` $+!X @HVB2 d` $+!X @HVB2 d` $+!X @HVB2 d` $+M&X&Nk6IENDB`PNG  IHDR]ݽsRGBgAMA a pHYsodIDATx^ݹoTqҦB2NED("҄%R2U@$+$& Q5+P@J?\YHk̽g&S*$$$$$$$$$$$$$$$$$$$$$$$$$$$$$sPJ(@H իWA(@(!) ܗ/_$ W/ÇG~[O $ aIPPDBRϟ?'BBIPPDB+ QgψPPDB+ QӧOPPDB+ QD E $  QD E $c  QD E $QBBIa>z8E $QBBIa>|8E $QBBIaÆʔ)S6mZԩSTBBIa>x@.Νtuuehپ}{XPPDBrO5g1cFBBIa޿_*wޭ,[,+/2+G?G! QZgϞ..ZrW^PPDBRݓʮ]۶m[M!f'OF?{(@(!Io߾]3gNMݹs'$J1CBIxѬ͛7?+\) Qm*Qɺu벢oߺu'j>O! QVo+|9ZBBIa=49r$+ V_7ڟ#._\CBIJh_.]֭[8g}qPPDBR?5/u8~xkf{9 Q5vڵqV8+cǎE (@HR)@dɒg8ˍ7j.uPPDBRvs:D3}ŋ_[(@(!I׬Y-f޽ѯ- QPeboV\BBIa=6rPܹ3_=S(@(!I?WX=&3gTfΜ}-[y E $k/ ӧOה؞={bN/^} ;K(@(!{/cq%h$X .Ǝ+ '5 (@H ^<-wMyƊ[LDY@X$cǎׁcL+ Q]n۬2k֬>qIך?~ab (@HZ" {ePPDB+ QD E ${U  QD E ${  QD E $QBBIaڻCBIPPDBR.]"BBIPPDB+ QH$ W(@(!) ܡ!  QD E $QBBIa$ W(@(!) ܁  QD E ${  QD E $̞=3OѾ؟: PԩS+SLtvvFC{ﱣ#% Jcƍo fO>ᅬ!7oξŋGig(@F 𫯾J=vx~(@F___ӳ~L>1Vw}3Ďiu(@xGuww|TǼy߻ _("0l'S@3D/<{gΟ?=]|G S- 'Of?ϦMF> rvޝ{YA#^( {X6v ~6$Zg@(@w|WjæDR-i?[x4YQH^jcg( IKX|H d3Ҋ/V@j(@$HpVPDRR9^ E(@$CBϊ" ~PH9> EJ(@^ 72RAԸ59lB (@'8 bx}eGlBQ(v26m r@YP(^'΃!g"3ᑏ:(@H^T u {~PBB :g|v|X(!͞Nah`g(Pp:G % \cǧvBx6+3Qp+?Mzo (@Ďre+; B |h= m@İ.P mÎO%\ڂD[. x+=+?[#V@Qh9vaX+h' -ŎOLmbag(ZDKPagAQh v|Vt{8D#;Cr:L ]|h6ZD:.4  5h%JXh`ځuf0^v+ DCx^P4Ĥ;Ea(@L ;>Q4vQ &h c'<ɯᑏoGbB ɢ1nsW/l(06 ®;xgk4<@(0: o뮠 c سPlX(@Pdúeg(FCbT rŽcg(ކ`g(B;>Q6\h(@dobg(b(@d;( *!z Gd y`%kZÚ׺"]`xRU 0QAx?PJ8vLDQ.;>7寎|eF&34-`_xRr`+=+pE;>agh(bWΡޑL(+V|Z>`ɰh.X2DT`gh9Q%O^>` whM}8e2C ˍ} o/ P;Ӏb?'& P@bY#܃F _x . P6O7Ez(@<43T \;>{zzF> (@]h@:ad.{J0ܺ;(D8Y츏C(<;\t"_~a'P;Cţ?Q";>rg~኎=# ]< Ёp3v^˚绝(X> ;?2hq$ pܹHWWWD\-@֟Z켷.\1j(@Ҵ[.W^Q]  ]/_$+3$ '>>|x43WTgtx8H}؂hf 4]_Qs :(@pT?t1|EuP.~b .gϞQ] @+3@>}JDuC3WTgH.f 4]_Q :(@pT?t1|EuHǏ.~b IQ] @+3@=zDDuC3WTgH.f|!q]_Q i8:(L2eB6mZԩSѯ9fXٰaCtXx .o]I(] 3>ΝtuuEX@(EU?t)̀|z7Vf̘Q~zkz .ͯ-EUnݺ=.]v]=ST?t)ݻw+˖-9-̈́gϾ,Qa ELZOG] 3~\,0zq^: ޽{nsњEqO[crqmp kbæzg@]ǎ?ܲeK: Uk<-Z+@{A{Ѳsy?v^ _zgݷ_ű9s2sٳ'z\Q`h_B3f@UZϟ_}+ ]vykEآžר.~R.}Κ5+z[y IQ]  ]vm]_Q i8:(@pT?t1|EuHn]_Q i8: ]_Q i8:(@pT?t1|EuH)>:(@pT?t1|EuHQ,)>:(@pT?t1|EuP.~b .@⣺+3$ GuC3WTgt Q] @+3$ GuC3WTgtQ] @+3@.~b IQ]  ].\ +3$ GuC3WTgt_Q[[ 4-jb Y17n|/HZ]i 8G!). NQ$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$Q$QU*uWIENDB`PNG  IHDR{hsRGBgAMA a pHYsod/IDATx^ۏmYY7V jh ҉ 7+!rabl AnL nJ@0!^4!!4ghQ0 &Ч{Ms>_c9kQU֚c̱'yfUWZo9f]wp= {$ H0 a`@= {$ H0 a`@= {$ H0 a`@= {$ H0 a`@= {$ Hǐg P=Tk0}j վǐJSO~:(M9*s@;C*M?4yd{ IqҥU}|2Uv=Tʏ~#A#}Z 怾Ўǐ4J`N怾ЎǐJS:(M9*s@;C*M9*s@;C*M9*s@;C*M?4yd{ I4yd{ 492#1$M92#1$M92#1TJ`N怾Ўǐ4J`N怾ЎǐJSw:(M9*s@;C*M9*s@;C*M9*s@;C*M;4yd{ I4yd{ 4o۪92#1$M92#17׼5]wa=O_Ӹ7W]Ji̩9`:j_c2#1F} 7|K_nSsG:UghGcH|\T}_?xK^rؘU뮻Sos<7]wqd?^zvs9a!7}s2^}ݫF򗿼9K(M96lkmyYooRGVۦjp2#1To,M {|.ЎǐJSɂ߾jz?|>#SOs@>;aʕ+v]v=SߤNkmvcK(M92޿2?Ri*&~ 5=?o|^<seh%=(s@;CoRYxՌ7^M/G[7&zZߵ9gRM~Ӛy%'۽կi4yjNUv=T#<}?&viO|]0|ы^zwm,s@;CoRM9xԶK}_>rIV<sj=ؿRv=TJ.e>ig'?<@V<sj=̽ϥ7tӑwm2#1M~zի^u1zj'&Zs?O}s˥m[9a!nT(Y[o~J`N-9g{ЎǐJSw}9o}nSi̩0}}o>rJuehGcH-5e/{YuZ}=rv=&Zs׃Y?-ЎǐJS+>i|;*r-͑˗/W4yjc^ zVv=ԪoZFK:(M9*s@;C*M9*s@;C*M9*s@;C*MP&}9a!i}&}9a!rJ`N怾Ўǐ4J`N怾Ўǐ4J`N怾ЎǐJST0's@_ehGcH|_0's@_ehGcH{ェ92#1$M92#1T/J`N怾Ўǐ4J`N怾ЎǐJSn馃o*cv]<svUO{nᆃ믿 OxBu?{l'1Zu^wuOyS{]&jmTBSOzғS+>wJ<0?a׾&U6=_y-T`.go?_>8_>tZys}{[zsTf$˿|//~ {]4HvmrPT K]`aY>j/!~?S'7.];VMwwX;s=?=j4y4_~m/_:ܿ/mmvg?>~y>^0aGY{ _{}?| _8x;qWep4|,-oy=g}{WfRYC-uCQa%~ʁ؄׽uJ@ʁl`>0SBj 'JѵX*;r0G `)J[?U*dz]`7˴%dQpŨ?˔ 9,9U>mZj?6 {DJ#dg-D$$eߺ:or[sM-@cۄ=@tY <5 ,S*'6zMd~H]vZ-$)҄Q;rSVvHY༲O)7PI9m]K\٭^lTc.Jއ<=l@uvٯd3g*KǡaN](;MdTvTqTeI.˾tN"#;ҸɎvXk}]7W.[aqdϸ'ӁaA؃JNk_M}n=ԔF]0 x%`Mvٹ%ryNvjUYg T:<@~;Y=d x, 3%_'eeJ@IXˁ̲U>m)ԔW}!1 {0owܔ˒``qK*ɾW~'K'O;Yd`v!'7P)+/F&O]`/#Ks/>oSW~O 0 TN[gWng x {3V)r2g@Sn:r^-X/=Zvr46Sh(vdv:mIJ+RLco%#~q4 g*>:i8VSp'Z-Yf {rv*;;ϳsV`3L٭Ryuv'+/_ x.c#챗NʎN-;ټ ?Tmr6ؾ=;JnWvr`ߔuv9Xt`rjӼ/t{%,`;)A9t T:gI/oNco$XcYCQvH`Ǧ7Pd]w67'<^sµ)^Ȏ C|"kze;7P9jgKc/#ٱ؁쎳lή@%p\ xgqa=g=ټ2xh)}?uv v_Xg= p-aMi2]*ӃN v9uvۓq/nbǰaj+T~[l[tz([g;y-=Z%9{ );FɿQ%TV]Yg'IsNi'Vy,2lrШVuvn\{<|L{ ٤d+AL% $إ?l T# { %Gx$GyA yO97J9^yYg*ʾ3ZKNxr{ #;CrԷ?9j_AƗ@0i첝]{e2aͨrjuv*ٍC$5gzS9p9<]gw\Oj}|h:슰G+; dj7(`ޖ `4?0'a.e'Xfg>24C*KF хڬ]uЕ!6=C& T_9czUyKɁh.XY)sMe@ٙVEF TrU5tq ^eYSztq./7Ps:;9.@@; BأrYUvJ+Ù!wYg~ݴ Tc*<'r``9S 0"af2eGg'Yy+g<Ƨ@%$z玀a&.;ݼ Q@;3Le&7P<k |̥>tU78rI_}^yKyN"OcVd x .s_l.AP&s|#{hz&;h7Jm/eHdgQ^P'12fGm̶ Rڭ 7YgWn1y}ruvƧ7P1s^y嵼\<6#s9_vvRF9k`5mu\Aٳ8a`X@/VuvlK^y><5ؙНawN2\q TMBam'٩3an* IYgL幘a ˀvGc'ʙ ~BBL,G 3y}%g@Tޟ`uɮW;,Kcr X.]Ck*y:A5^e-幚랇تwO9U3ZԆR0<t(d=fHѷQ(7fQn6YgWnbs;.祀aP7y4jK @=!\M__*~ {\X2dp^8tq./7PΎ<1{\H٥b?^\soۦ6JB.8<k$ ,u?HBУre@5J\ӫbxDT y`WmrA8!pCoV ̥rIJseg!q\V(gm+2Ğt uv6J^s^༄=_bJPz TZ=rְ|?m)︿xlGU9ʜAy`&NJ lc^xp'<<vA$jΆՆRXekKxn9E9~s ]8Z%N6ύM*s[.9')v6[`n+RParYY.d]l`70adh=u-מ߄=M^dhgdd]' ~ٮuv2=띟@iք=|O]Yg TvrP`wI}]^^?yxFc*7ήvRX٥ۥˀ2oDޞJ@(LPI7PIuv vX} !g%!SmrveS7_^9XR{=J^Cz0aod)N5!ChM%%dXgׇ<yl{Sjg<F'$2pȥa9 : T<^*P(Dd'o veq.UnBC4{<B2ۑ>a-vÇRXO7}9}9kΒO_c<# ن(2Lv:;O|8OuD3]gw Ty9R#ٕW xeup:ao`>Ax%؝tTYg`g$y_;] x6-G3 u]dh6TDzp^ xppzsE L9 2$..7kئ'yM΢'5L(l7^E]~Mev vJv-<>:.ף! d_"d(<:rJ9/gKZ?7ze$s2I Tr zo ;>?<苰7|9T]>:;"ο#\!לa_xEy;{XuvAqZcyPM^*@? ei7PMPy'-Am-,eΰ xao2|+o*e/z~y=5\%m ~ʠJ6=*ǥKV+z@_@CG*Gi}m{jecnz@_@C217= aO zMhCS;/s*=:?PA}m{jecnz@_@C217= aO zMhCS;/s*=:}{SA}m{jecnz@_@C~217= aO zMhCS;/s*=:};QA}m{jecnz@_@Co۪217= aO zMhCkT3ăOկszI׼RwWK6{ַ~An*u{,s[BX7xc5_??~K6FuZR31%it&鷺/UfO@Co~G>n7m zW^nSz@_K^#q=s5ghCkT {a﮻nSz@;X /nKaFȀ|]b6=A-9k\}=ThCkT {!O؃k|3b>Cm~zֳ hCkTžA{ӛT~9J6{A}h5=?\ݮV~sT z̭pҥk8`~Sn7JІר{uF ~]r]Oec[^-o9O{z6g=س^Za/+ݴ^W>'zz+ېW<ȣ=v/~W?mVn aQMY+}}򓟬~ˠ6$%%M_/}KT= s7VyXn aQ]$쥖4 ئ\™K9yM<=x;qM=@`.C 2LT=կS屨}JS[{Fғ=רa/k^ZnZCY>e屘~Jn{'\{B|`5|͇P]aw]z)/|pu|y4\Ԟ+qE/Z}߷zkuJ6Fu;wtMˠǶ};\W\׽ȟcUZw@^HKM'?<@uʠ6+9rCu?w'>QZ?K7]?m}ݫ-a/qַu {uʠǶRa?uӳ iN~O}SW_|gu֥@^h[ 쬟?gzYJ^.q._\ݮVszVe/{Yu^ʠܖ>9+w>0!׿z(=E+_#C^.ݪmKz@j3|w_uiM/N]z!53zKR=涄ʝuo#^gS/rͶ=m Nj{KS>W~OecnKrZۤ$^h^{okV=涄^wyg~\=y;x_^.ǡki/S5{-s[B8r3?77hCS;/s*=:e-j_=Uz!쩝A}m{jecnz@_@C]:վ zMhCS;/s*=:]rEuP=Uz!쩝A}m{jecnz@_@C˗/ʠJ6=217=ރ>:(s*=ˠJ6=217=<:(s*=ˠJ6{ zMhCS;/s*=ˠJ6{w zMhCS;/s*=:{ェ217= aO zMhc/~QuP=Uz!쩝A}m T_ec.j_zKSAԞ}0!^k_k ն`Nz@얰Iڛw%Q[yڕ7a`{$ H0 a`@= {$ H0 a`@= {$ H0 a`@= {$ H0 a`@= {$ ($IENDB`class = $class; } public function getClass() { return $this->class; } public function setGroup($group) { $this->group = $group; return $this; } public function getGroup() { return $this->group; } public function setMaker(callable $maker) { $this->maker = $maker; return $this; } public function clearMaker() { $this->maker = null; return $this; } public function getMaker() { return $this->maker; } public function setCallback(callable $callback) { $this->callback = $callback; return $this; } public function clearCallback() { $this->callback = null; return $this; } public function getCallback() { return $this->callback; } public function addDefinition($attribute, $definition) { $this->definitions[$attribute] = $definition; return $this; } public function setDefinitions(array $definitions = []) { $this->definitions = array_merge($this->definitions, $definitions); return $this; } public function clearDefinitions() { $this->definitions = []; return $this; } public function getDefinitions() { return $this->definitions; } } store = $store ?: new ModelStore(); $this->factory = $factory ?: new GeneratorFactory(); } public function seed($times, $name, array $attr = []) { $seeds = []; for ($i = 0; $i < $times; ++$i) { $seeds[] = $this->create($name, $attr); } return $seeds; } public function create($name, array $attr = []) { $model = $this->make($name, $attr, true); $this->store->persist($model); if ($this->triggerCallback($model, $name)) { $this->store->persist($model); } return $model; } protected function triggerCallback($model, $name) { $callback = $this->getDefinition($name)->getCallback(); if ($callback) { $saved = $this->isPendingOrSaved($model); return call_user_func($callback, $model, $saved) !== false; } return false; } protected function make($name, array $attr, $save) { $definition = $this->getDefinition($name); $model = $this->makeClass($definition->getClass(), $definition->getMaker()); if ($save) { $this->store->markPending($model); } $attributes = array_merge($this->getDefinition($name)->getDefinitions(), $attr); $this->generate($model, $attributes); return $model; } protected function makeClass($class, callable $maker = null) { if (!class_exists($class)) { throw new ModelNotFoundException($class); } if ($maker) { return call_user_func($maker, $class); } return new $class(); } public function isPendingOrSaved($model) { return $this->store->isSaved($model) || $this->store->isPending($model); } public function deleteSaved() { $this->store->deleteSaved(); return $this; } public function instance($name, array $attr = []) { $model = $this->make($name, $attr, false); $this->triggerCallback($model, $name); return $model; } protected function generate($model, array $attr = []) { foreach ($attr as $key => $kind) { $value = $this->factory->generate($kind, $model, $this); $setter = 'set'.ucfirst(static::camelize($key)); if (method_exists($model, $setter) && is_callable([$model, $setter])) { $model->$setter($value); } else { $model->$key = $value; } } } public function getDefinition($name) { if (!isset($this->definitions[$name])) { throw new DefinitionNotFoundException($name); } return $this->definitions[$name]; } public function define($name) { if (isset($this->definitions[$name])) { throw new DefinitionAlreadyDefinedException($name); } if (strpos($name, ':') !== false) { $group = current(explode(':', $name)); $class = str_replace($group.':', '', $name); $this->definitions[$name] = clone $this->getDefinition($class); $this->definitions[$name]->setGroup($group); } else { $this->definitions[$name] = new Definition($name); } return $this->definitions[$name]; } public function loadFactories($paths) { foreach ((array) $paths as $path) { $real = realpath($path); if (!$real) { throw new DirectoryNotFoundException($path); } if (!is_dir($real)) { throw new DirectoryNotFoundException($real); } $this->loadDirectory($real); } return $this; } private function loadDirectory($path) { $directory = new RecursiveDirectoryIterator($path); $iterator = new RecursiveIteratorIterator($directory); $files = new RegexIterator($iterator, '/^[^\.](?:(?!\/\.).)+?\.php$/i'); $fm = $this; foreach ($files as $file) { require $file->getPathName(); } } public static function camelize($str) { return preg_replace_callback('/_([a-z0-9])/', function ($c) { return strtoupper($c[1]); }, $str); } } modelClass = $class; parent::__construct($message); } public function getModelClass() { return $this->modelClass; } } formatErrors($errors); $this->validationErrors = $errors; if (!$message) { if ($errors) { $message = "$errors We could not save the model: '$class'."; } else { $message = "We could not save the model: '$class'."; } } parent::__construct($class, $message); } private function formatErrors($errors) { if ($errors) { $errors = trim($errors); if (in_array(substr($errors, -1), ['.', '!', '?'], true)) { return $errors; } return $errors.'.'; } } public function getValidationErrors() { return $this->validationErrors; } } path = $path; if (!$message) { $message = "The directory '$path' was not found."; } parent::__construct($message); } public function getPath() { return $this->path; } } definitionName = $name; parent::__construct($message); } public function getDefinitionName() { return $this->definitionName; } } exceptions = $exceptions; if (!$message) { $count = count($exceptions); $problems = $this->plural('problem', $count); $message = "We encountered $count $problems while trying to delete the saved models."; } parent::__construct($message); } private function plural($word, $count) { if ($count === 1) { return $word; } return $word.'s'; } public function getExceptions() { return $this->exceptions; } } methodName = $method; parent::__construct($class, $message); } public function getMethodName() { return $this->methodName; } } getId($model); } private function getId($model) { foreach (self::$methods as $method) { if (method_exists($model, $method) && is_callable([$model, $method])) { return $model->$method(); } } foreach (self::$properties as $property) { if (isset($model->$property)) { return $model->$property; } } } public static function getPrefix() { return 'factory|'; } } make($kind, $model, $factoryMuffin); if ($generator) { return $generator->generate(); } return $kind; } public function make($kind, $model, FactoryMuffin $factoryMuffin) { if (is_callable($kind)) { return new CallableGenerator($kind, $model, $factoryMuffin); } if (is_string($kind) && strpos($kind, EntityGenerator::getPrefix()) === 0) { return new EntityGenerator($kind, $model, $factoryMuffin); } if (is_string($kind) && strpos($kind, FactoryGenerator::getPrefix()) === 0) { return new FactoryGenerator($kind, $model, $factoryMuffin); } } } kind = $kind; $this->model = $model; $this->factoryMuffin = $factoryMuffin; } public function generate() { $name = substr($this->kind, strlen(static::getPrefix())); return $this->factory($name); } private function factory($name) { if ($this->factoryMuffin->isPendingOrSaved($this->model)) { return $this->factoryMuffin->create($name); } return $this->factoryMuffin->instance($name); } public static function getPrefix() { return 'entity|'; } } bindTo($factoryMuffin); } $this->kind = $kind; $this->model = $model; $this->factoryMuffin = $factoryMuffin; } public function generate() { $saved = $this->factoryMuffin->isPendingOrSaved($this->model); return call_user_func($this->kind, $this->model, $saved); } } save($model)) { if (isset($model->validationErrors) && $model->validationErrors) { throw new SaveFailedException(get_class($model), $model->validationErrors); } throw new SaveFailedException(get_class($model)); } if (!$this->isSaved($model)) { $this->markSaved($model); } } abstract protected function save($model); public function pending() { return $this->pending; } public function markPending($model) { $hash = spl_object_hash($model); $this->pending[$hash] = $model; } public function isPending($model) { return in_array($model, $this->pending, true); } public function saved() { return $this->saved; } public function markSaved($model) { $hash = spl_object_hash($model); if (isset($this->pending[$hash])) { unset($this->pending[$hash]); } $this->saved[$hash] = $model; } public function isSaved($model) { return in_array($model, $this->saved, true); } public function deleteSaved() { $exceptions = []; while ($model = array_pop($this->saved)) { try { if (!$this->delete($model)) { throw new DeleteFailedException(get_class($model)); } } catch (Exception $e) { $exceptions[] = $e; } } if ($exceptions) { throw new DeletingFailedException($exceptions); } } abstract protected function delete($model); } storage = $storage; $this->methods = [ 'save' => $saveMethod ?: 'persist', 'delete' => $deleteMethod ?: 'remove', 'flush' => $flushMethod ?: 'flush', ]; } protected function save($model) { $method = $this->methods['save']; if (!method_exists($this->storage, $method) || !is_callable([$this->storage, $method])) { throw new SaveMethodNotFoundException(get_class($this->storage), $method); } $this->storage->$method($model); $this->flush(); return true; } protected function flush() { $method = $this->methods['flush']; if (!method_exists($this->storage, $method) || !is_callable([$this->storage, $method])) { throw new FlushMethodNotFoundException(get_class($this->storage), $method); } $this->storage->$method(); return true; } protected function delete($model) { $method = $this->methods['delete']; if (!method_exists($this->storage, $method) || !is_callable([$this->storage, $method])) { throw new DeleteMethodNotFoundException(get_class($this->storage), $method); } $this->storage->$method($model); return true; } public function deleteSaved() { parent::deleteSaved(); try { $this->flush(); } catch (Exception $e) { throw new DeletingFailedException([$e]); } } } methods = [ 'save' => $saveMethod ?: 'save', 'delete' => $deleteMethod ?: 'delete', ]; } protected function save($model) { $method = $this->methods['save']; if (!method_exists($model, $method) || !is_callable([$model, $method])) { throw new SaveMethodNotFoundException(get_class($model), $method); } return $model->$method(); } protected function delete($model) { $method = $this->methods['delete']; if (!method_exists($model, $method) || !is_callable([$model, $method])) { throw new DeleteMethodNotFoundException(get_class($model), $method); } return $model->$method(); } } generator = $generator; } public function setLocale($local) { $this->locale = $local; $this->generator = null; return $this; } public function getGenerator() { if (!$this->generator) { $this->generator = Factory::create($this->locale); } return $this->generator; } public function addProvider($provider) { $this->getGenerator()->addProvider($provider); return $this; } public function getProviders() { return $this->getGenerator()->getProviders(); } public function format($formatter, array $arguments = []) { $generator = $this->getGenerator(); return function () use ($generator, $formatter, $arguments) { return $generator->format($formatter, $arguments); }; } public function getFormatter($formatter) { return $this->getGenerator()->getFormatter($formatter); } public function unique($reset = false, $maxRetries = 10000) { return new static($this->getGenerator()->unique($reset, $maxRetries)); } public function optional($weight = 0.5, $default = null) { return new static($this->getGenerator()->optional($weight, $default)); } public function __call($method, $arguments) { return $this->format($method, $arguments); } } $method(); case 1: return self::instance()->$method($arguments[0]); case 2: return self::instance()->$method($arguments[0], $arguments[1]); case 3: return self::instance()->$method($arguments[0], $arguments[1], $arguments[2]); default: return call_user_func_array([self::instance(), $method], $arguments); } } } serverParams = $serverParams; parent::__construct($method, $uri, $headers, $body, $version); } public static function normalizeFiles(array $files) { $normalized = []; foreach ($files as $key => $value) { if ($value instanceof UploadedFileInterface) { $normalized[$key] = $value; } elseif (is_array($value) && isset($value['tmp_name'])) { $normalized[$key] = self::createUploadedFileFromSpec($value); } elseif (is_array($value)) { $normalized[$key] = self::normalizeFiles($value); continue; } else { throw new InvalidArgumentException('Invalid value in files specification'); } } return $normalized; } private static function createUploadedFileFromSpec(array $value) { if (is_array($value['tmp_name'])) { return self::normalizeNestedFileSpec($value); } return new UploadedFile( $value['tmp_name'], (int) $value['size'], (int) $value['error'], $value['name'], $value['type'] ); } private static function normalizeNestedFileSpec(array $files = []) { $normalizedFiles = []; foreach (array_keys($files['tmp_name']) as $key) { $spec = [ 'tmp_name' => $files['tmp_name'][$key], 'size' => $files['size'][$key], 'error' => $files['error'][$key], 'name' => $files['name'][$key], 'type' => $files['type'][$key], ]; $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); } return $normalizedFiles; } public static function fromGlobals() { $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; $headers = function_exists('getallheaders') ? getallheaders() : []; $uri = self::getUriFromGlobals(); $body = new LazyOpenStream('php://input', 'r+'); $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); return $serverRequest ->withCookieParams($_COOKIE) ->withQueryParams($_GET) ->withParsedBody($_POST) ->withUploadedFiles(self::normalizeFiles($_FILES)); } public static function getUriFromGlobals() { $uri = new Uri(''); $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); $hasPort = false; if (isset($_SERVER['HTTP_HOST'])) { $hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']); $uri = $uri->withHost($hostHeaderParts[0]); if (isset($hostHeaderParts[1])) { $hasPort = true; $uri = $uri->withPort($hostHeaderParts[1]); } } elseif (isset($_SERVER['SERVER_NAME'])) { $uri = $uri->withHost($_SERVER['SERVER_NAME']); } elseif (isset($_SERVER['SERVER_ADDR'])) { $uri = $uri->withHost($_SERVER['SERVER_ADDR']); } if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { $uri = $uri->withPort($_SERVER['SERVER_PORT']); } $hasQuery = false; if (isset($_SERVER['REQUEST_URI'])) { $requestUriParts = explode('?', $_SERVER['REQUEST_URI']); $uri = $uri->withPath($requestUriParts[0]); if (isset($requestUriParts[1])) { $hasQuery = true; $uri = $uri->withQuery($requestUriParts[1]); } } if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { $uri = $uri->withQuery($_SERVER['QUERY_STRING']); } return $uri; } public function getServerParams() { return $this->serverParams; } public function getUploadedFiles() { return $this->uploadedFiles; } public function withUploadedFiles(array $uploadedFiles) { $new = clone $this; $new->uploadedFiles = $uploadedFiles; return $new; } public function getCookieParams() { return $this->cookieParams; } public function withCookieParams(array $cookies) { $new = clone $this; $new->cookieParams = $cookies; return $new; } public function getQueryParams() { return $this->queryParams; } public function withQueryParams(array $query) { $new = clone $this; $new->queryParams = $query; return $new; } public function getParsedBody() { return $this->parsedBody; } public function withParsedBody($data) { $new = clone $this; $new->parsedBody = $data; return $new; } public function getAttributes() { return $this->attributes; } public function getAttribute($attribute, $default = null) { if (false === array_key_exists($attribute, $this->attributes)) { return $default; } return $this->attributes[$attribute]; } public function withAttribute($attribute, $value) { $new = clone $this; $new->attributes[$attribute] = $value; return $new; } public function withoutAttribute($attribute) { if (false === array_key_exists($attribute, $this->attributes)) { return $this; } $new = clone $this; unset($new->attributes[$attribute]); return $new; } } stream = $stream; $this->maxLength = $maxLength; } public function write($string) { $diff = $this->maxLength - $this->stream->getSize(); if ($diff <= 0) { return 0; } if (strlen($string) < $diff) { return $this->stream->write($string); } return $this->stream->write(substr($string, 0, $diff)); } } stream = $stream; } public function __get($name) { if ($name == 'stream') { $this->stream = $this->createStream(); return $this->stream; } throw new \UnexpectedValueException("$name not found on class"); } public function __toString() { try { if ($this->isSeekable()) { $this->seek(0); } return $this->getContents(); } catch (\Exception $e) { trigger_error('StreamDecorator::__toString exception: ' . (string) $e, E_USER_ERROR); return ''; } } public function getContents() { return copy_to_string($this); } public function __call($method, array $args) { $result = call_user_func_array([$this->stream, $method], $args); return $result === $this->stream ? $this : $result; } public function close() { $this->stream->close(); } public function getMetadata($key = null) { return $this->stream->getMetadata($key); } public function detach() { return $this->stream->detach(); } public function getSize() { return $this->stream->getSize(); } public function eof() { return $this->stream->eof(); } public function tell() { return $this->stream->tell(); } public function isReadable() { return $this->stream->isReadable(); } public function isWritable() { return $this->stream->isWritable(); } public function isSeekable() { return $this->stream->isSeekable(); } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { $this->stream->seek($offset, $whence); } public function read($length) { return $this->stream->read($length); } public function write($string) { return $this->stream->write($string); } protected function createStream() { throw new \BadMethodCallException('Not implemented'); } } setError($errorStatus); $this->setSize($size); $this->setClientFilename($clientFilename); $this->setClientMediaType($clientMediaType); if ($this->isOk()) { $this->setStreamOrFile($streamOrFile); } } private function setStreamOrFile($streamOrFile) { if (is_string($streamOrFile)) { $this->file = $streamOrFile; } elseif (is_resource($streamOrFile)) { $this->stream = new Stream($streamOrFile); } elseif ($streamOrFile instanceof StreamInterface) { $this->stream = $streamOrFile; } else { throw new InvalidArgumentException( 'Invalid stream or file provided for UploadedFile' ); } } private function setError($error) { if (false === is_int($error)) { throw new InvalidArgumentException( 'Upload file error status must be an integer' ); } if (false === in_array($error, UploadedFile::$errors)) { throw new InvalidArgumentException( 'Invalid error status for UploadedFile' ); } $this->error = $error; } private function setSize($size) { if (false === is_int($size)) { throw new InvalidArgumentException( 'Upload file size must be an integer' ); } $this->size = $size; } private function isStringOrNull($param) { return in_array(gettype($param), ['string', 'NULL']); } private function isStringNotEmpty($param) { return is_string($param) && false === empty($param); } private function setClientFilename($clientFilename) { if (false === $this->isStringOrNull($clientFilename)) { throw new InvalidArgumentException( 'Upload file client filename must be a string or null' ); } $this->clientFilename = $clientFilename; } private function setClientMediaType($clientMediaType) { if (false === $this->isStringOrNull($clientMediaType)) { throw new InvalidArgumentException( 'Upload file client media type must be a string or null' ); } $this->clientMediaType = $clientMediaType; } private function isOk() { return $this->error === UPLOAD_ERR_OK; } public function isMoved() { return $this->moved; } private function validateActive() { if (false === $this->isOk()) { throw new RuntimeException('Cannot retrieve stream due to upload error'); } if ($this->isMoved()) { throw new RuntimeException('Cannot retrieve stream after it has already been moved'); } } public function getStream() { $this->validateActive(); if ($this->stream instanceof StreamInterface) { return $this->stream; } return new LazyOpenStream($this->file, 'r+'); } public function moveTo($targetPath) { $this->validateActive(); if (false === $this->isStringNotEmpty($targetPath)) { throw new InvalidArgumentException( 'Invalid path provided for move operation; must be a non-empty string' ); } if ($this->file) { $this->moved = php_sapi_name() == 'cli' ? rename($this->file, $targetPath) : move_uploaded_file($this->file, $targetPath); } else { copy_to_stream( $this->getStream(), new LazyOpenStream($targetPath, 'w') ); $this->moved = true; } if (false === $this->moved) { throw new RuntimeException( sprintf('Uploaded file could not be moved to %s', $targetPath) ); } } public function getSize() { return $this->size; } public function getError() { return $this->error; } public function getClientFilename() { return $this->clientFilename; } public function getClientMediaType() { return $this->clientMediaType; } } getScheme() != '') { return $rel->withPath(self::removeDotSegments($rel->getPath())); } if ($rel->getAuthority() != '') { $targetAuthority = $rel->getAuthority(); $targetPath = self::removeDotSegments($rel->getPath()); $targetQuery = $rel->getQuery(); } else { $targetAuthority = $base->getAuthority(); if ($rel->getPath() === '') { $targetPath = $base->getPath(); $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); } else { if ($rel->getPath()[0] === '/') { $targetPath = $rel->getPath(); } else { if ($targetAuthority != '' && $base->getPath() === '') { $targetPath = '/' . $rel->getPath(); } else { $lastSlashPos = strrpos($base->getPath(), '/'); if ($lastSlashPos === false) { $targetPath = $rel->getPath(); } else { $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); } } } $targetPath = self::removeDotSegments($targetPath); $targetQuery = $rel->getQuery(); } } return new Uri(Uri::composeComponents( $base->getScheme(), $targetAuthority, $targetPath, $targetQuery, $rel->getFragment() )); } public static function relativize(UriInterface $base, UriInterface $target) { if ($target->getScheme() !== '' && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') ) { return $target; } if (Uri::isRelativePathReference($target)) { return $target; } if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { return $target->withScheme(''); } $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); if ($base->getPath() !== $target->getPath()) { return $emptyPathUri->withPath(self::getRelativePath($base, $target)); } if ($base->getQuery() === $target->getQuery()) { return $emptyPathUri->withQuery(''); } if ($target->getQuery() === '') { $segments = explode('/', $target->getPath()); $lastSegment = end($segments); return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); } return $emptyPathUri; } private static function getRelativePath(UriInterface $base, UriInterface $target) { $sourceSegments = explode('/', $base->getPath()); $targetSegments = explode('/', $target->getPath()); array_pop($sourceSegments); $targetLastSegment = array_pop($targetSegments); foreach ($sourceSegments as $i => $segment) { if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { unset($sourceSegments[$i], $targetSegments[$i]); } else { break; } } $targetSegments[] = $targetLastSegment; $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { $relativePath = "./$relativePath"; } elseif ('/' === $relativePath[0]) { if ($base->getAuthority() != '' && $base->getPath() === '') { $relativePath = ".$relativePath"; } else { $relativePath = "./$relativePath"; } } return $relativePath; } private function __construct() { } } stream = $stream; $this->setLimit($limit); $this->setOffset($offset); } public function eof() { if ($this->stream->eof()) { return true; } if ($this->limit == -1) { return false; } return $this->stream->tell() >= $this->offset + $this->limit; } public function getSize() { if (null === ($length = $this->stream->getSize())) { return null; } elseif ($this->limit == -1) { return $length - $this->offset; } else { return min($this->limit, $length - $this->offset); } } public function seek($offset, $whence = SEEK_SET) { if ($whence !== SEEK_SET || $offset < 0) { throw new \RuntimeException(sprintf( 'Cannot seek to offset % with whence %s', $offset, $whence )); } $offset += $this->offset; if ($this->limit !== -1) { if ($offset > $this->offset + $this->limit) { $offset = $this->offset + $this->limit; } } $this->stream->seek($offset); } public function tell() { return $this->stream->tell() - $this->offset; } public function setOffset($offset) { $current = $this->stream->tell(); if ($current !== $offset) { if ($this->stream->isSeekable()) { $this->stream->seek($offset); } elseif ($current > $offset) { throw new \RuntimeException("Could not seek to stream offset $offset"); } else { $this->stream->read($offset - $current); } } $this->offset = $offset; } public function setLimit($limit) { $this->limit = $limit; } public function read($length) { if ($this->limit == -1) { return $this->stream->read($length); } $remaining = ($this->offset + $this->limit) - $this->stream->tell(); if ($remaining > 0) { return $this->stream->read(min($remaining, $length)); } return ''; } } 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', ]; private $reasonPhrase = ''; private $statusCode = 200; public function __construct( $status = 200, array $headers = [], $body = null, $version = '1.1', $reason = null ) { $this->statusCode = (int) $status; if ($body !== '' && $body !== null) { $this->stream = stream_for($body); } $this->setHeaders($headers); if ($reason == '' && isset(self::$phrases[$this->statusCode])) { $this->reasonPhrase = self::$phrases[$this->statusCode]; } else { $this->reasonPhrase = (string) $reason; } $this->protocol = $version; } public function getStatusCode() { return $this->statusCode; } public function getReasonPhrase() { return $this->reasonPhrase; } public function withStatus($code, $reasonPhrase = '') { $new = clone $this; $new->statusCode = (int) $code; if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { $reasonPhrase = self::$phrases[$new->statusCode]; } $new->reasonPhrase = $reasonPhrase; return $new; } } 80, 'https' => 443, 'ftp' => 21, 'gopher' => 70, 'nntp' => 119, 'news' => 119, 'telnet' => 23, 'tn3270' => 23, 'imap' => 143, 'pop' => 110, 'ldap' => 389, ]; private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; private static $charSubDelims = '!\$&\'\(\)\*\+,;='; private static $replaceQuery = ['=' => '%3D', '&' => '%26']; private $scheme = ''; private $userInfo = ''; private $host = ''; private $port; private $path = ''; private $query = ''; private $fragment = ''; public function __construct($uri = '') { if ($uri != '') { $parts = parse_url($uri); if ($parts === false) { throw new \InvalidArgumentException("Unable to parse URI: $uri"); } $this->applyParts($parts); } } public function __toString() { return self::composeComponents( $this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment ); } public static function composeComponents($scheme, $authority, $path, $query, $fragment) { $uri = ''; if ($scheme != '') { $uri .= $scheme . ':'; } if ($authority != ''|| $scheme === 'file') { $uri .= '//' . $authority; } $uri .= $path; if ($query != '') { $uri .= '?' . $query; } if ($fragment != '') { $uri .= '#' . $fragment; } return $uri; } public static function isDefaultPort(UriInterface $uri) { return $uri->getPort() === null || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); } public static function isAbsolute(UriInterface $uri) { return $uri->getScheme() !== ''; } public static function isNetworkPathReference(UriInterface $uri) { return $uri->getScheme() === '' && $uri->getAuthority() !== ''; } public static function isAbsolutePathReference(UriInterface $uri) { return $uri->getScheme() === '' && $uri->getAuthority() === '' && isset($uri->getPath()[0]) && $uri->getPath()[0] === '/'; } public static function isRelativePathReference(UriInterface $uri) { return $uri->getScheme() === '' && $uri->getAuthority() === '' && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); } public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) { if ($base !== null) { $uri = UriResolver::resolve($base, $uri); return ($uri->getScheme() === $base->getScheme()) && ($uri->getAuthority() === $base->getAuthority()) && ($uri->getPath() === $base->getPath()) && ($uri->getQuery() === $base->getQuery()); } return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; } public static function removeDotSegments($path) { return UriResolver::removeDotSegments($path); } public static function resolve(UriInterface $base, $rel) { if (!($rel instanceof UriInterface)) { $rel = new self($rel); } return UriResolver::resolve($base, $rel); } public static function withoutQueryValue(UriInterface $uri, $key) { $current = $uri->getQuery(); if ($current === '') { return $uri; } $decodedKey = rawurldecode($key); $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { return rawurldecode(explode('=', $part)[0]) !== $decodedKey; }); return $uri->withQuery(implode('&', $result)); } public static function withQueryValue(UriInterface $uri, $key, $value) { $current = $uri->getQuery(); if ($current === '') { $result = []; } else { $decodedKey = rawurldecode($key); $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { return rawurldecode(explode('=', $part)[0]) !== $decodedKey; }); } $key = strtr($key, self::$replaceQuery); if ($value !== null) { $result[] = $key . '=' . strtr($value, self::$replaceQuery); } else { $result[] = $key; } return $uri->withQuery(implode('&', $result)); } public static function fromParts(array $parts) { $uri = new self(); $uri->applyParts($parts); $uri->validateState(); return $uri; } public function getScheme() { return $this->scheme; } public function getAuthority() { $authority = $this->host; if ($this->userInfo !== '') { $authority = $this->userInfo . '@' . $authority; } if ($this->port !== null) { $authority .= ':' . $this->port; } return $authority; } public function getUserInfo() { return $this->userInfo; } public function getHost() { return $this->host; } public function getPort() { return $this->port; } public function getPath() { return $this->path; } public function getQuery() { return $this->query; } public function getFragment() { return $this->fragment; } public function withScheme($scheme) { $scheme = $this->filterScheme($scheme); if ($this->scheme === $scheme) { return $this; } $new = clone $this; $new->scheme = $scheme; $new->removeDefaultPort(); $new->validateState(); return $new; } public function withUserInfo($user, $password = null) { $info = $user; if ($password != '') { $info .= ':' . $password; } if ($this->userInfo === $info) { return $this; } $new = clone $this; $new->userInfo = $info; $new->validateState(); return $new; } public function withHost($host) { $host = $this->filterHost($host); if ($this->host === $host) { return $this; } $new = clone $this; $new->host = $host; $new->validateState(); return $new; } public function withPort($port) { $port = $this->filterPort($port); if ($this->port === $port) { return $this; } $new = clone $this; $new->port = $port; $new->removeDefaultPort(); $new->validateState(); return $new; } public function withPath($path) { $path = $this->filterPath($path); if ($this->path === $path) { return $this; } $new = clone $this; $new->path = $path; $new->validateState(); return $new; } public function withQuery($query) { $query = $this->filterQueryAndFragment($query); if ($this->query === $query) { return $this; } $new = clone $this; $new->query = $query; return $new; } public function withFragment($fragment) { $fragment = $this->filterQueryAndFragment($fragment); if ($this->fragment === $fragment) { return $this; } $new = clone $this; $new->fragment = $fragment; return $new; } private function applyParts(array $parts) { $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : ''; $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : ''; $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; if (isset($parts['pass'])) { $this->userInfo .= ':' . $parts['pass']; } $this->removeDefaultPort(); } private function filterScheme($scheme) { if (!is_string($scheme)) { throw new \InvalidArgumentException('Scheme must be a string'); } return strtolower($scheme); } private function filterHost($host) { if (!is_string($host)) { throw new \InvalidArgumentException('Host must be a string'); } return strtolower($host); } private function filterPort($port) { if ($port === null) { return null; } $port = (int) $port; if (1 > $port || 0xffff < $port) { throw new \InvalidArgumentException( sprintf('Invalid port: %d. Must be between 1 and 65535', $port) ); } return $port; } private function removeDefaultPort() { if ($this->port !== null && self::isDefaultPort($this)) { $this->port = null; } } private function filterPath($path) { if (!is_string($path)) { throw new \InvalidArgumentException('Path must be a string'); } return preg_replace_callback( '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $path ); } private function filterQueryAndFragment($str) { if (!is_string($str)) { throw new \InvalidArgumentException('Query and fragment must be a string'); } return preg_replace_callback( '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $str ); } private function rawurlencodeMatchZero(array $match) { return rawurlencode($match[0]); } private function validateState() { if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { $this->host = self::HTTP_DEFAULT_HOST; } if ($this->getAuthority() === '') { if (0 === strpos($this->path, '//')) { throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"'); } if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); } } elseif (isset($this->path[0]) && $this->path[0] !== '/') { @trigger_error( 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', E_USER_DEPRECATED ); $this->path = '/'. $this->path; } } } [ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a+' => true ], 'write' => [ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true ] ]; public function __construct($stream, $options = []) { if (!is_resource($stream)) { throw new \InvalidArgumentException('Stream must be a resource'); } if (isset($options['size'])) { $this->size = $options['size']; } $this->customMetadata = isset($options['metadata']) ? $options['metadata'] : []; $this->stream = $stream; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); $this->uri = $this->getMetadata('uri'); } public function __get($name) { if ($name == 'stream') { throw new \RuntimeException('The stream is detached'); } throw new \BadMethodCallException('No value for ' . $name); } public function __destruct() { $this->close(); } public function __toString() { try { $this->seek(0); return (string) stream_get_contents($this->stream); } catch (\Exception $e) { return ''; } } public function getContents() { $contents = stream_get_contents($this->stream); if ($contents === false) { throw new \RuntimeException('Unable to read stream contents'); } return $contents; } public function close() { if (isset($this->stream)) { if (is_resource($this->stream)) { fclose($this->stream); } $this->detach(); } } public function detach() { if (!isset($this->stream)) { return null; } $result = $this->stream; unset($this->stream); $this->size = $this->uri = null; $this->readable = $this->writable = $this->seekable = false; return $result; } public function getSize() { if ($this->size !== null) { return $this->size; } if (!isset($this->stream)) { return null; } if ($this->uri) { clearstatcache(true, $this->uri); } $stats = fstat($this->stream); if (isset($stats['size'])) { $this->size = $stats['size']; return $this->size; } return null; } public function isReadable() { return $this->readable; } public function isWritable() { return $this->writable; } public function isSeekable() { return $this->seekable; } public function eof() { return !$this->stream || feof($this->stream); } public function tell() { $result = ftell($this->stream); if ($result === false) { throw new \RuntimeException('Unable to determine stream position'); } return $result; } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { if (!$this->seekable) { throw new \RuntimeException('Stream is not seekable'); } elseif (fseek($this->stream, $offset, $whence) === -1) { throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . var_export($whence, true)); } } public function read($length) { if (!$this->readable) { throw new \RuntimeException('Cannot read from non-readable stream'); } if ($length < 0) { throw new \RuntimeException('Length parameter cannot be negative'); } if (0 === $length) { return ''; } $string = fread($this->stream, $length); if (false === $string) { throw new \RuntimeException('Unable to read from stream'); } return $string; } public function write($string) { if (!$this->writable) { throw new \RuntimeException('Cannot write to a non-writable stream'); } $this->size = null; $result = fwrite($this->stream, $string); if ($result === false) { throw new \RuntimeException('Unable to write to stream'); } return $result; } public function getMetadata($key = null) { if (!isset($this->stream)) { return $key ? null : []; } elseif (!$key) { return $this->customMetadata + stream_get_meta_data($this->stream); } elseif (isset($this->customMetadata[$key])) { return $this->customMetadata[$key]; } $meta = stream_get_meta_data($this->stream); return isset($meta[$key]) ? $meta[$key] : null; } } boundary = $boundary ?: sha1(uniqid('', true)); $this->stream = $this->createStream($elements); } public function getBoundary() { return $this->boundary; } public function isWritable() { return false; } private function getHeaders(array $headers) { $str = ''; foreach ($headers as $key => $value) { $str .= "{$key}: {$value}\r\n"; } return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; } protected function createStream(array $elements) { $stream = new AppendStream(); foreach ($elements as $element) { $this->addElement($stream, $element); } $stream->addStream(stream_for("--{$this->boundary}--\r\n")); return $stream; } private function addElement(AppendStream $stream, array $element) { foreach (['contents', 'name'] as $key) { if (!array_key_exists($key, $element)) { throw new \InvalidArgumentException("A '{$key}' key is required"); } } $element['contents'] = stream_for($element['contents']); if (empty($element['filename'])) { $uri = $element['contents']->getMetadata('uri'); if (substr($uri, 0, 6) !== 'php://') { $element['filename'] = $uri; } } list($body, $headers) = $this->createElement( $element['name'], $element['contents'], isset($element['filename']) ? $element['filename'] : null, isset($element['headers']) ? $element['headers'] : [] ); $stream->addStream(stream_for($this->getHeaders($headers))); $stream->addStream($body); $stream->addStream(stream_for("\r\n")); } private function createElement($name, StreamInterface $stream, $filename, array $headers) { $disposition = $this->getHeader($headers, 'content-disposition'); if (!$disposition) { $headers['Content-Disposition'] = ($filename === '0' || $filename) ? sprintf('form-data; name="%s"; filename="%s"', $name, basename($filename)) : "form-data; name=\"{$name}\""; } $length = $this->getHeader($headers, 'content-length'); if (!$length) { if ($length = $stream->getSize()) { $headers['Content-Length'] = (string) $length; } } $type = $this->getHeader($headers, 'content-type'); if (!$type && ($filename === '0' || $filename)) { if ($type = mimetype_from_filename($filename)) { $headers['Content-Type'] = $type; } } return [$stream, $headers]; } private function getHeader(array $headers, $key) { $lowercaseHeader = strtolower($key); foreach ($headers as $k => $v) { if (strtolower($k) === $lowercaseHeader) { return $v; } } return null; } } isReadable()) { $mode = $stream->isWritable() ? 'r+' : 'r'; } elseif ($stream->isWritable()) { $mode = 'w'; } else { throw new \InvalidArgumentException('The stream must be readable, ' . 'writable, or both.'); } return fopen('guzzle://stream', $mode, null, stream_context_create([ 'guzzle' => ['stream' => $stream] ])); } public static function register() { if (!in_array('guzzle', stream_get_wrappers())) { stream_wrapper_register('guzzle', __CLASS__); } } public function stream_open($path, $mode, $options, &$opened_path) { $options = stream_context_get_options($this->context); if (!isset($options['guzzle']['stream'])) { return false; } $this->mode = $mode; $this->stream = $options['guzzle']['stream']; return true; } public function stream_read($count) { return $this->stream->read($count); } public function stream_write($data) { return (int) $this->stream->write($data); } public function stream_tell() { return $this->stream->tell(); } public function stream_eof() { return $this->stream->eof(); } public function stream_seek($offset, $whence) { $this->stream->seek($offset, $whence); return true; } public function stream_stat() { static $modeMap = [ 'r' => 33060, 'r+' => 33206, 'w' => 33188 ]; return [ 'dev' => 0, 'ino' => 0, 'mode' => $modeMap[$this->mode], 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => $this->stream->getSize() ?: 0, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0 ]; } } read(10); $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header); $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength); $resource = StreamWrapper::getResource($stream); stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); $this->stream = new Stream($resource); } private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header) { $filename_header_length = 0; if (substr(bin2hex($header), 6, 2) === '08') { $filename_header_length = 1; while ($stream->read(1) !== chr(0)) { $filename_header_length++; } } return $filename_header_length; } } source = $source; $this->size = isset($options['size']) ? $options['size'] : null; $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; $this->buffer = new BufferStream(); } public function __toString() { try { return copy_to_string($this); } catch (\Exception $e) { return ''; } } public function close() { $this->detach(); } public function detach() { $this->tellPos = false; $this->source = null; } public function getSize() { return $this->size; } public function tell() { return $this->tellPos; } public function eof() { return !$this->source; } public function isSeekable() { return false; } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { throw new \RuntimeException('Cannot seek a PumpStream'); } public function isWritable() { return false; } public function write($string) { throw new \RuntimeException('Cannot write to a PumpStream'); } public function isReadable() { return true; } public function read($length) { $data = $this->buffer->read($length); $readLen = strlen($data); $this->tellPos += $readLen; $remaining = $length - $readLen; if ($remaining) { $this->pump($remaining); $data .= $this->buffer->read($remaining); $this->tellPos += strlen($data) - $readLen; } return $data; } public function getContents() { $result = ''; while (!$this->eof()) { $result .= $this->read(1000000); } return $result; } public function getMetadata($key = null) { if (!$key) { return $this->metadata; } return isset($this->metadata[$key]) ? $this->metadata[$key] : null; } private function pump($length) { if ($this->source) { do { $data = call_user_func($this->source, $length); if ($data === false || $data === null) { $this->source = null; return; } $this->buffer->write($data); $length -= strlen($data); } while ($length > 0); } } } protocol; } public function withProtocolVersion($version) { if ($this->protocol === $version) { return $this; } $new = clone $this; $new->protocol = $version; return $new; } public function getHeaders() { return $this->headers; } public function hasHeader($header) { return isset($this->headerNames[strtolower($header)]); } public function getHeader($header) { $header = strtolower($header); if (!isset($this->headerNames[$header])) { return []; } $header = $this->headerNames[$header]; return $this->headers[$header]; } public function getHeaderLine($header) { return implode(', ', $this->getHeader($header)); } public function withHeader($header, $value) { if (!is_array($value)) { $value = [$value]; } $value = $this->trimHeaderValues($value); $normalized = strtolower($header); $new = clone $this; if (isset($new->headerNames[$normalized])) { unset($new->headers[$new->headerNames[$normalized]]); } $new->headerNames[$normalized] = $header; $new->headers[$header] = $value; return $new; } public function withAddedHeader($header, $value) { if (!is_array($value)) { $value = [$value]; } $value = $this->trimHeaderValues($value); $normalized = strtolower($header); $new = clone $this; if (isset($new->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; $new->headers[$header] = array_merge($this->headers[$header], $value); } else { $new->headerNames[$normalized] = $header; $new->headers[$header] = $value; } return $new; } public function withoutHeader($header) { $normalized = strtolower($header); if (!isset($this->headerNames[$normalized])) { return $this; } $header = $this->headerNames[$normalized]; $new = clone $this; unset($new->headers[$header], $new->headerNames[$normalized]); return $new; } public function getBody() { if (!$this->stream) { $this->stream = stream_for(''); } return $this->stream; } public function withBody(StreamInterface $body) { if ($body === $this->stream) { return $this; } $new = clone $this; $new->stream = $body; return $new; } private function setHeaders(array $headers) { $this->headerNames = $this->headers = []; foreach ($headers as $header => $value) { if (!is_array($value)) { $value = [$value]; } $value = $this->trimHeaderValues($value); $normalized = strtolower($header); if (isset($this->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; $this->headers[$header] = array_merge($this->headers[$header], $value); } else { $this->headerNames[$normalized] = $header; $this->headers[$header] = $value; } } } private function trimHeaderValues(array $values) { return array_map(function ($value) { return trim($value, " \t"); }, $values); } } method = strtoupper($method); $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $version; if (!$this->hasHeader('Host')) { $this->updateHostFromUri(); } if ($body !== '' && $body !== null) { $this->stream = stream_for($body); } } public function getRequestTarget() { if ($this->requestTarget !== null) { return $this->requestTarget; } $target = $this->uri->getPath(); if ($target == '') { $target = '/'; } if ($this->uri->getQuery() != '') { $target .= '?' . $this->uri->getQuery(); } return $target; } public function withRequestTarget($requestTarget) { if (preg_match('#\s#', $requestTarget)) { throw new InvalidArgumentException( 'Invalid request target provided; cannot contain whitespace' ); } $new = clone $this; $new->requestTarget = $requestTarget; return $new; } public function getMethod() { return $this->method; } public function withMethod($method) { $new = clone $this; $new->method = strtoupper($method); return $new; } public function getUri() { return $this->uri; } public function withUri(UriInterface $uri, $preserveHost = false) { if ($uri === $this->uri) { return $this; } $new = clone $this; $new->uri = $uri; if (!$preserveHost) { $new->updateHostFromUri(); } return $new; } private function updateHostFromUri() { $host = $this->uri->getHost(); if ($host == '') { return; } if (($port = $this->uri->getPort()) !== null) { $host .= ':' . $port; } if (isset($this->headerNames['host'])) { $header = $this->headerNames['host']; } else { $header = 'Host'; $this->headerNames['host'] = 'Host'; } $this->headers = [$header => [$host]] + $this->headers; } } filename = $filename; $this->mode = $mode; } protected function createStream() { return stream_for(try_fopen($this->filename, $this->mode)); } } remoteStream = $stream; $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); } public function getSize() { return max($this->stream->getSize(), $this->remoteStream->getSize()); } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { if ($whence == SEEK_SET) { $byte = $offset; } elseif ($whence == SEEK_CUR) { $byte = $offset + $this->tell(); } elseif ($whence == SEEK_END) { $size = $this->remoteStream->getSize(); if ($size === null) { $size = $this->cacheEntireStream(); } $byte = $size + $offset; } else { throw new \InvalidArgumentException('Invalid whence'); } $diff = $byte - $this->stream->getSize(); if ($diff > 0) { while ($diff > 0 && !$this->remoteStream->eof()) { $this->read($diff); $diff = $byte - $this->stream->getSize(); } } else { $this->stream->seek($byte); } } public function read($length) { $data = $this->stream->read($length); $remaining = $length - strlen($data); if ($remaining) { $remoteData = $this->remoteStream->read( $remaining + $this->skipReadBytes ); if ($this->skipReadBytes) { $len = strlen($remoteData); $remoteData = substr($remoteData, $this->skipReadBytes); $this->skipReadBytes = max(0, $this->skipReadBytes - $len); } $data .= $remoteData; $this->stream->write($remoteData); } return $data; } public function write($string) { $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); if ($overflow > 0) { $this->skipReadBytes += $overflow; } return $this->stream->write($string); } public function eof() { return $this->stream->eof() && $this->remoteStream->eof(); } public function close() { $this->remoteStream->close() && $this->stream->close(); } private function cacheEntireStream() { $target = new FnStream(['write' => 'strlen']); copy_to_stream($this, $target); return $this->tell(); } } getPath() === '' && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') ) { $uri = $uri->withPath('/'); } if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { $uri = $uri->withHost(''); } if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { $uri = $uri->withPort(null); } if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); } if ($flags & self::REMOVE_DUPLICATE_SLASHES) { $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); } if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { $queryKeyValues = explode('&', $uri->getQuery()); sort($queryKeyValues); $uri = $uri->withQuery(implode('&', $queryKeyValues)); } return $uri; } public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS) { return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); } private static function capitalizePercentEncoding(UriInterface $uri) { $regex = '/(?:%[A-Fa-f0-9]{2})++/'; $callback = function (array $match) { return strtoupper($match[0]); }; return $uri->withPath( preg_replace_callback($regex, $callback, $uri->getPath()) )->withQuery( preg_replace_callback($regex, $callback, $uri->getQuery()) ); } private static function decodeUnreservedCharacters(UriInterface $uri) { $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; $callback = function (array $match) { return rawurldecode($match[0]); }; return $uri->withPath( preg_replace_callback($regex, $callback, $uri->getPath()) )->withQuery( preg_replace_callback($regex, $callback, $uri->getQuery()) ); } private function __construct() { } } getMethod() . ' ' . $message->getRequestTarget()) . ' HTTP/' . $message->getProtocolVersion(); if (!$message->hasHeader('host')) { $msg .= "\r\nHost: " . $message->getUri()->getHost(); } } elseif ($message instanceof ResponseInterface) { $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' . $message->getStatusCode() . ' ' . $message->getReasonPhrase(); } else { throw new \InvalidArgumentException('Unknown message type'); } foreach ($message->getHeaders() as $name => $values) { $msg .= "\r\n{$name}: " . implode(', ', $values); } return "{$msg}\r\n\r\n" . $message->getBody(); } function uri_for($uri) { if ($uri instanceof UriInterface) { return $uri; } elseif (is_string($uri)) { return new Uri($uri); } throw new \InvalidArgumentException('URI must be a string or UriInterface'); } function stream_for($resource = '', array $options = []) { if (is_scalar($resource)) { $stream = fopen('php://temp', 'r+'); if ($resource !== '') { fwrite($stream, $resource); fseek($stream, 0); } return new Stream($stream, $options); } switch (gettype($resource)) { case 'resource': return new Stream($resource, $options); case 'object': if ($resource instanceof StreamInterface) { return $resource; } elseif ($resource instanceof \Iterator) { return new PumpStream(function () use ($resource) { if (!$resource->valid()) { return false; } $result = $resource->current(); $resource->next(); return $result; }, $options); } elseif (method_exists($resource, '__toString')) { return stream_for((string) $resource, $options); } break; case 'NULL': return new Stream(fopen('php://temp', 'r+'), $options); } if (is_callable($resource)) { return new PumpStream($resource, $options); } throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); } function parse_header($header) { static $trimmed = "\"' \n\t\r"; $params = $matches = []; foreach (normalize_header($header) as $val) { $part = []; foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { $m = $matches[0]; if (isset($m[1])) { $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); } else { $part[] = trim($m[0], $trimmed); } } } if ($part) { $params[] = $part; } } return $params; } function normalize_header($header) { if (!is_array($header)) { return array_map('trim', explode(',', $header)); } $result = []; foreach ($header as $value) { foreach ((array) $value as $v) { if (strpos($v, ',') === false) { $result[] = $v; continue; } foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { $result[] = trim($vv); } } } return $result; } function modify_request(RequestInterface $request, array $changes) { if (!$changes) { return $request; } $headers = $request->getHeaders(); if (!isset($changes['uri'])) { $uri = $request->getUri(); } else { if ($host = $changes['uri']->getHost()) { $changes['set_headers']['Host'] = $host; if ($port = $changes['uri']->getPort()) { $standardPorts = ['http' => 80, 'https' => 443]; $scheme = $changes['uri']->getScheme(); if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { $changes['set_headers']['Host'] .= ':'.$port; } } } $uri = $changes['uri']; } if (!empty($changes['remove_headers'])) { $headers = _caseless_remove($changes['remove_headers'], $headers); } if (!empty($changes['set_headers'])) { $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); $headers = $changes['set_headers'] + $headers; } if (isset($changes['query'])) { $uri = $uri->withQuery($changes['query']); } if ($request instanceof ServerRequestInterface) { return new ServerRequest( isset($changes['method']) ? $changes['method'] : $request->getMethod(), $uri, $headers, isset($changes['body']) ? $changes['body'] : $request->getBody(), isset($changes['version']) ? $changes['version'] : $request->getProtocolVersion(), $request->getServerParams() ); } return new Request( isset($changes['method']) ? $changes['method'] : $request->getMethod(), $uri, $headers, isset($changes['body']) ? $changes['body'] : $request->getBody(), isset($changes['version']) ? $changes['version'] : $request->getProtocolVersion() ); } function rewind_body(MessageInterface $message) { $body = $message->getBody(); if ($body->tell()) { $body->rewind(); } } function try_fopen($filename, $mode) { $ex = null; set_error_handler(function () use ($filename, $mode, &$ex) { $ex = new \RuntimeException(sprintf( 'Unable to open %s using mode %s: %s', $filename, $mode, func_get_args()[1] )); }); $handle = fopen($filename, $mode); restore_error_handler(); if ($ex) { throw $ex; } return $handle; } function copy_to_string(StreamInterface $stream, $maxLen = -1) { $buffer = ''; if ($maxLen === -1) { while (!$stream->eof()) { $buf = $stream->read(1048576); if ($buf == null) { break; } $buffer .= $buf; } return $buffer; } $len = 0; while (!$stream->eof() && $len < $maxLen) { $buf = $stream->read($maxLen - $len); if ($buf == null) { break; } $buffer .= $buf; $len = strlen($buffer); } return $buffer; } function copy_to_stream( StreamInterface $source, StreamInterface $dest, $maxLen = -1 ) { $bufferSize = 8192; if ($maxLen === -1) { while (!$source->eof()) { if (!$dest->write($source->read($bufferSize))) { break; } } } else { $remaining = $maxLen; while ($remaining > 0 && !$source->eof()) { $buf = $source->read(min($bufferSize, $remaining)); $len = strlen($buf); if (!$len) { break; } $remaining -= $len; $dest->write($buf); } } } function hash( StreamInterface $stream, $algo, $rawOutput = false ) { $pos = $stream->tell(); if ($pos > 0) { $stream->rewind(); } $ctx = hash_init($algo); while (!$stream->eof()) { hash_update($ctx, $stream->read(1048576)); } $out = hash_final($ctx, (bool) $rawOutput); $stream->seek($pos); return $out; } function readline(StreamInterface $stream, $maxLength = null) { $buffer = ''; $size = 0; while (!$stream->eof()) { if (null == ($byte = $stream->read(1))) { return $buffer; } $buffer .= $byte; if ($byte === "\n" || ++$size === $maxLength - 1) { break; } } return $buffer; } function parse_request($message) { $data = _parse_message($message); $matches = []; if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { throw new \InvalidArgumentException('Invalid request string'); } $parts = explode(' ', $data['start-line'], 3); $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; $request = new Request( $parts[0], $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1], $data['headers'], $data['body'], $version ); return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); } function parse_response($message) { $data = _parse_message($message); if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { throw new \InvalidArgumentException('Invalid response string'); } $parts = explode(' ', $data['start-line'], 3); return new Response( $parts[1], $data['headers'], $data['body'], explode('/', $parts[0])[1], isset($parts[2]) ? $parts[2] : null ); } function parse_query($str, $urlEncoding = true) { $result = []; if ($str === '') { return $result; } if ($urlEncoding === true) { $decoder = function ($value) { return rawurldecode(str_replace('+', ' ', $value)); }; } elseif ($urlEncoding == PHP_QUERY_RFC3986) { $decoder = 'rawurldecode'; } elseif ($urlEncoding == PHP_QUERY_RFC1738) { $decoder = 'urldecode'; } else { $decoder = function ($str) { return $str; }; } foreach (explode('&', $str) as $kvp) { $parts = explode('=', $kvp, 2); $key = $decoder($parts[0]); $value = isset($parts[1]) ? $decoder($parts[1]) : null; if (!isset($result[$key])) { $result[$key] = $value; } else { if (!is_array($result[$key])) { $result[$key] = [$result[$key]]; } $result[$key][] = $value; } } return $result; } function build_query(array $params, $encoding = PHP_QUERY_RFC3986) { if (!$params) { return ''; } if ($encoding === false) { $encoder = function ($str) { return $str; }; } elseif ($encoding === PHP_QUERY_RFC3986) { $encoder = 'rawurlencode'; } elseif ($encoding === PHP_QUERY_RFC1738) { $encoder = 'urlencode'; } else { throw new \InvalidArgumentException('Invalid type'); } $qs = ''; foreach ($params as $k => $v) { $k = $encoder($k); if (!is_array($v)) { $qs .= $k; if ($v !== null) { $qs .= '=' . $encoder($v); } $qs .= '&'; } else { foreach ($v as $vv) { $qs .= $k; if ($vv !== null) { $qs .= '=' . $encoder($vv); } $qs .= '&'; } } } return $qs ? (string) substr($qs, 0, -1) : ''; } function mimetype_from_filename($filename) { return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); } function mimetype_from_extension($extension) { static $mimetypes = [ '7z' => 'application/x-7z-compressed', 'aac' => 'audio/x-aac', 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'asc' => 'text/plain', 'asf' => 'video/x-ms-asf', 'atom' => 'application/atom+xml', 'avi' => 'video/x-msvideo', 'bmp' => 'image/bmp', 'bz2' => 'application/x-bzip2', 'cer' => 'application/pkix-cert', 'crl' => 'application/pkix-crl', 'crt' => 'application/x-x509-ca-cert', 'css' => 'text/css', 'csv' => 'text/csv', 'cu' => 'application/cu-seeme', 'deb' => 'application/x-debian-package', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dvi' => 'application/x-dvi', 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', 'etx' => 'text/x-setext', 'flac' => 'audio/flac', 'flv' => 'video/x-flv', 'gif' => 'image/gif', 'gz' => 'application/gzip', 'htm' => 'text/html', 'html' => 'text/html', 'ico' => 'image/x-icon', 'ics' => 'text/calendar', 'ini' => 'text/plain', 'iso' => 'application/x-iso9660-image', 'jar' => 'application/java-archive', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'js' => 'text/javascript', 'json' => 'application/json', 'latex' => 'application/x-latex', 'log' => 'text/plain', 'm4a' => 'audio/mp4', 'm4v' => 'video/mp4', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mov' => 'video/quicktime', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'mp4a' => 'audio/mp4', 'mp4v' => 'video/mp4', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpg4' => 'video/mp4', 'oga' => 'audio/ogg', 'ogg' => 'audio/ogg', 'ogv' => 'video/ogg', 'ogx' => 'application/ogg', 'pbm' => 'image/x-portable-bitmap', 'pdf' => 'application/pdf', 'pgm' => 'image/x-portable-graymap', 'png' => 'image/png', 'pnm' => 'image/x-portable-anymap', 'ppm' => 'image/x-portable-pixmap', 'ppt' => 'application/vnd.ms-powerpoint', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ps' => 'application/postscript', 'qt' => 'video/quicktime', 'rar' => 'application/x-rar-compressed', 'ras' => 'image/x-cmu-raster', 'rss' => 'application/rss+xml', 'rtf' => 'application/rtf', 'sgm' => 'text/sgml', 'sgml' => 'text/sgml', 'svg' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash', 'tar' => 'application/x-tar', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'torrent' => 'application/x-bittorrent', 'ttf' => 'application/x-font-ttf', 'txt' => 'text/plain', 'wav' => 'audio/x-wav', 'webm' => 'video/webm', 'wma' => 'audio/x-ms-wma', 'wmv' => 'video/x-ms-wmv', 'woff' => 'application/x-font-woff', 'wsdl' => 'application/wsdl+xml', 'xbm' => 'image/x-xbitmap', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xml' => 'application/xml', 'xpm' => 'image/x-xpixmap', 'xwd' => 'image/x-xwindowdump', 'yaml' => 'text/yaml', 'yml' => 'text/yaml', 'zip' => 'application/zip', ]; $extension = strtolower($extension); return isset($mimetypes[$extension]) ? $mimetypes[$extension] : null; } function _parse_message($message) { if (!$message) { throw new \InvalidArgumentException('Invalid message'); } $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => '']; array_shift($lines); for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { $line = $lines[$i]; if (empty($line)) { if ($i < $totalLines - 1) { $result['body'] = implode('', array_slice($lines, $i + 2)); } break; } if (strpos($line, ':')) { $parts = explode(':', $line, 2); $key = trim($parts[0]); $value = isset($parts[1]) ? trim($parts[1]) : ''; $result['headers'][$key][] = $value; } } return $result; } function _parse_request_uri($path, array $headers) { $hostKey = array_filter(array_keys($headers), function ($k) { return strtolower($k) === 'host'; }); if (!$hostKey) { return $path; } $host = $headers[reset($hostKey)][0]; $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; return $scheme . '://' . $host . '/' . ltrim($path, '/'); } function _caseless_remove($keys, array $data) { $result = []; foreach ($keys as &$key) { $key = strtolower($key); } foreach ($data as $k => $v) { if (!in_array(strtolower($k), $keys)) { $result[$k] = $v; } } return $result; } addStream($stream); } } public function __toString() { try { $this->rewind(); return $this->getContents(); } catch (\Exception $e) { return ''; } } public function addStream(StreamInterface $stream) { if (!$stream->isReadable()) { throw new \InvalidArgumentException('Each stream must be readable'); } if (!$stream->isSeekable()) { $this->seekable = false; } $this->streams[] = $stream; } public function getContents() { return copy_to_string($this); } public function close() { $this->pos = $this->current = 0; foreach ($this->streams as $stream) { $stream->close(); } $this->streams = []; } public function detach() { $this->close(); $this->detached = true; } public function tell() { return $this->pos; } public function getSize() { $size = 0; foreach ($this->streams as $stream) { $s = $stream->getSize(); if ($s === null) { return null; } $size += $s; } return $size; } public function eof() { return !$this->streams || ($this->current >= count($this->streams) - 1 && $this->streams[$this->current]->eof()); } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { if (!$this->seekable) { throw new \RuntimeException('This AppendStream is not seekable'); } elseif ($whence !== SEEK_SET) { throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); } $this->pos = $this->current = 0; foreach ($this->streams as $i => $stream) { try { $stream->rewind(); } catch (\Exception $e) { throw new \RuntimeException('Unable to seek stream ' . $i . ' of the AppendStream', 0, $e); } } while ($this->pos < $offset && !$this->eof()) { $result = $this->read(min(8096, $offset - $this->pos)); if ($result === '') { break; } } } public function read($length) { $buffer = ''; $total = count($this->streams) - 1; $remaining = $length; $progressToNext = false; while ($remaining > 0) { if ($progressToNext || $this->streams[$this->current]->eof()) { $progressToNext = false; if ($this->current === $total) { break; } $this->current++; } $result = $this->streams[$this->current]->read($remaining); if ($result == null) { $progressToNext = true; continue; } $buffer .= $result; $remaining = $length - strlen($buffer); } $this->pos += strlen($buffer); return $buffer; } public function isReadable() { return true; } public function isWritable() { return false; } public function isSeekable() { return $this->seekable; } public function write($string) { throw new \RuntimeException('Cannot write to an AppendStream'); } public function getMetadata($key = null) { return $key ? null : []; } } hwm = $hwm; } public function __toString() { return $this->getContents(); } public function getContents() { $buffer = $this->buffer; $this->buffer = ''; return $buffer; } public function close() { $this->buffer = ''; } public function detach() { $this->close(); } public function getSize() { return strlen($this->buffer); } public function isReadable() { return true; } public function isWritable() { return true; } public function isSeekable() { return false; } public function rewind() { $this->seek(0); } public function seek($offset, $whence = SEEK_SET) { throw new \RuntimeException('Cannot seek a BufferStream'); } public function eof() { return strlen($this->buffer) === 0; } public function tell() { throw new \RuntimeException('Cannot determine the position of a BufferStream'); } public function read($length) { $currentLength = strlen($this->buffer); if ($length >= $currentLength) { $result = $this->buffer; $this->buffer = ''; } else { $result = substr($this->buffer, 0, $length); $this->buffer = substr($this->buffer, $length); } return $result; } public function write($string) { $this->buffer .= $string; if (strlen($this->buffer) >= $this->hwm) { return false; } return strlen($string); } public function getMetadata($key = null) { if ($key == 'hwm') { return $this->hwm; } return $key ? null : []; } } methods = $methods; foreach ($methods as $name => $fn) { $this->{'_fn_' . $name} = $fn; } } public function __get($name) { throw new \BadMethodCallException(str_replace('_fn_', '', $name) . '() is not implemented in the FnStream'); } public function __destruct() { if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } } public static function decorate(StreamInterface $stream, array $methods) { foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { $methods[$diff] = [$stream, $diff]; } return new self($methods); } public function __toString() { return call_user_func($this->_fn___toString); } public function close() { return call_user_func($this->_fn_close); } public function detach() { return call_user_func($this->_fn_detach); } public function getSize() { return call_user_func($this->_fn_getSize); } public function tell() { return call_user_func($this->_fn_tell); } public function eof() { return call_user_func($this->_fn_eof); } public function isSeekable() { return call_user_func($this->_fn_isSeekable); } public function rewind() { call_user_func($this->_fn_rewind); } public function seek($offset, $whence = SEEK_SET) { call_user_func($this->_fn_seek, $offset, $whence); } public function isWritable() { return call_user_func($this->_fn_isWritable); } public function write($string) { return call_user_func($this->_fn_write, $string); } public function isReadable() { return call_user_func($this->_fn_isReadable); } public function read($length) { return call_user_func($this->_fn_read, $length); } public function getContents() { return call_user_func($this->_fn_getContents); } public function getMetadata($key = null) { return call_user_func($this->_fn_getMetadata, $key); } } reason = $reason; } public function then( callable $onFulfilled = null, callable $onRejected = null ) { if (!$onRejected) { return $this; } $queue = queue(); $reason = $this->reason; $p = new Promise([$queue, 'run']); $queue->add(static function () use ($p, $reason, $onRejected) { if ($p->getState() === self::PENDING) { try { $p->resolve($onRejected($reason)); } catch (\Throwable $e) { $p->reject($e); } catch (\Exception $e) { $p->reject($e); } } }); return $p; } public function otherwise(callable $onRejected) { return $this->then(null, $onRejected); } public function wait($unwrap = true, $defaultDelivery = null) { if ($unwrap) { throw exception_for($this->reason); } } public function getState() { return self::REJECTED; } public function resolve($value) { throw new \LogicException("Cannot resolve a rejected promise"); } public function reject($reason) { if ($reason !== $this->reason) { throw new \LogicException("Cannot reject a rejected promise"); } } public function cancel() { } } generator = $generatorFn(); $this->result = new Promise(function () { while (isset($this->currentPromise)) { $this->currentPromise->wait(); } }); $this->nextCoroutine($this->generator->current()); } public function then( callable $onFulfilled = null, callable $onRejected = null ) { return $this->result->then($onFulfilled, $onRejected); } public function otherwise(callable $onRejected) { return $this->result->otherwise($onRejected); } public function wait($unwrap = true) { return $this->result->wait($unwrap); } public function getState() { return $this->result->getState(); } public function resolve($value) { $this->result->resolve($value); } public function reject($reason) { $this->result->reject($reason); } public function cancel() { $this->currentPromise->cancel(); $this->result->cancel(); } private function nextCoroutine($yielded) { $this->currentPromise = promise_for($yielded) ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); } public function _handleSuccess($value) { unset($this->currentPromise); try { $next = $this->generator->send($value); if ($this->generator->valid()) { $this->nextCoroutine($next); } else { $this->result->resolve($value); } } catch (Exception $exception) { $this->result->reject($exception); } catch (Throwable $throwable) { $this->result->reject($throwable); } } public function _handleFailure($reason) { unset($this->currentPromise); try { $nextYield = $this->generator->throw(exception_for($reason)); $this->nextCoroutine($nextYield); } catch (Exception $exception) { $this->result->reject($exception); } catch (Throwable $throwable) { $this->result->reject($throwable); } } } waitFn = $waitFn; $this->cancelFn = $cancelFn; } public function then( callable $onFulfilled = null, callable $onRejected = null ) { if ($this->state === self::PENDING) { $p = new Promise(null, [$this, 'cancel']); $this->handlers[] = [$p, $onFulfilled, $onRejected]; $p->waitList = $this->waitList; $p->waitList[] = $this; return $p; } if ($this->state === self::FULFILLED) { return $onFulfilled ? promise_for($this->result)->then($onFulfilled) : promise_for($this->result); } $rejection = rejection_for($this->result); return $onRejected ? $rejection->then(null, $onRejected) : $rejection; } public function otherwise(callable $onRejected) { return $this->then(null, $onRejected); } public function wait($unwrap = true) { $this->waitIfPending(); $inner = $this->result instanceof PromiseInterface ? $this->result->wait($unwrap) : $this->result; if ($unwrap) { if ($this->result instanceof PromiseInterface || $this->state === self::FULFILLED ) { return $inner; } else { throw exception_for($inner); } } } public function getState() { return $this->state; } public function cancel() { if ($this->state !== self::PENDING) { return; } $this->waitFn = $this->waitList = null; if ($this->cancelFn) { $fn = $this->cancelFn; $this->cancelFn = null; try { $fn(); } catch (\Throwable $e) { $this->reject($e); } catch (\Exception $e) { $this->reject($e); } } if ($this->state === self::PENDING) { $this->reject(new CancellationException('Promise has been cancelled')); } } public function resolve($value) { $this->settle(self::FULFILLED, $value); } public function reject($reason) { $this->settle(self::REJECTED, $reason); } private function settle($state, $value) { if ($this->state !== self::PENDING) { if ($state === $this->state && $value === $this->result) { return; } throw $this->state === $state ? new \LogicException("The promise is already {$state}.") : new \LogicException("Cannot change a {$this->state} promise to {$state}"); } if ($value === $this) { throw new \LogicException('Cannot fulfill or reject a promise with itself'); } $this->state = $state; $this->result = $value; $handlers = $this->handlers; $this->handlers = null; $this->waitList = $this->waitFn = null; $this->cancelFn = null; if (!$handlers) { return; } if (!method_exists($value, 'then')) { $id = $state === self::FULFILLED ? 1 : 2; queue()->add(static function () use ($id, $value, $handlers) { foreach ($handlers as $handler) { self::callHandler($id, $value, $handler); } }); } elseif ($value instanceof Promise && $value->getState() === self::PENDING ) { $value->handlers = array_merge($value->handlers, $handlers); } else { $value->then( static function ($value) use ($handlers) { foreach ($handlers as $handler) { self::callHandler(1, $value, $handler); } }, static function ($reason) use ($handlers) { foreach ($handlers as $handler) { self::callHandler(2, $reason, $handler); } } ); } } private static function callHandler($index, $value, array $handler) { $promise = $handler[0]; if ($promise->getState() !== self::PENDING) { return; } try { if (isset($handler[$index])) { $promise->resolve($handler[$index]($value)); } elseif ($index === 1) { $promise->resolve($value); } else { $promise->reject($value); } } catch (\Throwable $reason) { $promise->reject($reason); } catch (\Exception $reason) { $promise->reject($reason); } } private function waitIfPending() { if ($this->state !== self::PENDING) { return; } elseif ($this->waitFn) { $this->invokeWaitFn(); } elseif ($this->waitList) { $this->invokeWaitList(); } else { $this->reject('Cannot wait on a promise that has ' . 'no internal wait function. You must provide a wait ' . 'function when constructing the promise to be able to ' . 'wait on a promise.'); } queue()->run(); if ($this->state === self::PENDING) { $this->reject('Invoking the wait callback did not resolve the promise'); } } private function invokeWaitFn() { try { $wfn = $this->waitFn; $this->waitFn = null; $wfn(true); } catch (\Exception $reason) { if ($this->state === self::PENDING) { $this->reject($reason); } else { throw $reason; } } } private function invokeWaitList() { $waitList = $this->waitList; $this->waitList = null; foreach ($waitList as $result) { while (true) { $result->waitIfPending(); if ($result->result instanceof Promise) { $result = $result->result; } else { if ($result->result instanceof PromiseInterface) { $result->result->wait(false); } break; } } } } } enableShutdown) { $err = error_get_last(); if (!$err || ($err['type'] ^ E_ERROR)) { $this->run(); } } }); } } public function isEmpty() { return !$this->queue; } public function add(callable $task) { $this->queue[] = $task; } public function run() { while ($task = array_shift($this->queue)) { $task(); } } public function disableShutdown() { $this->enableShutdown = false; } } reason = $reason; $message = 'The promise was rejected'; if ($description) { $message .= ' with reason: ' . $description; } elseif (is_string($reason) || (is_object($reason) && method_exists($reason, '__toString')) ) { $message .= ' with reason: ' . $this->reason; } elseif ($reason instanceof \JsonSerializable) { $message .= ' with reason: ' . json_encode($this->reason, JSON_PRETTY_PRINT); } parent::__construct($message); } public function getReason() { return $this->reason; } } iterable = iter_for($iterable); if (isset($config['concurrency'])) { $this->concurrency = $config['concurrency']; } if (isset($config['fulfilled'])) { $this->onFulfilled = $config['fulfilled']; } if (isset($config['rejected'])) { $this->onRejected = $config['rejected']; } } public function promise() { if ($this->aggregate) { return $this->aggregate; } try { $this->createPromise(); $this->iterable->rewind(); $this->refillPending(); } catch (\Throwable $e) { $this->aggregate->reject($e); } catch (\Exception $e) { $this->aggregate->reject($e); } return $this->aggregate; } private function createPromise() { $this->mutex = false; $this->aggregate = new Promise(function () { reset($this->pending); if (empty($this->pending) && !$this->iterable->valid()) { $this->aggregate->resolve(null); return; } while ($promise = current($this->pending)) { next($this->pending); $promise->wait(); if ($this->aggregate->getState() !== PromiseInterface::PENDING) { return; } } }); $clearFn = function () { $this->iterable = $this->concurrency = $this->pending = null; $this->onFulfilled = $this->onRejected = null; }; $this->aggregate->then($clearFn, $clearFn); } private function refillPending() { if (!$this->concurrency) { while ($this->addPending() && $this->advanceIterator()); return; } $concurrency = is_callable($this->concurrency) ? call_user_func($this->concurrency, count($this->pending)) : $this->concurrency; $concurrency = max($concurrency - count($this->pending), 0); if (!$concurrency) { return; } $this->addPending(); while (--$concurrency && $this->advanceIterator() && $this->addPending()); } private function addPending() { if (!$this->iterable || !$this->iterable->valid()) { return false; } $promise = promise_for($this->iterable->current()); $idx = $this->iterable->key(); $this->pending[$idx] = $promise->then( function ($value) use ($idx) { if ($this->onFulfilled) { call_user_func( $this->onFulfilled, $value, $idx, $this->aggregate ); } $this->step($idx); }, function ($reason) use ($idx) { if ($this->onRejected) { call_user_func( $this->onRejected, $reason, $idx, $this->aggregate ); } $this->step($idx); } ); return true; } private function advanceIterator() { if ($this->mutex) { return false; } $this->mutex = true; try { $this->iterable->next(); $this->mutex = false; return true; } catch (\Throwable $e) { $this->aggregate->reject($e); $this->mutex = false; return false; } catch (\Exception $e) { $this->aggregate->reject($e); $this->mutex = false; return false; } } private function step($idx) { if ($this->aggregate->getState() !== PromiseInterface::PENDING) { return; } unset($this->pending[$idx]); if ($this->advanceIterator() && !$this->checkIfFinished()) { $this->refillPending(); } } private function checkIfFinished() { if (!$this->pending && !$this->iterable->valid()) { $this->aggregate->resolve(null); return true; } return false; } } add(function () use ($task, $promise) { try { $promise->resolve($task()); } catch (\Throwable $e) { $promise->reject($e); } catch (\Exception $e) { $promise->reject($e); } }); return $promise; } function promise_for($value) { if ($value instanceof PromiseInterface) { return $value; } if (method_exists($value, 'then')) { $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null; $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null; $promise = new Promise($wfn, $cfn); $value->then([$promise, 'resolve'], [$promise, 'reject']); return $promise; } return new FulfilledPromise($value); } function rejection_for($reason) { if ($reason instanceof PromiseInterface) { return $reason; } return new RejectedPromise($reason); } function exception_for($reason) { return $reason instanceof \Exception || $reason instanceof \Throwable ? $reason : new RejectionException($reason); } function iter_for($value) { if ($value instanceof \Iterator) { return $value; } elseif (is_array($value)) { return new \ArrayIterator($value); } else { return new \ArrayIterator([$value]); } } function inspect(PromiseInterface $promise) { try { return [ 'state' => PromiseInterface::FULFILLED, 'value' => $promise->wait() ]; } catch (RejectionException $e) { return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; } catch (\Throwable $e) { return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; } catch (\Exception $e) { return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; } } function inspect_all($promises) { $results = []; foreach ($promises as $key => $promise) { $results[$key] = inspect($promise); } return $results; } function unwrap($promises) { $results = []; foreach ($promises as $key => $promise) { $results[$key] = $promise->wait(); } return $results; } function all($promises) { $results = []; return each( $promises, function ($value, $idx) use (&$results) { $results[$idx] = $value; }, function ($reason, $idx, Promise $aggregate) { $aggregate->reject($reason); } )->then(function () use (&$results) { ksort($results); return $results; }); } function some($count, $promises) { $results = []; $rejections = []; return each( $promises, function ($value, $idx, PromiseInterface $p) use (&$results, $count) { if ($p->getState() !== PromiseInterface::PENDING) { return; } $results[$idx] = $value; if (count($results) >= $count) { $p->resolve(null); } }, function ($reason) use (&$rejections) { $rejections[] = $reason; } )->then( function () use (&$results, &$rejections, $count) { if (count($results) !== $count) { throw new AggregateException( 'Not enough promises to fulfill count', $rejections ); } ksort($results); return array_values($results); } ); } function any($promises) { return some(1, $promises)->then(function ($values) { return $values[0]; }); } function settle($promises) { $results = []; return each( $promises, function ($value, $idx) use (&$results) { $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; }, function ($reason, $idx) use (&$results) { $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; } )->then(function () use (&$results) { ksort($results); return $results; }); } function each( $iterable, callable $onFulfilled = null, callable $onRejected = null ) { return (new EachPromise($iterable, [ 'fulfilled' => $onFulfilled, 'rejected' => $onRejected ]))->promise(); } function each_limit( $iterable, $concurrency, callable $onFulfilled = null, callable $onRejected = null ) { return (new EachPromise($iterable, [ 'fulfilled' => $onFulfilled, 'rejected' => $onRejected, 'concurrency' => $concurrency ]))->promise(); } function each_limit_all( $iterable, $concurrency, callable $onFulfilled = null ) { return each_limit( $iterable, $concurrency, $onFulfilled, function ($reason, $idx, PromiseInterface $aggregate) { $aggregate->reject($reason); } ); } function is_fulfilled(PromiseInterface $promise) { return $promise->getState() === PromiseInterface::FULFILLED; } function is_rejected(PromiseInterface $promise) { return $promise->getState() === PromiseInterface::REJECTED; } function is_settled(PromiseInterface $promise) { return $promise->getState() !== PromiseInterface::PENDING; } function coroutine(callable $generatorFn) { return new Coroutine($generatorFn); } value = $value; } public function then( callable $onFulfilled = null, callable $onRejected = null ) { if (!$onFulfilled) { return $this; } $queue = queue(); $p = new Promise([$queue, 'run']); $value = $this->value; $queue->add(static function () use ($p, $value, $onFulfilled) { if ($p->getState() === self::PENDING) { try { $p->resolve($onFulfilled($value)); } catch (\Throwable $e) { $p->reject($e); } catch (\Exception $e) { $p->reject($e); } } }); return $p; } public function otherwise(callable $onRejected) { return $this->then(null, $onRejected); } public function wait($unwrap = true, $defaultDelivery = null) { return $unwrap ? $this->value : null; } public function getState() { return self::FULFILLED; } public function resolve($value) { if ($value !== $this->value) { throw new \LogicException("Cannot resolve a fulfilled promise"); } } public function reject($reason) { throw new \LogicException("Cannot reject a fulfilled promise"); } public function cancel() { } } $rfn) { if ($rfn instanceof RequestInterface) { yield $key => $client->sendAsync($rfn, $opts); } elseif (is_callable($rfn)) { yield $key => $rfn($opts); } else { throw new \InvalidArgumentException('Each value yielded by ' . 'the iterator must be a Psr7\Http\Message\RequestInterface ' . 'or a callable that returns a promise that fulfills ' . 'with a Psr7\Message\Http\ResponseInterface object.'); } } }; $this->each = new EachPromise($requests(), $config); } public function promise() { return $this->each->promise(); } public static function batch( ClientInterface $client, $requests, array $options = [] ) { $res = []; self::cmpCallback($options, 'fulfilled', $res); self::cmpCallback($options, 'rejected', $res); $pool = new static($client, $requests, $options); $pool->promise()->wait(); ksort($res); return $res; } private static function cmpCallback(array &$options, $name, array &$results) { if (!isset($options[$name])) { $options[$name] = function ($v, $k) use (&$results) { $results[$k] = $v; }; } else { $currentFn = $options[$name]; $options[$name] = function ($v, $k) use (&$results, $currentFn) { $currentFn($v, $k); $results[$k] = $v; }; } } } 5, 'protocols' => ['http', 'https'], 'strict' => false, 'referer' => false, 'track_redirects' => false, ]; private $nextHandler; public function __construct(callable $nextHandler) { $this->nextHandler = $nextHandler; } public function __invoke(RequestInterface $request, array $options) { $fn = $this->nextHandler; if (empty($options['allow_redirects'])) { return $fn($request, $options); } if ($options['allow_redirects'] === true) { $options['allow_redirects'] = self::$defaultSettings; } elseif (!is_array($options['allow_redirects'])) { throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); } else { $options['allow_redirects'] += self::$defaultSettings; } if (empty($options['allow_redirects']['max'])) { return $fn($request, $options); } return $fn($request, $options) ->then(function (ResponseInterface $response) use ($request, $options) { return $this->checkRedirect($request, $options, $response); }); } public function checkRedirect( RequestInterface $request, array $options, ResponseInterface $response ) { if (substr($response->getStatusCode(), 0, 1) != '3' || !$response->hasHeader('Location') ) { return $response; } $this->guardMax($request, $options); $nextRequest = $this->modifyRequest($request, $options, $response); if (isset($options['allow_redirects']['on_redirect'])) { call_user_func( $options['allow_redirects']['on_redirect'], $request, $response, $nextRequest->getUri() ); } $promise = $this($nextRequest, $options); if (!empty($options['allow_redirects']['track_redirects'])) { return $this->withTracking( $promise, (string) $nextRequest->getUri() ); } return $promise; } private function withTracking(PromiseInterface $promise, $uri) { return $promise->then( function (ResponseInterface $response) use ($uri) { $header = $response->getHeader(self::HISTORY_HEADER); array_unshift($header, $uri); return $response->withHeader(self::HISTORY_HEADER, $header); } ); } private function guardMax(RequestInterface $request, array &$options) { $current = isset($options['__redirect_count']) ? $options['__redirect_count'] : 0; $options['__redirect_count'] = $current + 1; $max = $options['allow_redirects']['max']; if ($options['__redirect_count'] > $max) { throw new TooManyRedirectsException( "Will not follow more than {$max} redirects", $request ); } } public function modifyRequest( RequestInterface $request, array $options, ResponseInterface $response ) { $modify = []; $protocols = $options['allow_redirects']['protocols']; $statusCode = $response->getStatusCode(); if ($statusCode == 303 || ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict']) ) { $modify['method'] = 'GET'; $modify['body'] = ''; } $modify['uri'] = $this->redirectUri($request, $response, $protocols); Psr7\rewind_body($request); if ($options['allow_redirects']['referer'] && $modify['uri']->getScheme() === $request->getUri()->getScheme() ) { $uri = $request->getUri()->withUserInfo('', ''); $modify['set_headers']['Referer'] = (string) $uri; } else { $modify['remove_headers'][] = 'Referer'; } if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { $modify['remove_headers'][] = 'Authorization'; } return Psr7\modify_request($request, $modify); } private function redirectUri( RequestInterface $request, ResponseInterface $response, array $protocols ) { $location = Psr7\UriResolver::resolve( $request->getUri(), new Psr7\Uri($response->getHeaderLine('Location')) ); if (!in_array($location->getScheme(), $protocols)) { throw new BadResponseException( sprintf( 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, implode(', ', $protocols) ), $request, $response ); } return $location; } } true, 'HEAD' => true]; public function __construct(callable $nextHandler) { $this->nextHandler = $nextHandler; } public function __invoke(RequestInterface $request, array $options) { $fn = $this->nextHandler; if (isset(self::$skipMethods[$request->getMethod()]) || $request->getBody()->getSize() === 0 ) { return $fn($request, $options); } $modify = []; if (!$request->hasHeader('Content-Type')) { if ($uri = $request->getBody()->getMetadata('uri')) { if ($type = Psr7\mimetype_from_filename($uri)) { $modify['set_headers']['Content-Type'] = $type; } } } if (!isset(self::$skipMethods[$request->getMethod()]) && !$request->hasHeader('Content-Length') && !$request->hasHeader('Transfer-Encoding') ) { $size = $request->getBody()->getSize(); if ($size !== null) { $modify['set_headers']['Content-Length'] = $size; } else { $modify['set_headers']['Transfer-Encoding'] = 'chunked'; } } $this->addExpectHeader($request, $options, $modify); return $fn(Psr7\modify_request($request, $modify), $options); } private function addExpectHeader( RequestInterface $request, array $options, array &$modify ) { if ($request->hasHeader('Expect')) { return; } $expect = isset($options['expect']) ? $options['expect'] : null; if ($expect === false || $request->getProtocolVersion() < 1.1) { return; } if ($expect === true) { $modify['set_headers']['Expect'] = '100-Continue'; return; } if ($expect === null) { $expect = 1048576; } $body = $request->getBody(); $size = $body->getSize(); if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { $modify['set_headers']['Expect'] = '100-Continue'; } } } configureDefaults($config); } public function __call($method, $args) { if (count($args) < 1) { throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); } $uri = $args[0]; $opts = isset($args[1]) ? $args[1] : []; return substr($method, -5) === 'Async' ? $this->requestAsync(substr($method, 0, -5), $uri, $opts) : $this->request($method, $uri, $opts); } public function sendAsync(RequestInterface $request, array $options = []) { $options = $this->prepareDefaults($options); return $this->transfer( $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), $options ); } public function send(RequestInterface $request, array $options = []) { $options[RequestOptions::SYNCHRONOUS] = true; return $this->sendAsync($request, $options)->wait(); } public function requestAsync($method, $uri = '', array $options = []) { $options = $this->prepareDefaults($options); $headers = isset($options['headers']) ? $options['headers'] : []; $body = isset($options['body']) ? $options['body'] : null; $version = isset($options['version']) ? $options['version'] : '1.1'; $uri = $this->buildUri($uri, $options); if (is_array($body)) { $this->invalidBody(); } $request = new Psr7\Request($method, $uri, $headers, $body, $version); unset($options['headers'], $options['body'], $options['version']); return $this->transfer($request, $options); } public function request($method, $uri = '', array $options = []) { $options[RequestOptions::SYNCHRONOUS] = true; return $this->requestAsync($method, $uri, $options)->wait(); } public function getConfig($option = null) { return $option === null ? $this->config : (isset($this->config[$option]) ? $this->config[$option] : null); } private function buildUri($uri, array $config) { $uri = Psr7\uri_for($uri === null ? '' : $uri); if (isset($config['base_uri'])) { $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); } return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; } private function configureDefaults(array $config) { $defaults = [ 'allow_redirects' => RedirectMiddleware::$defaultSettings, 'http_errors' => true, 'decode_content' => true, 'verify' => true, 'cookies' => false ]; if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) { $defaults['proxy']['http'] = getenv('HTTP_PROXY'); } if ($proxy = getenv('HTTPS_PROXY')) { $defaults['proxy']['https'] = $proxy; } if ($noProxy = getenv('NO_PROXY')) { $cleanedNoProxy = str_replace(' ', '', $noProxy); $defaults['proxy']['no'] = explode(',', $cleanedNoProxy); } $this->config = $config + $defaults; if (!empty($config['cookies']) && $config['cookies'] === true) { $this->config['cookies'] = new CookieJar(); } if (!isset($this->config['headers'])) { $this->config['headers'] = ['User-Agent' => default_user_agent()]; } else { foreach (array_keys($this->config['headers']) as $name) { if (strtolower($name) === 'user-agent') { return; } } $this->config['headers']['User-Agent'] = default_user_agent(); } } private function prepareDefaults($options) { $defaults = $this->config; if (!empty($defaults['headers'])) { $defaults['_conditional'] = $defaults['headers']; unset($defaults['headers']); } if (array_key_exists('headers', $options)) { if ($options['headers'] === null) { $defaults['_conditional'] = null; unset($options['headers']); } elseif (!is_array($options['headers'])) { throw new \InvalidArgumentException('headers must be an array'); } } $result = $options + $defaults; foreach ($result as $k => $v) { if ($v === null) { unset($result[$k]); } } return $result; } private function transfer(RequestInterface $request, array $options) { if (isset($options['save_to'])) { $options['sink'] = $options['save_to']; unset($options['save_to']); } if (isset($options['exceptions'])) { $options['http_errors'] = $options['exceptions']; unset($options['exceptions']); } $request = $this->applyOptions($request, $options); $handler = $options['handler']; try { return Promise\promise_for($handler($request, $options)); } catch (\Exception $e) { return Promise\rejection_for($e); } } private function applyOptions(RequestInterface $request, array &$options) { $modify = []; if (isset($options['form_params'])) { if (isset($options['multipart'])) { throw new \InvalidArgumentException('You cannot use ' . 'form_params and multipart at the same time. Use the ' . 'form_params option if you want to send application/' . 'x-www-form-urlencoded requests, and the multipart ' . 'option to send multipart/form-data requests.'); } $options['body'] = http_build_query($options['form_params'], '', '&'); unset($options['form_params']); $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; } if (isset($options['multipart'])) { $options['body'] = new Psr7\MultipartStream($options['multipart']); unset($options['multipart']); } if (isset($options['json'])) { $options['body'] = \GuzzleHttp\json_encode($options['json']); unset($options['json']); $options['_conditional']['Content-Type'] = 'application/json'; } if (!empty($options['decode_content']) && $options['decode_content'] !== true ) { $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; } if (isset($options['headers'])) { if (isset($modify['set_headers'])) { $modify['set_headers'] = $options['headers'] + $modify['set_headers']; } else { $modify['set_headers'] = $options['headers']; } unset($options['headers']); } if (isset($options['body'])) { if (is_array($options['body'])) { $this->invalidBody(); } $modify['body'] = Psr7\stream_for($options['body']); unset($options['body']); } if (!empty($options['auth']) && is_array($options['auth'])) { $value = $options['auth']; $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; switch ($type) { case 'basic': $modify['set_headers']['Authorization'] = 'Basic ' . base64_encode("$value[0]:$value[1]"); break; case 'digest': $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; break; } } if (isset($options['query'])) { $value = $options['query']; if (is_array($value)) { $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986); } if (!is_string($value)) { throw new \InvalidArgumentException('query must be a string or array'); } $modify['query'] = $value; unset($options['query']); } if (isset($options['sink'])) { if (is_bool($options['sink'])) { throw new \InvalidArgumentException('sink must not be a boolean'); } } $request = Psr7\modify_request($request, $modify); if ($request->getBody() instanceof Psr7\MultipartStream) { $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' . $request->getBody()->getBoundary(); } if (isset($options['_conditional'])) { $modify = []; foreach ($options['_conditional'] as $k => $v) { if (!$request->hasHeader($k)) { $modify['set_headers'][$k] = $v; } } $request = Psr7\modify_request($request, $modify); unset($options['_conditional']); } return $request; } private function invalidBody() { throw new \InvalidArgumentException('Passing in the "body" request ' . 'option as an array to send a POST request has been deprecated. ' . 'Please use the "form_params" request option to send a ' . 'application/x-www-form-urlencoded request, or a the "multipart" ' . 'request option to send a multipart/form-data request.'); } } onFulfilled = $onFulfilled; $this->onRejected = $onRejected; if ($queue) { call_user_func_array([$this, 'append'], $queue); } } public function __invoke(RequestInterface $request, array $options) { if (!$this->queue) { throw new \OutOfBoundsException('Mock queue is empty'); } if (isset($options['delay'])) { usleep($options['delay'] * 1000); } $this->lastRequest = $request; $this->lastOptions = $options; $response = array_shift($this->queue); if (is_callable($response)) { $response = call_user_func($response, $request, $options); } $response = $response instanceof \Exception ? new RejectedPromise($response) : \GuzzleHttp\Promise\promise_for($response); return $response->then( function ($value) use ($request, $options) { $this->invokeStats($request, $options, $value); if ($this->onFulfilled) { call_user_func($this->onFulfilled, $value); } if (isset($options['sink'])) { $contents = (string) $value->getBody(); $sink = $options['sink']; if (is_resource($sink)) { fwrite($sink, $contents); } elseif (is_string($sink)) { file_put_contents($sink, $contents); } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) { $sink->write($contents); } } return $value; }, function ($reason) use ($request, $options) { $this->invokeStats($request, $options, null, $reason); if ($this->onRejected) { call_user_func($this->onRejected, $reason); } return new RejectedPromise($reason); } ); } public function append() { foreach (func_get_args() as $value) { if ($value instanceof ResponseInterface || $value instanceof \Exception || $value instanceof PromiseInterface || is_callable($value) ) { $this->queue[] = $value; } else { throw new \InvalidArgumentException('Expected a response or ' . 'exception. Found ' . \GuzzleHttp\describe_type($value)); } } } public function getLastRequest() { return $this->lastRequest; } public function getLastOptions() { return $this->lastOptions; } public function count() { return count($this->queue); } private function invokeStats( RequestInterface $request, array $options, ResponseInterface $response = null, $reason = null ) { if (isset($options['on_stats'])) { $stats = new TransferStats($request, $response, 0, $reason); call_user_func($options['on_stats'], $stats); } } } maxHandles = $maxHandles; } public function create(RequestInterface $request, array $options) { if (isset($options['curl']['body_as_string'])) { $options['_body_as_string'] = $options['curl']['body_as_string']; unset($options['curl']['body_as_string']); } $easy = new EasyHandle; $easy->request = $request; $easy->options = $options; $conf = $this->getDefaultConf($easy); $this->applyMethod($easy, $conf); $this->applyHandlerOptions($easy, $conf); $this->applyHeaders($easy, $conf); unset($conf['_headers']); if (isset($options['curl'])) { $conf = array_replace($conf, $options['curl']); } $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); $easy->handle = $this->handles ? array_pop($this->handles) : curl_init(); curl_setopt_array($easy->handle, $conf); return $easy; } public function release(EasyHandle $easy) { $resource = $easy->handle; unset($easy->handle); if (count($this->handles) >= $this->maxHandles) { curl_close($resource); } else { curl_setopt($resource, CURLOPT_HEADERFUNCTION, null); curl_setopt($resource, CURLOPT_READFUNCTION, null); curl_setopt($resource, CURLOPT_WRITEFUNCTION, null); curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null); curl_reset($resource); $this->handles[] = $resource; } } public static function finish( callable $handler, EasyHandle $easy, CurlFactoryInterface $factory ) { if (isset($easy->options['on_stats'])) { self::invokeStats($easy); } if (!$easy->response || $easy->errno) { return self::finishError($handler, $easy, $factory); } $factory->release($easy); $body = $easy->response->getBody(); if ($body->isSeekable()) { $body->rewind(); } return new FulfilledPromise($easy->response); } private static function invokeStats(EasyHandle $easy) { $curlStats = curl_getinfo($easy->handle); $stats = new TransferStats( $easy->request, $easy->response, $curlStats['total_time'], $easy->errno, $curlStats ); call_user_func($easy->options['on_stats'], $stats); } private static function finishError( callable $handler, EasyHandle $easy, CurlFactoryInterface $factory ) { $ctx = [ 'errno' => $easy->errno, 'error' => curl_error($easy->handle), ] + curl_getinfo($easy->handle); $factory->release($easy); if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65) ) { return self::retryFailedRewind($handler, $easy, $ctx); } return self::createRejection($easy, $ctx); } private static function createRejection(EasyHandle $easy, array $ctx) { static $connectionErrors = [ CURLE_OPERATION_TIMEOUTED => true, CURLE_COULDNT_RESOLVE_HOST => true, CURLE_COULDNT_CONNECT => true, CURLE_SSL_CONNECT_ERROR => true, CURLE_GOT_NOTHING => true, ]; if ($easy->onHeadersException) { return new RejectedPromise( new RequestException( 'An error was encountered during the on_headers event', $easy->request, $easy->response, $easy->onHeadersException, $ctx ) ); } $message = sprintf( 'cURL error %s: %s (%s)', $ctx['errno'], $ctx['error'], 'see http://curl.haxx.se/libcurl/c/libcurl-errors.html' ); $error = isset($connectionErrors[$easy->errno]) ? new ConnectException($message, $easy->request, null, $ctx) : new RequestException($message, $easy->request, $easy->response, null, $ctx); return new RejectedPromise($error); } private function getDefaultConf(EasyHandle $easy) { $conf = [ '_headers' => $easy->request->getHeaders(), CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), CURLOPT_RETURNTRANSFER => false, CURLOPT_HEADER => false, CURLOPT_CONNECTTIMEOUT => 150, ]; if (defined('CURLOPT_PROTOCOLS')) { $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } $version = $easy->request->getProtocolVersion(); if ($version == 1.1) { $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; } elseif ($version == 2.0) { $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; } else { $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; } return $conf; } private function applyMethod(EasyHandle $easy, array &$conf) { $body = $easy->request->getBody(); $size = $body->getSize(); if ($size === null || $size > 0) { $this->applyBody($easy->request, $easy->options, $conf); return; } $method = $easy->request->getMethod(); if ($method === 'PUT' || $method === 'POST') { if (!$easy->request->hasHeader('Content-Length')) { $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; } } elseif ($method === 'HEAD') { $conf[CURLOPT_NOBODY] = true; unset( $conf[CURLOPT_WRITEFUNCTION], $conf[CURLOPT_READFUNCTION], $conf[CURLOPT_FILE], $conf[CURLOPT_INFILE] ); } } private function applyBody(RequestInterface $request, array $options, array &$conf) { $size = $request->hasHeader('Content-Length') ? (int) $request->getHeaderLine('Content-Length') : null; if (($size !== null && $size < 1000000) || !empty($options['_body_as_string']) ) { $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody(); $this->removeHeader('Content-Length', $conf); $this->removeHeader('Transfer-Encoding', $conf); } else { $conf[CURLOPT_UPLOAD] = true; if ($size !== null) { $conf[CURLOPT_INFILESIZE] = $size; $this->removeHeader('Content-Length', $conf); } $body = $request->getBody(); if ($body->isSeekable()) { $body->rewind(); } $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { return $body->read($length); }; } if (!$request->hasHeader('Expect')) { $conf[CURLOPT_HTTPHEADER][] = 'Expect:'; } if (!$request->hasHeader('Content-Type')) { $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:'; } } private function applyHeaders(EasyHandle $easy, array &$conf) { foreach ($conf['_headers'] as $name => $values) { foreach ($values as $value) { $conf[CURLOPT_HTTPHEADER][] = "$name: $value"; } } if (!$easy->request->hasHeader('Accept')) { $conf[CURLOPT_HTTPHEADER][] = 'Accept:'; } } private function removeHeader($name, array &$options) { foreach (array_keys($options['_headers']) as $key) { if (!strcasecmp($key, $name)) { unset($options['_headers'][$key]); return; } } } private function applyHandlerOptions(EasyHandle $easy, array &$conf) { $options = $easy->options; if (isset($options['verify'])) { if ($options['verify'] === false) { unset($conf[CURLOPT_CAINFO]); $conf[CURLOPT_SSL_VERIFYHOST] = 0; $conf[CURLOPT_SSL_VERIFYPEER] = false; } else { $conf[CURLOPT_SSL_VERIFYHOST] = 2; $conf[CURLOPT_SSL_VERIFYPEER] = true; if (is_string($options['verify'])) { $conf[CURLOPT_CAINFO] = $options['verify']; if (!file_exists($options['verify'])) { throw new \InvalidArgumentException( "SSL CA bundle not found: {$options['verify']}" ); } } } } if (!empty($options['decode_content'])) { $accept = $easy->request->getHeaderLine('Accept-Encoding'); if ($accept) { $conf[CURLOPT_ENCODING] = $accept; } else { $conf[CURLOPT_ENCODING] = ''; $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; } } if (isset($options['sink'])) { $sink = $options['sink']; if (!is_string($sink)) { $sink = \GuzzleHttp\Psr7\stream_for($sink); } elseif (!is_dir(dirname($sink))) { throw new \RuntimeException(sprintf( 'Directory %s does not exist for sink value of %s', dirname($sink), $sink )); } else { $sink = new LazyOpenStream($sink, 'w+'); } $easy->sink = $sink; $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { return $sink->write($write); }; } else { $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); } if (isset($options['timeout'])) { $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; } if (isset($options['connect_timeout'])) { $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; } if (isset($options['proxy'])) { if (!is_array($options['proxy'])) { $conf[CURLOPT_PROXY] = $options['proxy']; } else { $scheme = $easy->request->getUri()->getScheme(); if (isset($options['proxy'][$scheme])) { $host = $easy->request->getUri()->getHost(); if (!isset($options['proxy']['no']) || !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) ) { $conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; } } } } if (isset($options['cert'])) { $cert = $options['cert']; if (is_array($cert)) { $conf[CURLOPT_SSLCERTPASSWD] = $cert[1]; $cert = $cert[0]; } if (!file_exists($cert)) { throw new \InvalidArgumentException( "SSL certificate not found: {$cert}" ); } $conf[CURLOPT_SSLCERT] = $cert; } if (isset($options['ssl_key'])) { $sslKey = $options['ssl_key']; if (is_array($sslKey)) { $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1]; $sslKey = $sslKey[0]; } if (!file_exists($sslKey)) { throw new \InvalidArgumentException( "SSL private key not found: {$sslKey}" ); } $conf[CURLOPT_SSLKEY] = $sslKey; } if (isset($options['progress'])) { $progress = $options['progress']; if (!is_callable($progress)) { throw new \InvalidArgumentException( 'progress client option must be callable' ); } $conf[CURLOPT_NOPROGRESS] = false; $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) { $args = func_get_args(); if (is_resource($args[0])) { array_shift($args); } call_user_func_array($progress, $args); }; } if (!empty($options['debug'])) { $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']); $conf[CURLOPT_VERBOSE] = true; } } private static function retryFailedRewind( callable $handler, EasyHandle $easy, array $ctx ) { try { $body = $easy->request->getBody(); if ($body->tell() > 0) { $body->rewind(); } } catch (\RuntimeException $e) { $ctx['error'] = 'The connection unexpectedly failed without ' . 'providing an error. The request would have been retried, ' . 'but attempting to rewind the request body failed. ' . 'Exception: ' . $e; return self::createRejection($easy, $ctx); } if (!isset($easy->options['_curl_retries'])) { $easy->options['_curl_retries'] = 1; } elseif ($easy->options['_curl_retries'] == 2) { $ctx['error'] = 'The cURL request was retried 3 times ' . 'and did not succeed. The most likely reason for the failure ' . 'is that cURL was unable to rewind the body of the request ' . 'and subsequent retries resulted in the same error. Turn on ' . 'the debug option to see what went wrong. See ' . 'https://bugs.php.net/bug.php?id=47204 for more information.'; return self::createRejection($easy, $ctx); } else { $easy->options['_curl_retries']++; } return $handler($easy->request, $easy->options); } private function createHeaderFn(EasyHandle $easy) { if (isset($easy->options['on_headers'])) { $onHeaders = $easy->options['on_headers']; if (!is_callable($onHeaders)) { throw new \InvalidArgumentException('on_headers must be callable'); } } else { $onHeaders = null; } return function ($ch, $h) use ( $onHeaders, $easy, &$startingResponse ) { $value = trim($h); if ($value === '') { $startingResponse = true; $easy->createResponse(); if ($onHeaders !== null) { try { $onHeaders($easy->response); } catch (\Exception $e) { $easy->onHeadersException = $e; return -1; } } } elseif ($startingResponse) { $startingResponse = false; $easy->headers = [$value]; } else { $easy->headers[] = $value; } return strlen($h); }; } } headers)) { throw new \RuntimeException('No headers have been received'); } $startLine = explode(' ', array_shift($this->headers), 3); $headers = \GuzzleHttp\headers_from_lines($this->headers); $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding']) ) { $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; unset($headers[$normalizedKeys['content-encoding']]); if (isset($normalizedKeys['content-length'])) { $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; $bodyLength = (int) $this->sink->getSize(); if ($bodyLength) { $headers[$normalizedKeys['content-length']] = $bodyLength; } else { unset($headers[$normalizedKeys['content-length']]); } } } $this->response = new Response( $startLine[1], $headers, $this->sink, substr($startLine[0], 5), isset($startLine[2]) ? (string) $startLine[2] : null ); } public function __get($name) { $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name; throw new \BadMethodCallException($msg); } } withoutHeader('Expect'); if (0 === $request->getBody()->getSize()) { $request = $request->withHeader('Content-Length', 0); } return $this->createResponse( $request, $options, $this->createStream($request, $options), $startTime ); } catch (\InvalidArgumentException $e) { throw $e; } catch (\Exception $e) { $message = $e->getMessage(); if (strpos($message, 'getaddrinfo') || strpos($message, 'Connection refused') || strpos($message, "couldn't connect to host") ) { $e = new ConnectException($e->getMessage(), $request, $e); } $e = RequestException::wrapException($request, $e); $this->invokeStats($options, $request, $startTime, null, $e); return new RejectedPromise($e); } } private function invokeStats( array $options, RequestInterface $request, $startTime, ResponseInterface $response = null, $error = null ) { if (isset($options['on_stats'])) { $stats = new TransferStats( $request, $response, microtime(true) - $startTime, $error, [] ); call_user_func($options['on_stats'], $stats); } } private function createResponse( RequestInterface $request, array $options, $stream, $startTime ) { $hdrs = $this->lastHeaders; $this->lastHeaders = []; $parts = explode(' ', array_shift($hdrs), 3); $ver = explode('/', $parts[0])[1]; $status = $parts[1]; $reason = isset($parts[2]) ? $parts[2] : null; $headers = \GuzzleHttp\headers_from_lines($hdrs); list ($stream, $headers) = $this->checkDecode($options, $headers, $stream); $stream = Psr7\stream_for($stream); $sink = $stream; if (strcasecmp('HEAD', $request->getMethod())) { $sink = $this->createSink($stream, $options); } $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); if (isset($options['on_headers'])) { try { $options['on_headers']($response); } catch (\Exception $e) { $msg = 'An error was encountered during the on_headers event'; $ex = new RequestException($msg, $request, $response, $e); return new RejectedPromise($ex); } } if ($sink !== $stream) { $this->drain( $stream, $sink, $response->getHeaderLine('Content-Length') ); } $this->invokeStats($options, $request, $startTime, $response, null); return new FulfilledPromise($response); } private function createSink(StreamInterface $stream, array $options) { if (!empty($options['stream'])) { return $stream; } $sink = isset($options['sink']) ? $options['sink'] : fopen('php://temp', 'r+'); return is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\stream_for($sink); } private function checkDecode(array $options, array $headers, $stream) { if (!empty($options['decode_content'])) { $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); if (isset($normalizedKeys['content-encoding'])) { $encoding = $headers[$normalizedKeys['content-encoding']]; if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { $stream = new Psr7\InflateStream( Psr7\stream_for($stream) ); $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; unset($headers[$normalizedKeys['content-encoding']]); if (isset($normalizedKeys['content-length'])) { $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; $length = (int) $stream->getSize(); if ($length === 0) { unset($headers[$normalizedKeys['content-length']]); } else { $headers[$normalizedKeys['content-length']] = [$length]; } } } } } return [$stream, $headers]; } private function drain( StreamInterface $source, StreamInterface $sink, $contentLength ) { Psr7\copy_to_stream( $source, $sink, (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 ); $sink->seek(0); $source->close(); return $sink; } private function createResource(callable $callback) { $errors = null; set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { $errors[] = [ 'message' => $msg, 'file' => $file, 'line' => $line ]; return true; }); $resource = $callback(); restore_error_handler(); if (!$resource) { $message = 'Error creating resource: '; foreach ($errors as $err) { foreach ($err as $key => $value) { $message .= "[$key] $value" . PHP_EOL; } } throw new \RuntimeException(trim($message)); } return $resource; } private function createStream(RequestInterface $request, array $options) { static $methods; if (!$methods) { $methods = array_flip(get_class_methods(__CLASS__)); } if ($request->getProtocolVersion() == '1.1' && !$request->hasHeader('Connection') ) { $request = $request->withHeader('Connection', 'close'); } if (!isset($options['verify'])) { $options['verify'] = true; } $params = []; $context = $this->getDefaultContext($request, $options); if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { throw new \InvalidArgumentException('on_headers must be callable'); } if (!empty($options)) { foreach ($options as $key => $value) { $method = "add_{$key}"; if (isset($methods[$method])) { $this->{$method}($request, $context, $value, $params); } } } if (isset($options['stream_context'])) { if (!is_array($options['stream_context'])) { throw new \InvalidArgumentException('stream_context must be an array'); } $context = array_replace_recursive( $context, $options['stream_context'] ); } $context = $this->createResource( function () use ($context, $params) { return stream_context_create($context, $params); } ); return $this->createResource( function () use ($request, &$http_response_header, $context) { $resource = fopen((string) $request->getUri()->withFragment(''), 'r', null, $context); $this->lastHeaders = $http_response_header; return $resource; } ); } private function getDefaultContext(RequestInterface $request) { $headers = ''; foreach ($request->getHeaders() as $name => $value) { foreach ($value as $val) { $headers .= "$name: $val\r\n"; } } $context = [ 'http' => [ 'method' => $request->getMethod(), 'header' => $headers, 'protocol_version' => $request->getProtocolVersion(), 'ignore_errors' => true, 'follow_location' => 0, ], ]; $body = (string) $request->getBody(); if (!empty($body)) { $context['http']['content'] = $body; if (!$request->hasHeader('Content-Type')) { $context['http']['header'] .= "Content-Type:\r\n"; } } $context['http']['header'] = rtrim($context['http']['header']); return $context; } private function add_proxy(RequestInterface $request, &$options, $value, &$params) { if (!is_array($value)) { $options['http']['proxy'] = $value; } else { $scheme = $request->getUri()->getScheme(); if (isset($value[$scheme])) { if (!isset($value['no']) || !\GuzzleHttp\is_host_in_noproxy( $request->getUri()->getHost(), $value['no'] ) ) { $options['http']['proxy'] = $value[$scheme]; } } } } private function add_timeout(RequestInterface $request, &$options, $value, &$params) { if ($value > 0) { $options['http']['timeout'] = $value; } } private function add_verify(RequestInterface $request, &$options, $value, &$params) { if ($value === true) { if (PHP_VERSION_ID < 50600) { $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); } } elseif (is_string($value)) { $options['ssl']['cafile'] = $value; if (!file_exists($value)) { throw new \RuntimeException("SSL CA bundle not found: $value"); } } elseif ($value === false) { $options['ssl']['verify_peer'] = false; $options['ssl']['verify_peer_name'] = false; return; } else { throw new \InvalidArgumentException('Invalid verify request option'); } $options['ssl']['verify_peer'] = true; $options['ssl']['verify_peer_name'] = true; $options['ssl']['allow_self_signed'] = false; } private function add_cert(RequestInterface $request, &$options, $value, &$params) { if (is_array($value)) { $options['ssl']['passphrase'] = $value[1]; $value = $value[0]; } if (!file_exists($value)) { throw new \RuntimeException("SSL certificate not found: {$value}"); } $options['ssl']['local_cert'] = $value; } private function add_progress(RequestInterface $request, &$options, $value, &$params) { $this->addNotification( $params, function ($code, $a, $b, $c, $transferred, $total) use ($value) { if ($code == STREAM_NOTIFY_PROGRESS) { $value($total, $transferred, null, null); } } ); } private function add_debug(RequestInterface $request, &$options, $value, &$params) { if ($value === false) { return; } static $map = [ STREAM_NOTIFY_CONNECT => 'CONNECT', STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', STREAM_NOTIFY_PROGRESS => 'PROGRESS', STREAM_NOTIFY_FAILURE => 'FAILURE', STREAM_NOTIFY_COMPLETED => 'COMPLETED', STREAM_NOTIFY_RESOLVE => 'RESOLVE', ]; static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; $value = \GuzzleHttp\debug_resource($value); $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); $this->addNotification( $params, function () use ($ident, $value, $map, $args) { $passed = func_get_args(); $code = array_shift($passed); fprintf($value, '<%s> [%s] ', $ident, $map[$code]); foreach (array_filter($passed) as $i => $v) { fwrite($value, $args[$i] . ': "' . $v . '" '); } fwrite($value, "\n"); } ); } private function addNotification(array &$params, callable $notify) { if (!isset($params['notification'])) { $params['notification'] = $notify; } else { $params['notification'] = $this->callArray([ $params['notification'], $notify ]); } } private function callArray(array $functions) { return function () use ($functions) { $args = func_get_args(); foreach ($functions as $fn) { call_user_func_array($fn, $args); } }; } } factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(3); } public function __invoke(RequestInterface $request, array $options) { if (isset($options['delay'])) { usleep($options['delay'] * 1000); } $easy = $this->factory->create($request, $options); curl_exec($easy->handle); $easy->errno = curl_errno($easy->handle); return CurlFactory::finish($this, $easy, $this->factory); } } factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(50); $this->selectTimeout = isset($options['select_timeout']) ? $options['select_timeout'] : 1; } public function __get($name) { if ($name === '_mh') { return $this->_mh = curl_multi_init(); } throw new \BadMethodCallException(); } public function __destruct() { if (isset($this->_mh)) { curl_multi_close($this->_mh); unset($this->_mh); } } public function __invoke(RequestInterface $request, array $options) { $easy = $this->factory->create($request, $options); $id = (int) $easy->handle; $promise = new Promise( [$this, 'execute'], function () use ($id) { return $this->cancel($id); } ); $this->addRequest(['easy' => $easy, 'deferred' => $promise]); return $promise; } public function tick() { if ($this->delays) { $currentTime = microtime(true); foreach ($this->delays as $id => $delay) { if ($currentTime >= $delay) { unset($this->delays[$id]); curl_multi_add_handle( $this->_mh, $this->handles[$id]['easy']->handle ); } } } P\queue()->run(); if ($this->active && curl_multi_select($this->_mh, $this->selectTimeout) === -1 ) { usleep(250); } while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM); $this->processMessages(); } public function execute() { $queue = P\queue(); while ($this->handles || !$queue->isEmpty()) { if (!$this->active && $this->delays) { usleep($this->timeToNext()); } $this->tick(); } } private function addRequest(array $entry) { $easy = $entry['easy']; $id = (int) $easy->handle; $this->handles[$id] = $entry; if (empty($easy->options['delay'])) { curl_multi_add_handle($this->_mh, $easy->handle); } else { $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000); } } private function cancel($id) { if (!isset($this->handles[$id])) { return false; } $handle = $this->handles[$id]['easy']->handle; unset($this->delays[$id], $this->handles[$id]); curl_multi_remove_handle($this->_mh, $handle); curl_close($handle); return true; } private function processMessages() { while ($done = curl_multi_info_read($this->_mh)) { $id = (int) $done['handle']; curl_multi_remove_handle($this->_mh, $done['handle']); if (!isset($this->handles[$id])) { continue; } $entry = $this->handles[$id]; unset($this->handles[$id], $this->delays[$id]); $entry['easy']->errno = $done['result']; $entry['deferred']->resolve( CurlFactory::finish( $this, $entry['easy'], $this->factory ) ); } } private function timeToNext() { $currentTime = microtime(true); $nextTime = PHP_INT_MAX; foreach ($this->delays as $time) { if ($time < $nextTime) { $nextTime = $time; } } return max(0, $nextTime - $currentTime) * 1000000; } } withCookieHeader($request); return $handler($request, $options) ->then(function ($response) use ($cookieJar, $request) { $cookieJar->extractCookies($request, $response); return $response; } ); }; }; } public static function httpErrors() { return function (callable $handler) { return function ($request, array $options) use ($handler) { if (empty($options['http_errors'])) { return $handler($request, $options); } return $handler($request, $options)->then( function (ResponseInterface $response) use ($request, $handler) { $code = $response->getStatusCode(); if ($code < 400) { return $response; } throw RequestException::create($request, $response); } ); }; }; } public static function history(&$container) { if (!is_array($container) && !$container instanceof \ArrayAccess) { throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); } return function (callable $handler) use (&$container) { return function ($request, array $options) use ($handler, &$container) { return $handler($request, $options)->then( function ($value) use ($request, &$container, $options) { $container[] = [ 'request' => $request, 'response' => $value, 'error' => null, 'options' => $options ]; return $value; }, function ($reason) use ($request, &$container, $options) { $container[] = [ 'request' => $request, 'response' => null, 'error' => $reason, 'options' => $options ]; return new RejectedPromise($reason); } ); }; }; } public static function tap(callable $before = null, callable $after = null) { return function (callable $handler) use ($before, $after) { return function ($request, array $options) use ($handler, $before, $after) { if ($before) { $before($request, $options); } $response = $handler($request, $options); if ($after) { $after($request, $options, $response); } return $response; }; }; } public static function redirect() { return function (callable $handler) { return new RedirectMiddleware($handler); }; } public static function retry(callable $decider, callable $delay = null) { return function (callable $handler) use ($decider, $delay) { return new RetryMiddleware($decider, $handler, $delay); }; } public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) { return function (callable $handler) use ($logger, $formatter, $logLevel) { return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { return $handler($request, $options)->then( function ($response) use ($logger, $request, $formatter, $logLevel) { $message = $formatter->format($request, $response); $logger->log($logLevel, $message); return $response; }, function ($reason) use ($logger, $request, $formatter) { $response = $reason instanceof RequestException ? $reason->getResponse() : null; $message = $formatter->format($request, $response, $reason); $logger->notice($message); return \GuzzleHttp\Promise\rejection_for($reason); } ); }; }; } public static function prepareBody() { return function (callable $handler) { return new PrepareBodyMiddleware($handler); }; } public static function mapRequest(callable $fn) { return function (callable $handler) use ($fn) { return function ($request, array $options) use ($handler, $fn) { return $handler($fn($request), $options); }; }; } public static function mapResponse(callable $fn) { return function (callable $handler) use ($fn) { return function ($request, array $options) use ($handler, $fn) { return $handler($request, $options)->then($fn); }; }; } } decider = $decider; $this->nextHandler = $nextHandler; $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; } public static function exponentialDelay($retries) { return (int) pow(2, $retries - 1); } public function __invoke(RequestInterface $request, array $options) { if (!isset($options['retries'])) { $options['retries'] = 0; } $fn = $this->nextHandler; return $fn($request, $options) ->then( $this->onFulfilled($request, $options), $this->onRejected($request, $options) ); } private function onFulfilled(RequestInterface $req, array $options) { return function ($value) use ($req, $options) { if (!call_user_func( $this->decider, $options['retries'], $req, $value, null )) { return $value; } return $this->doRetry($req, $options, $value); }; } private function onRejected(RequestInterface $req, array $options) { return function ($reason) use ($req, $options) { if (!call_user_func( $this->decider, $options['retries'], $req, null, $reason )) { return new RejectedPromise($reason); } return $this->doRetry($req, $options); }; } private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) { $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); return $this($request, $options); } } push(Middleware::httpErrors(), 'http_errors'); $stack->push(Middleware::redirect(), 'allow_redirects'); $stack->push(Middleware::cookies(), 'cookies'); $stack->push(Middleware::prepareBody(), 'prepare_body'); return $stack; } public function __construct(callable $handler = null) { $this->handler = $handler; } public function __invoke(RequestInterface $request, array $options) { $handler = $this->resolve(); return $handler($request, $options); } public function __toString() { $depth = 0; $stack = []; if ($this->handler) { $stack[] = "0) Handler: " . $this->debugCallable($this->handler); } $result = ''; foreach (array_reverse($this->stack) as $tuple) { $depth++; $str = "{$depth}) Name: '{$tuple[1]}', "; $str .= "Function: " . $this->debugCallable($tuple[0]); $result = "> {$str}\n{$result}"; $stack[] = $str; } foreach (array_keys($stack) as $k) { $result .= "< {$stack[$k]}\n"; } return $result; } public function setHandler(callable $handler) { $this->handler = $handler; $this->cached = null; } public function hasHandler() { return (bool) $this->handler; } public function unshift(callable $middleware, $name = null) { array_unshift($this->stack, [$middleware, $name]); $this->cached = null; } public function push(callable $middleware, $name = '') { $this->stack[] = [$middleware, $name]; $this->cached = null; } public function before($findName, callable $middleware, $withName = '') { $this->splice($findName, $withName, $middleware, true); } public function after($findName, callable $middleware, $withName = '') { $this->splice($findName, $withName, $middleware, false); } public function remove($remove) { $this->cached = null; $idx = is_callable($remove) ? 0 : 1; $this->stack = array_values(array_filter( $this->stack, function ($tuple) use ($idx, $remove) { return $tuple[$idx] !== $remove; } )); } public function resolve() { if (!$this->cached) { if (!($prev = $this->handler)) { throw new \LogicException('No handler has been specified'); } foreach (array_reverse($this->stack) as $fn) { $prev = $fn[0]($prev); } $this->cached = $prev; } return $this->cached; } private function findByName($name) { foreach ($this->stack as $k => $v) { if ($v[1] === $name) { return $k; } } throw new \InvalidArgumentException("Middleware not found: $name"); } private function splice($findName, $withName, callable $middleware, $before) { $this->cached = null; $idx = $this->findByName($findName); $tuple = [$middleware, $withName]; if ($before) { if ($idx === 0) { array_unshift($this->stack, $tuple); } else { $replacement = [$tuple, $this->stack[$idx]]; array_splice($this->stack, $idx, 1, $replacement); } } elseif ($idx === count($this->stack) - 1) { $this->stack[] = $tuple; } else { $replacement = [$this->stack[$idx], $tuple]; array_splice($this->stack, $idx, 1, $replacement); } } private function debugCallable($fn) { if (is_string($fn)) { return "callable({$fn})"; } if (is_array($fn)) { return is_string($fn[0]) ? "callable({$fn[0]}::{$fn[1]})" : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; } return 'callable(' . spl_object_hash($fn) . ')'; } } getStatusCode() : 0; parent::__construct($message, $code, $previous); $this->request = $request; $this->response = $response; $this->handlerContext = $handlerContext; } public static function wrapException(RequestInterface $request, \Exception $e) { return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); } public static function create( RequestInterface $request, ResponseInterface $response = null, \Exception $previous = null, array $ctx = [] ) { if (!$response) { return new self( 'Error completing request', $request, null, $previous, $ctx ); } $level = (int) floor($response->getStatusCode() / 100); if ($level === 4) { $label = 'Client error'; $className = __NAMESPACE__ . '\\ClientException'; } elseif ($level === 5) { $label = 'Server error'; $className = __NAMESPACE__ . '\\ServerException'; } else { $label = 'Unsuccessful request'; $className = __CLASS__; } $uri = $request->getUri(); $uri = static::obfuscateUri($uri); $message = sprintf( '%s: `%s` resulted in a `%s` response', $label, $request->getMethod() . ' ' . $uri, $response->getStatusCode() . ' ' . $response->getReasonPhrase() ); $summary = static::getResponseBodySummary($response); if ($summary !== null) { $message .= ":\n{$summary}\n"; } return new $className($message, $request, $response, $previous, $ctx); } public static function getResponseBodySummary(ResponseInterface $response) { $body = $response->getBody(); if (!$body->isSeekable()) { return null; } $size = $body->getSize(); $summary = $body->read(120); $body->rewind(); if ($size > 120) { $summary .= ' (truncated...)'; } if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) { return null; } return $summary; } private static function obfuscateUri($uri) { $userInfo = $uri->getUserInfo(); if (false !== ($pos = strpos($userInfo, ':'))) { return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); } return $uri; } public function getRequest() { return $this->request; } public function getResponse() { return $this->response; } public function hasResponse() { return $this->response !== null; } public function getHandlerContext() { return $this->handlerContext; } } stream = $stream; $msg = $msg ?: 'Could not seek the stream to position ' . $pos; parent::__construct($msg); } public function getStream() { return $this->stream; } } expand($template, $variables); } function describe_type($input) { switch (gettype($input)) { case 'object': return 'object(' . get_class($input) . ')'; case 'array': return 'array(' . count($input) . ')'; default: ob_start(); var_dump($input); return str_replace('double(', 'float(', rtrim(ob_get_clean())); } } function headers_from_lines($lines) { $headers = []; foreach ($lines as $line) { $parts = explode(':', $line, 2); $headers[trim($parts[0])][] = isset($parts[1]) ? trim($parts[1]) : null; } return $headers; } function debug_resource($value = null) { if (is_resource($value)) { return $value; } elseif (defined('STDOUT')) { return STDOUT; } return fopen('php://output', 'w'); } function choose_handler() { $handler = null; if (function_exists('curl_multi_exec') && function_exists('curl_exec')) { $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); } elseif (function_exists('curl_exec')) { $handler = new CurlHandler(); } elseif (function_exists('curl_multi_exec')) { $handler = new CurlMultiHandler(); } if (ini_get('allow_url_fopen')) { $handler = $handler ? Proxy::wrapStreaming($handler, new StreamHandler()) : new StreamHandler(); } elseif (!$handler) { throw new \RuntimeException('GuzzleHttp requires cURL, the ' . 'allow_url_fopen ini setting, or a custom HTTP handler.'); } return $handler; } function default_user_agent() { static $defaultAgent = ''; if (!$defaultAgent) { $defaultAgent = 'GuzzleHttp/' . Client::VERSION; if (extension_loaded('curl') && function_exists('curl_version')) { $defaultAgent .= ' curl/' . \curl_version()['version']; } $defaultAgent .= ' PHP/' . PHP_VERSION; } return $defaultAgent; } function default_ca_bundle() { static $cached = null; static $cafiles = [ '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/certs/ca-certificates.crt', '/usr/local/share/certs/ca-root-nss.crt', '/usr/local/etc/openssl/cert.pem', '/etc/ca-certificates.crt', 'C:\\windows\\system32\\curl-ca-bundle.crt', 'C:\\windows\\curl-ca-bundle.crt', ]; if ($cached) { return $cached; } if ($ca = ini_get('openssl.cafile')) { return $cached = $ca; } if ($ca = ini_get('curl.cainfo')) { return $cached = $ca; } foreach ($cafiles as $filename) { if (file_exists($filename)) { return $cached = $filename; } } throw new \RuntimeException(<<< EOT No system CA bundle could be found in any of the the common system locations. PHP versions earlier than 5.6 are not properly configured to use the system's CA bundle by default. In order to verify peer certificates, you will need to supply the path on disk to a certificate bundle to the 'verify' request option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not need a specific certificate bundle, then Mozilla provides a commonly used CA bundle which can be downloaded here (provided by the maintainer of cURL): https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP ini setting to point to the path to the file, allowing you to omit the 'verify' request option. See http://curl.haxx.se/docs/sslcerts.html for more information. EOT ); } function normalize_header_keys(array $headers) { $result = []; foreach (array_keys($headers) as $key) { $result[strtolower($key)] = $key; } return $result; } function is_host_in_noproxy($host, array $noProxyArray) { if (strlen($host) === 0) { throw new \InvalidArgumentException('Empty host provided'); } if (strpos($host, ':')) { $host = explode($host, ':', 2)[0]; } foreach ($noProxyArray as $area) { if ($area === '*') { return true; } elseif (empty($area)) { continue; } elseif ($area === $host) { return true; } else { $area = '.' . ltrim($area, '.'); if (substr($host, -(strlen($area))) === $area) { return true; } } } return false; } function json_decode($json, $assoc = false, $depth = 512, $options = 0) { $data = \json_decode($json, $assoc, $depth, $options); if (JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException( 'json_decode error: ' . json_last_error_msg()); } return $data; } function json_encode($value, $options = 0, $depth = 512) { $json = \json_encode($value, $options, $depth); if (JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException( 'json_encode error: ' . json_last_error_msg()); } return $json; } strictMode = $strictMode; foreach ($cookieArray as $cookie) { if (!($cookie instanceof SetCookie)) { $cookie = new SetCookie($cookie); } $this->setCookie($cookie); } } public static function fromArray(array $cookies, $domain) { $cookieJar = new self(); foreach ($cookies as $name => $value) { $cookieJar->setCookie(new SetCookie([ 'Domain' => $domain, 'Name' => $name, 'Value' => $value, 'Discard' => true ])); } return $cookieJar; } public static function getCookieValue($value) { return $value; } public static function shouldPersist( SetCookie $cookie, $allowSessionCookies = false ) { if ($cookie->getExpires() || $allowSessionCookies) { if (!$cookie->getDiscard()) { return true; } } return false; } public function toArray() { return array_map(function (SetCookie $cookie) { return $cookie->toArray(); }, $this->getIterator()->getArrayCopy()); } public function clear($domain = null, $path = null, $name = null) { if (!$domain) { $this->cookies = []; return; } elseif (!$path) { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain) { return !$cookie->matchesDomain($domain); } ); } elseif (!$name) { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain) { return !($cookie->matchesPath($path) && $cookie->matchesDomain($domain)); } ); } else { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain, $name) { return !($cookie->getName() == $name && $cookie->matchesPath($path) && $cookie->matchesDomain($domain)); } ); } } public function clearSessionCookies() { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) { return !$cookie->getDiscard() && $cookie->getExpires(); } ); } public function setCookie(SetCookie $cookie) { $name = $cookie->getName(); if (!$name && $name !== '0') { return false; } $result = $cookie->validate(); if ($result !== true) { if ($this->strictMode) { throw new \RuntimeException('Invalid cookie: ' . $result); } else { $this->removeCookieIfEmpty($cookie); return false; } } foreach ($this->cookies as $i => $c) { if ($c->getPath() != $cookie->getPath() || $c->getDomain() != $cookie->getDomain() || $c->getName() != $cookie->getName() ) { continue; } if (!$cookie->getDiscard() && $c->getDiscard()) { unset($this->cookies[$i]); continue; } if ($cookie->getExpires() > $c->getExpires()) { unset($this->cookies[$i]); continue; } if ($cookie->getValue() !== $c->getValue()) { unset($this->cookies[$i]); continue; } return false; } $this->cookies[] = $cookie; return true; } public function count() { return count($this->cookies); } public function getIterator() { return new \ArrayIterator(array_values($this->cookies)); } public function extractCookies( RequestInterface $request, ResponseInterface $response ) { if ($cookieHeader = $response->getHeader('Set-Cookie')) { foreach ($cookieHeader as $cookie) { $sc = SetCookie::fromString($cookie); if (!$sc->getDomain()) { $sc->setDomain($request->getUri()->getHost()); } $this->setCookie($sc); } } } public function withCookieHeader(RequestInterface $request) { $values = []; $uri = $request->getUri(); $scheme = $uri->getScheme(); $host = $uri->getHost(); $path = $uri->getPath() ?: '/'; foreach ($this->cookies as $cookie) { if ($cookie->matchesPath($path) && $cookie->matchesDomain($host) && !$cookie->isExpired() && (!$cookie->getSecure() || $scheme === 'https') ) { $values[] = $cookie->getName() . '=' . $cookie->getValue(); } } return $values ? $request->withHeader('Cookie', implode('; ', $values)) : $request; } private function removeCookieIfEmpty(SetCookie $cookie) { $cookieValue = $cookie->getValue(); if ($cookieValue === null || $cookieValue === '') { $this->clear( $cookie->getDomain(), $cookie->getPath(), $cookie->getName() ); } } } null, 'Value' => null, 'Domain' => null, 'Path' => '/', 'Max-Age' => null, 'Expires' => null, 'Secure' => false, 'Discard' => false, 'HttpOnly' => false ]; private $data; public static function fromString($cookie) { $data = self::$defaults; $pieces = array_filter(array_map('trim', explode(';', $cookie))); if (empty($pieces) || !strpos($pieces[0], '=')) { return new self($data); } foreach ($pieces as $part) { $cookieParts = explode('=', $part, 2); $key = trim($cookieParts[0]); $value = isset($cookieParts[1]) ? trim($cookieParts[1], " \n\r\t\0\x0B") : true; if (empty($data['Name'])) { $data['Name'] = $key; $data['Value'] = $value; } else { foreach (array_keys(self::$defaults) as $search) { if (!strcasecmp($search, $key)) { $data[$search] = $value; continue 2; } } $data[$key] = $value; } } return new self($data); } public function __construct(array $data = []) { $this->data = array_replace(self::$defaults, $data); if (!$this->getExpires() && $this->getMaxAge()) { $this->setExpires(time() + $this->getMaxAge()); } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { $this->setExpires($this->getExpires()); } } public function __toString() { $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; foreach ($this->data as $k => $v) { if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { if ($k === 'Expires') { $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; } else { $str .= ($v === true ? $k : "{$k}={$v}") . '; '; } } } return rtrim($str, '; '); } public function toArray() { return $this->data; } public function getName() { return $this->data['Name']; } public function setName($name) { $this->data['Name'] = $name; } public function getValue() { return $this->data['Value']; } public function setValue($value) { $this->data['Value'] = $value; } public function getDomain() { return $this->data['Domain']; } public function setDomain($domain) { $this->data['Domain'] = $domain; } public function getPath() { return $this->data['Path']; } public function setPath($path) { $this->data['Path'] = $path; } public function getMaxAge() { return $this->data['Max-Age']; } public function setMaxAge($maxAge) { $this->data['Max-Age'] = $maxAge; } public function getExpires() { return $this->data['Expires']; } public function setExpires($timestamp) { $this->data['Expires'] = is_numeric($timestamp) ? (int) $timestamp : strtotime($timestamp); } public function getSecure() { return $this->data['Secure']; } public function setSecure($secure) { $this->data['Secure'] = $secure; } public function getDiscard() { return $this->data['Discard']; } public function setDiscard($discard) { $this->data['Discard'] = $discard; } public function getHttpOnly() { return $this->data['HttpOnly']; } public function setHttpOnly($httpOnly) { $this->data['HttpOnly'] = $httpOnly; } public function matchesPath($requestPath) { $cookiePath = $this->getPath(); if ($cookiePath === '/' || $cookiePath == $requestPath) { return true; } if (0 !== strpos($requestPath, $cookiePath)) { return false; } if (substr($cookiePath, -1, 1) === '/') { return true; } return substr($requestPath, strlen($cookiePath), 1) === '/'; } public function matchesDomain($domain) { $cookieDomain = ltrim($this->getDomain(), '.'); if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { return true; } if (filter_var($domain, FILTER_VALIDATE_IP)) { return false; } return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain); } public function isExpired() { return $this->getExpires() && time() > $this->getExpires(); } public function validate() { $name = $this->getName(); if (empty($name) && !is_numeric($name)) { return 'The cookie name must not be empty'; } if (preg_match( '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', $name) ) { return 'Cookie name must not contain invalid characters: ASCII ' . 'Control characters (0-31;127), space, tab and the ' . 'following characters: ()<>@,;:\"/?={}'; } $value = $this->getValue(); if (empty($value) && !is_numeric($value)) { return 'The cookie value must not be empty'; } $domain = $this->getDomain(); if (empty($domain) && !is_numeric($domain)) { return 'The cookie domain must not be empty'; } return true; } } filename = $cookieFile; $this->storeSessionCookies = $storeSessionCookies; if (file_exists($cookieFile)) { $this->load($cookieFile); } } public function __destruct() { $this->save($this->filename); } public function save($filename) { $json = []; foreach ($this as $cookie) { if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { $json[] = $cookie->toArray(); } } $jsonStr = \GuzzleHttp\json_encode($json); if (false === file_put_contents($filename, $jsonStr)) { throw new \RuntimeException("Unable to save file {$filename}"); } } public function load($filename) { $json = file_get_contents($filename); if (false === $json) { throw new \RuntimeException("Unable to load file {$filename}"); } elseif ($json === '') { return; } $data = \GuzzleHttp\json_decode($json, true); if (is_array($data)) { foreach (json_decode($json, true) as $cookie) { $this->setCookie(new SetCookie($cookie)); } } elseif (strlen($data)) { throw new \RuntimeException("Invalid cookie file: {$filename}"); } } } sessionKey = $sessionKey; $this->storeSessionCookies = $storeSessionCookies; $this->load(); } public function __destruct() { $this->save(); } public function save() { $json = []; foreach ($this as $cookie) { if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { $json[] = $cookie->toArray(); } } $_SESSION[$this->sessionKey] = json_encode($json); } protected function load() { if (!isset($_SESSION[$this->sessionKey])) { return; } $data = json_decode($_SESSION[$this->sessionKey], true); if (is_array($data)) { foreach ($data as $cookie) { $this->setCookie(new SetCookie($cookie)); } } elseif (strlen($data)) { throw new \RuntimeException("Invalid cookie data"); } } } request = $request; $this->response = $response; $this->transferTime = $transferTime; $this->handlerErrorData = $handlerErrorData; $this->handlerStats = $handlerStats; } public function getRequest() { return $this->request; } public function getResponse() { return $this->response; } public function hasResponse() { return $this->response !== null; } public function getHandlerErrorData() { return $this->handlerErrorData; } public function getEffectiveUri() { return $this->request->getUri(); } public function getTransferTime() { return $this->transferTime; } public function getHandlerStats() { return $this->handlerStats; } public function getHandlerStat($stat) { return isset($this->handlerStats[$stat]) ? $this->handlerStats[$stat] : null; } } >>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; private $template; public function __construct($template = self::CLF) { $this->template = $template ?: self::CLF; } public function format( RequestInterface $request, ResponseInterface $response = null, \Exception $error = null ) { $cache = []; return preg_replace_callback( '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', function (array $matches) use ($request, $response, $error, &$cache) { if (isset($cache[$matches[1]])) { return $cache[$matches[1]]; } $result = ''; switch ($matches[1]) { case 'request': $result = Psr7\str($request); break; case 'response': $result = $response ? Psr7\str($response) : ''; break; case 'req_headers': $result = trim($request->getMethod() . ' ' . $request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . "\r\n" . $this->headers($request); break; case 'res_headers': $result = $response ? sprintf( 'HTTP/%s %d %s', $response->getProtocolVersion(), $response->getStatusCode(), $response->getReasonPhrase() ) . "\r\n" . $this->headers($response) : 'NULL'; break; case 'req_body': $result = $request->getBody(); break; case 'res_body': $result = $response ? $response->getBody() : 'NULL'; break; case 'ts': case 'date_iso_8601': $result = gmdate('c'); break; case 'date_common_log': $result = date('d/M/Y:H:i:s O'); break; case 'method': $result = $request->getMethod(); break; case 'version': $result = $request->getProtocolVersion(); break; case 'uri': case 'url': $result = $request->getUri(); break; case 'target': $result = $request->getRequestTarget(); break; case 'req_version': $result = $request->getProtocolVersion(); break; case 'res_version': $result = $response ? $response->getProtocolVersion() : 'NULL'; break; case 'host': $result = $request->getHeaderLine('Host'); break; case 'hostname': $result = gethostname(); break; case 'code': $result = $response ? $response->getStatusCode() : 'NULL'; break; case 'phrase': $result = $response ? $response->getReasonPhrase() : 'NULL'; break; case 'error': $result = $error ? $error->getMessage() : 'NULL'; break; default: if (strpos($matches[1], 'req_header_') === 0) { $result = $request->getHeaderLine(substr($matches[1], 11)); } elseif (strpos($matches[1], 'res_header_') === 0) { $result = $response ? $response->getHeaderLine(substr($matches[1], 11)) : 'NULL'; } } $cache[$matches[1]] = $result; return $result; }, $this->template ); } private function headers(MessageInterface $message) { $result = ''; foreach ($message->getHeaders() as $name => $values) { $result .= $name . ': ' . implode(', ', $values) . "\r\n"; } return trim($result); } } ['prefix' => '', 'joiner' => ',', 'query' => false], '+' => ['prefix' => '', 'joiner' => ',', 'query' => false], '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false], '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false], '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false], ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true], '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true], '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true] ]; private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=']; private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D']; public function expand($template, array $variables) { if (false === strpos($template, '{')) { return $template; } $this->template = $template; $this->variables = $variables; return preg_replace_callback( '/\{([^\}]+)\}/', [$this, 'expandMatch'], $this->template ); } private function parseExpression($expression) { $result = []; if (isset(self::$operatorHash[$expression[0]])) { $result['operator'] = $expression[0]; $expression = substr($expression, 1); } else { $result['operator'] = ''; } foreach (explode(',', $expression) as $value) { $value = trim($value); $varspec = []; if ($colonPos = strpos($value, ':')) { $varspec['value'] = substr($value, 0, $colonPos); $varspec['modifier'] = ':'; $varspec['position'] = (int) substr($value, $colonPos + 1); } elseif (substr($value, -1) === '*') { $varspec['modifier'] = '*'; $varspec['value'] = substr($value, 0, -1); } else { $varspec['value'] = (string) $value; $varspec['modifier'] = ''; } $result['values'][] = $varspec; } return $result; } private function expandMatch(array $matches) { static $rfc1738to3986 = ['+' => '%20', '%7e' => '~']; $replacements = []; $parsed = self::parseExpression($matches[1]); $prefix = self::$operatorHash[$parsed['operator']]['prefix']; $joiner = self::$operatorHash[$parsed['operator']]['joiner']; $useQuery = self::$operatorHash[$parsed['operator']]['query']; foreach ($parsed['values'] as $value) { if (!isset($this->variables[$value['value']])) { continue; } $variable = $this->variables[$value['value']]; $actuallyUseQuery = $useQuery; $expanded = ''; if (is_array($variable)) { $isAssoc = $this->isAssoc($variable); $kvp = []; foreach ($variable as $key => $var) { if ($isAssoc) { $key = rawurlencode($key); $isNestedArray = is_array($var); } else { $isNestedArray = false; } if (!$isNestedArray) { $var = rawurlencode($var); if ($parsed['operator'] === '+' || $parsed['operator'] === '#' ) { $var = $this->decodeReserved($var); } } if ($value['modifier'] === '*') { if ($isAssoc) { if ($isNestedArray) { $var = strtr( http_build_query([$key => $var]), $rfc1738to3986 ); } else { $var = $key . '=' . $var; } } elseif ($key > 0 && $actuallyUseQuery) { $var = $value['value'] . '=' . $var; } } $kvp[$key] = $var; } if (empty($variable)) { $actuallyUseQuery = false; } elseif ($value['modifier'] === '*') { $expanded = implode($joiner, $kvp); if ($isAssoc) { $actuallyUseQuery = false; } } else { if ($isAssoc) { foreach ($kvp as $k => &$v) { $v = $k . ',' . $v; } } $expanded = implode(',', $kvp); } } else { if ($value['modifier'] === ':') { $variable = substr($variable, 0, $value['position']); } $expanded = rawurlencode($variable); if ($parsed['operator'] === '+' || $parsed['operator'] === '#') { $expanded = $this->decodeReserved($expanded); } } if ($actuallyUseQuery) { if (!$expanded && $joiner !== '&') { $expanded = $value['value']; } else { $expanded = $value['value'] . '=' . $expanded; } } $replacements[] = $expanded; } $ret = implode($joiner, $replacements); if ($ret && $prefix) { return $prefix . $ret; } return $ret; } private function isAssoc(array $array) { return $array && array_keys($array)[0] !== 0; } private function decodeReserved($string) { return str_replace(self::$delimsPct, self::$delims, $string); } } connection = $connection; } public function getConnection() { return $this->connection; } public function shouldResetConnection() { return true; } public static function handle(CommunicationException $exception) { if ($exception->shouldResetConnection()) { $connection = $exception->getConnection(); if ($connection->isConnected()) { $connection->disconnect(); } } throw $exception; } } callbacks = array(); $this->pubsub = $pubsub; } protected function assertCallback($callable) { if (!is_callable($callable)) { throw new \InvalidArgumentException('The given argument must be a callable object.'); } } public function getPubSubConsumer() { return $this->pubsub; } public function subscriptionCallback($callable = null) { if (isset($callable)) { $this->assertCallback($callable); } $this->subscriptionCallback = $callable; } public function defaultCallback($callable = null) { if (isset($callable)) { $this->assertCallback($callable); } $this->subscriptionCallback = $callable; } public function attachCallback($channel, $callback) { $callbackName = $this->getPrefixKeys().$channel; $this->assertCallback($callback); $this->callbacks[$callbackName] = $callback; $this->pubsub->subscribe($channel); } public function detachCallback($channel) { $callbackName = $this->getPrefixKeys().$channel; if (isset($this->callbacks[$callbackName])) { unset($this->callbacks[$callbackName]); $this->pubsub->unsubscribe($channel); } } public function run() { foreach ($this->pubsub as $message) { $kind = $message->kind; if ($kind !== Consumer::MESSAGE && $kind !== Consumer::PMESSAGE) { if (isset($this->subscriptionCallback)) { $callback = $this->subscriptionCallback; call_user_func($callback, $message); } continue; } if (isset($this->callbacks[$message->channel])) { $callback = $this->callbacks[$message->channel]; call_user_func($callback, $message->payload); } elseif (isset($this->defaultCallback)) { $callback = $this->defaultCallback; call_user_func($callback, $message); } } } public function stop() { $this->pubsub->stop(); } protected function getPrefixKeys() { $options = $this->pubsub->getClient()->getOptions(); if (isset($options->prefix)) { return $options->prefix->getPrefix(); } return ''; } } checkCapabilities($client); $this->options = $options ?: array(); $this->client = $client; $this->genericSubscribeInit('subscribe'); $this->genericSubscribeInit('psubscribe'); } public function getClient() { return $this->client; } private function checkCapabilities(ClientInterface $client) { if ($client->getConnection() instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Cannot initialize a PUB/SUB consumer over aggregate connections.' ); } $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe'); if ($client->getProfile()->supportsCommands($commands) === false) { throw new NotSupportedException( 'The current profile does not support PUB/SUB related commands.' ); } } private function genericSubscribeInit($subscribeAction) { if (isset($this->options[$subscribeAction])) { $this->$subscribeAction($this->options[$subscribeAction]); } } protected function writeRequest($method, $arguments) { $this->client->getConnection()->writeRequest( $this->client->createCommand($method, Command::normalizeArguments($arguments) ) ); } protected function disconnect() { $this->client->disconnect(); } protected function getValue() { $response = $this->client->getConnection()->read(); switch ($response[0]) { case self::SUBSCRIBE: case self::UNSUBSCRIBE: case self::PSUBSCRIBE: case self::PUNSUBSCRIBE: if ($response[2] === 0) { $this->invalidate(); } case self::MESSAGE: return (object) array( 'kind' => $response[0], 'channel' => $response[1], 'payload' => $response[2], ); case self::PMESSAGE: return (object) array( 'kind' => $response[0], 'pattern' => $response[1], 'channel' => $response[2], 'payload' => $response[3], ); case self::PONG: return (object) array( 'kind' => $response[0], 'payload' => $response[1], ); default: throw new ClientException( "Unknown message type '{$response[0]}' received in the PUB/SUB context." ); } } } stop(true); } protected function isFlagSet($value) { return ($this->statusFlags & $value) === $value; } public function subscribe($channel ) { $this->writeRequest(self::SUBSCRIBE, func_get_args()); $this->statusFlags |= self::STATUS_SUBSCRIBED; } public function unsubscribe() { $this->writeRequest(self::UNSUBSCRIBE, func_get_args()); } public function psubscribe($pattern ) { $this->writeRequest(self::PSUBSCRIBE, func_get_args()); $this->statusFlags |= self::STATUS_PSUBSCRIBED; } public function punsubscribe() { $this->writeRequest(self::PUNSUBSCRIBE, func_get_args()); } public function ping($payload = null) { $this->writeRequest('PING', array($payload)); } public function stop($drop = false) { if (!$this->valid()) { return false; } if ($drop) { $this->invalidate(); $this->disconnect(); } else { if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) { $this->unsubscribe(); } if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) { $this->punsubscribe(); } } return !$drop; } abstract protected function disconnect(); abstract protected function writeRequest($method, $arguments); public function rewind() { } public function current() { return $this->getValue(); } public function key() { return $this->position; } public function next() { if ($this->valid()) { ++$this->position; } return $this->position; } public function valid() { $isValid = $this->isFlagSet(self::STATUS_VALID); $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED; $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0; return $isValid && $hasSubscriptions; } protected function invalidate() { $this->statusFlags = 0; } abstract protected function getValue(); } requiredCommand($client, 'LRANGE'); if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) { throw new \InvalidArgumentException('The $count argument must be a positive integer.'); } $this->client = $client; $this->key = $key; $this->count = $count; $this->reset(); } protected function requiredCommand(ClientInterface $client, $commandID) { if (!$client->getProfile()->supportsCommand($commandID)) { throw new NotSupportedException("The current profile does not support '$commandID'."); } } protected function reset() { $this->valid = true; $this->fetchmore = true; $this->elements = array(); $this->position = -1; $this->current = null; } protected function executeCommand() { return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count); } protected function fetch() { $elements = $this->executeCommand(); if (count($elements) < $this->count) { $this->fetchmore = false; } $this->elements = $elements; } protected function extractNext() { ++$this->position; $this->current = array_shift($this->elements); } public function rewind() { $this->reset(); $this->next(); } public function current() { return $this->current; } public function key() { return $this->position; } public function next() { if (!$this->elements && $this->fetchmore) { $this->fetch(); } if ($this->elements) { $this->extractNext(); } else { $this->valid = false; } } public function valid() { return $this->valid; } } requiredCommand($client, 'ZSCAN'); parent::__construct($client, $match, $count); $this->key = $key; } protected function executeCommand() { return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions()); } protected function extractNext() { if ($kv = each($this->elements)) { $this->position = $kv[0]; $this->current = $kv[1]; unset($this->elements[$this->position]); } } } requiredCommand($client, 'SCAN'); parent::__construct($client, $match, $count); } protected function executeCommand() { return $this->client->scan($this->cursor, $this->getScanOptions()); } } requiredCommand($client, 'SSCAN'); parent::__construct($client, $match, $count); $this->key = $key; } protected function executeCommand() { return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions()); } } client = $client; $this->match = $match; $this->count = $count; $this->reset(); } protected function requiredCommand(ClientInterface $client, $commandID) { if (!$client->getProfile()->supportsCommand($commandID)) { throw new NotSupportedException("The current profile does not support '$commandID'."); } } protected function reset() { $this->valid = true; $this->fetchmore = true; $this->elements = array(); $this->cursor = 0; $this->position = -1; $this->current = null; } protected function getScanOptions() { $options = array(); if (strlen($this->match) > 0) { $options['MATCH'] = $this->match; } if ($this->count > 0) { $options['COUNT'] = $this->count; } return $options; } abstract protected function executeCommand(); protected function fetch() { list($cursor, $elements) = $this->executeCommand(); if (!$cursor) { $this->fetchmore = false; } $this->cursor = $cursor; $this->elements = $elements; } protected function extractNext() { ++$this->position; $this->current = array_shift($this->elements); } public function rewind() { $this->reset(); $this->next(); } public function current() { return $this->current; } public function key() { return $this->position; } public function next() { tryFetch: { if (!$this->elements && $this->fetchmore) { $this->fetch(); } if ($this->elements) { $this->extractNext(); } elseif ($this->cursor) { goto tryFetch; } else { $this->valid = false; } } } public function valid() { return $this->valid; } } requiredCommand($client, 'HSCAN'); parent::__construct($client, $match, $count); $this->key = $key; } protected function executeCommand() { return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions()); } protected function extractNext() { if ($kv = each($this->elements)) { $this->position = $kv[0]; $this->current = $kv[1]; unset($this->elements[$this->position]); } } } hashGenerator = $hashGenerator ?: new CRC16(); } public function getSlotByKey($key) { $key = $this->extractKeyTag($key); $slot = $this->hashGenerator->hash($key) & 0x3FFF; return $slot; } public function getDistributor() { throw new NotSupportedException( 'This cluster strategy does not provide an external distributor' ); } } replicas = $replicas; $this->nodeHashCallback = $nodeHashCallback; } public function add($node, $weight = null) { $this->nodes[] = array( 'object' => $node, 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT, ); $this->reset(); } public function remove($node) { for ($i = 0; $i < count($this->nodes); ++$i) { if ($this->nodes[$i]['object'] === $node) { array_splice($this->nodes, $i, 1); $this->reset(); break; } } } private function reset() { unset( $this->ring, $this->ringKeys, $this->ringKeysCount ); } private function isInitialized() { return isset($this->ringKeys); } private function computeTotalWeight() { $totalWeight = 0; foreach ($this->nodes as $node) { $totalWeight += $node['weight']; } return $totalWeight; } private function initialize() { if ($this->isInitialized()) { return; } if (!$this->nodes) { throw new EmptyRingException('Cannot initialize an empty hashring.'); } $this->ring = array(); $totalWeight = $this->computeTotalWeight(); $nodesCount = count($this->nodes); foreach ($this->nodes as $node) { $weightRatio = $node['weight'] / $totalWeight; $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio); } ksort($this->ring, SORT_NUMERIC); $this->ringKeys = array_keys($this->ring); $this->ringKeysCount = count($this->ringKeys); } protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) { $nodeObject = $node['object']; $nodeHash = $this->getNodeHash($nodeObject); $replicas = (int) round($weightRatio * $totalNodes * $replicas); for ($i = 0; $i < $replicas; ++$i) { $key = crc32("$nodeHash:$i"); $ring[$key] = $nodeObject; } } protected function getNodeHash($nodeObject) { if (!isset($this->nodeHashCallback)) { return (string) $nodeObject; } return call_user_func($this->nodeHashCallback, $nodeObject); } public function hash($value) { return crc32($value); } public function getByHash($hash) { return $this->ring[$this->getSlot($hash)]; } public function getBySlot($slot) { $this->initialize(); if (isset($this->ring[$slot])) { return $this->ring[$slot]; } } public function getSlot($hash) { $this->initialize(); $ringKeys = $this->ringKeys; $upper = $this->ringKeysCount - 1; $lower = 0; while ($lower <= $upper) { $index = ($lower + $upper) >> 1; $item = $ringKeys[$index]; if ($item > $hash) { $upper = $index - 1; } elseif ($item < $hash) { $lower = $index + 1; } else { return $item; } } return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)]; } public function get($value) { $hash = $this->hash($value); $node = $this->getByHash($hash); return $node; } protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) { return $upper >= 0 ? $upper : $ringKeysCount - 1; } public function getHashGenerator() { return $this; } } getNodeHash($nodeObject); $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4)); for ($i = 0; $i < $replicas; ++$i) { $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true)); foreach ($unpackedDigest as $key) { $ring[$key] = $nodeObject; } } } public function hash($value) { $hash = unpack('V', md5($value, true)); return $hash[1]; } protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) { return $lower < $ringKeysCount ? $lower : 0; } } > 8) ^ ord($value[$i])]) & 0xFFFF; } return $crc; } } distributor = $distributor ?: new HashRing(); } public function getSlotByKey($key) { $key = $this->extractKeyTag($key); $hash = $this->distributor->hash($key); $slot = $this->distributor->getSlot($hash); return $slot; } protected function checkSameSlotForKeys(array $keys) { if (!$count = count($keys)) { return false; } $currentKey = $this->extractKeyTag($keys[0]); for ($i = 1; $i < $count; ++$i) { $nextKey = $this->extractKeyTag($keys[$i]); if ($currentKey !== $nextKey) { return false; } $currentKey = $nextKey; } return true; } public function getDistributor() { return $this->distributor; } } commands = $this->getDefaultCommands(); } protected function getDefaultCommands() { $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument'); $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments'); return array( 'EXISTS' => $getKeyFromAllArguments, 'DEL' => $getKeyFromAllArguments, 'TYPE' => $getKeyFromFirstArgument, 'EXPIRE' => $getKeyFromFirstArgument, 'EXPIREAT' => $getKeyFromFirstArgument, 'PERSIST' => $getKeyFromFirstArgument, 'PEXPIRE' => $getKeyFromFirstArgument, 'PEXPIREAT' => $getKeyFromFirstArgument, 'TTL' => $getKeyFromFirstArgument, 'PTTL' => $getKeyFromFirstArgument, 'SORT' => array($this, 'getKeyFromSortCommand'), 'DUMP' => $getKeyFromFirstArgument, 'RESTORE' => $getKeyFromFirstArgument, 'APPEND' => $getKeyFromFirstArgument, 'DECR' => $getKeyFromFirstArgument, 'DECRBY' => $getKeyFromFirstArgument, 'GET' => $getKeyFromFirstArgument, 'GETBIT' => $getKeyFromFirstArgument, 'MGET' => $getKeyFromAllArguments, 'SET' => $getKeyFromFirstArgument, 'GETRANGE' => $getKeyFromFirstArgument, 'GETSET' => $getKeyFromFirstArgument, 'INCR' => $getKeyFromFirstArgument, 'INCRBY' => $getKeyFromFirstArgument, 'INCRBYFLOAT' => $getKeyFromFirstArgument, 'SETBIT' => $getKeyFromFirstArgument, 'SETEX' => $getKeyFromFirstArgument, 'MSET' => array($this, 'getKeyFromInterleavedArguments'), 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'), 'SETNX' => $getKeyFromFirstArgument, 'SETRANGE' => $getKeyFromFirstArgument, 'STRLEN' => $getKeyFromFirstArgument, 'SUBSTR' => $getKeyFromFirstArgument, 'BITOP' => array($this, 'getKeyFromBitOp'), 'BITCOUNT' => $getKeyFromFirstArgument, 'BITFIELD' => $getKeyFromFirstArgument, 'LINSERT' => $getKeyFromFirstArgument, 'LINDEX' => $getKeyFromFirstArgument, 'LLEN' => $getKeyFromFirstArgument, 'LPOP' => $getKeyFromFirstArgument, 'RPOP' => $getKeyFromFirstArgument, 'RPOPLPUSH' => $getKeyFromAllArguments, 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'), 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'), 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'), 'LPUSH' => $getKeyFromFirstArgument, 'LPUSHX' => $getKeyFromFirstArgument, 'RPUSH' => $getKeyFromFirstArgument, 'RPUSHX' => $getKeyFromFirstArgument, 'LRANGE' => $getKeyFromFirstArgument, 'LREM' => $getKeyFromFirstArgument, 'LSET' => $getKeyFromFirstArgument, 'LTRIM' => $getKeyFromFirstArgument, 'SADD' => $getKeyFromFirstArgument, 'SCARD' => $getKeyFromFirstArgument, 'SDIFF' => $getKeyFromAllArguments, 'SDIFFSTORE' => $getKeyFromAllArguments, 'SINTER' => $getKeyFromAllArguments, 'SINTERSTORE' => $getKeyFromAllArguments, 'SUNION' => $getKeyFromAllArguments, 'SUNIONSTORE' => $getKeyFromAllArguments, 'SISMEMBER' => $getKeyFromFirstArgument, 'SMEMBERS' => $getKeyFromFirstArgument, 'SSCAN' => $getKeyFromFirstArgument, 'SPOP' => $getKeyFromFirstArgument, 'SRANDMEMBER' => $getKeyFromFirstArgument, 'SREM' => $getKeyFromFirstArgument, 'ZADD' => $getKeyFromFirstArgument, 'ZCARD' => $getKeyFromFirstArgument, 'ZCOUNT' => $getKeyFromFirstArgument, 'ZINCRBY' => $getKeyFromFirstArgument, 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), 'ZRANGE' => $getKeyFromFirstArgument, 'ZRANGEBYSCORE' => $getKeyFromFirstArgument, 'ZRANK' => $getKeyFromFirstArgument, 'ZREM' => $getKeyFromFirstArgument, 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument, 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument, 'ZREVRANGE' => $getKeyFromFirstArgument, 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument, 'ZREVRANK' => $getKeyFromFirstArgument, 'ZSCORE' => $getKeyFromFirstArgument, 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), 'ZSCAN' => $getKeyFromFirstArgument, 'ZLEXCOUNT' => $getKeyFromFirstArgument, 'ZRANGEBYLEX' => $getKeyFromFirstArgument, 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument, 'ZREVRANGEBYLEX' => $getKeyFromFirstArgument, 'HDEL' => $getKeyFromFirstArgument, 'HEXISTS' => $getKeyFromFirstArgument, 'HGET' => $getKeyFromFirstArgument, 'HGETALL' => $getKeyFromFirstArgument, 'HMGET' => $getKeyFromFirstArgument, 'HMSET' => $getKeyFromFirstArgument, 'HINCRBY' => $getKeyFromFirstArgument, 'HINCRBYFLOAT' => $getKeyFromFirstArgument, 'HKEYS' => $getKeyFromFirstArgument, 'HLEN' => $getKeyFromFirstArgument, 'HSET' => $getKeyFromFirstArgument, 'HSETNX' => $getKeyFromFirstArgument, 'HVALS' => $getKeyFromFirstArgument, 'HSCAN' => $getKeyFromFirstArgument, 'HSTRLEN' => $getKeyFromFirstArgument, 'PFADD' => $getKeyFromFirstArgument, 'PFCOUNT' => $getKeyFromAllArguments, 'PFMERGE' => $getKeyFromAllArguments, 'EVAL' => array($this, 'getKeyFromScriptingCommands'), 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'), 'GEOADD' => $getKeyFromFirstArgument, 'GEOHASH' => $getKeyFromFirstArgument, 'GEOPOS' => $getKeyFromFirstArgument, 'GEODIST' => $getKeyFromFirstArgument, 'GEORADIUS' => array($this, 'getKeyFromGeoradiusCommands'), 'GEORADIUSBYMEMBER' => array($this, 'getKeyFromGeoradiusCommands'), ); } public function getSupportedCommands() { return array_keys($this->commands); } public function setCommandHandler($commandID, $callback = null) { $commandID = strtoupper($commandID); if (!isset($callback)) { unset($this->commands[$commandID]); return; } if (!is_callable($callback)) { throw new \InvalidArgumentException( 'The argument must be a callable object or NULL.' ); } $this->commands[$commandID] = $callback; } protected function getKeyFromFirstArgument(CommandInterface $command) { return $command->getArgument(0); } protected function getKeyFromAllArguments(CommandInterface $command) { $arguments = $command->getArguments(); if ($this->checkSameSlotForKeys($arguments)) { return $arguments[0]; } } protected function getKeyFromInterleavedArguments(CommandInterface $command) { $arguments = $command->getArguments(); $keys = array(); for ($i = 0; $i < count($arguments); $i += 2) { $keys[] = $arguments[$i]; } if ($this->checkSameSlotForKeys($keys)) { return $arguments[0]; } } protected function getKeyFromSortCommand(CommandInterface $command) { $arguments = $command->getArguments(); $firstKey = $arguments[0]; if (1 === $argc = count($arguments)) { return $firstKey; } $keys = array($firstKey); for ($i = 1; $i < $argc; ++$i) { if (strtoupper($arguments[$i]) === 'STORE') { $keys[] = $arguments[++$i]; } } if ($this->checkSameSlotForKeys($keys)) { return $firstKey; } } protected function getKeyFromBlockingListCommands(CommandInterface $command) { $arguments = $command->getArguments(); if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) { return $arguments[0]; } } protected function getKeyFromBitOp(CommandInterface $command) { $arguments = $command->getArguments(); if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) { return $arguments[1]; } } protected function getKeyFromGeoradiusCommands(CommandInterface $command) { $arguments = $command->getArguments(); $argc = count($arguments); $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; if ($argc > $startIndex) { $keys = array($arguments[0]); for ($i = $startIndex; $i < $argc; ++$i) { $argument = strtoupper($arguments[$i]); if ($argument === 'STORE' || $argument === 'STOREDIST') { $keys[] = $arguments[++$i]; } } if ($this->checkSameSlotForKeys($keys)) { return $arguments[0]; } else { return; } } return $arguments[0]; } protected function getKeyFromZsetAggregationCommands(CommandInterface $command) { $arguments = $command->getArguments(); $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1])); if ($this->checkSameSlotForKeys($keys)) { return $arguments[0]; } } protected function getKeyFromScriptingCommands(CommandInterface $command) { if ($command instanceof ScriptCommand) { $keys = $command->getKeys(); } else { $keys = array_slice($args = $command->getArguments(), 2, $args[1]); } if ($keys && $this->checkSameSlotForKeys($keys)) { return $keys[0]; } } public function getSlot(CommandInterface $command) { $slot = $command->getSlot(); if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) { $key = call_user_func($this->commands[$cmdID], $command); if (isset($key)) { $slot = $this->getSlotByKey($key); $command->setSlot($slot); } } return $slot; } protected function checkSameSlotForKeys(array $keys) { if (!$count = count($keys)) { return false; } $currentSlot = $this->getSlotByKey($keys[0]); for ($i = 1; $i < $count; ++$i) { $nextSlot = $this->getSlotByKey($keys[$i]); if ($currentSlot !== $nextSlot) { return false; } $currentSlot = $nextSlot; } return true; } protected function extractKeyTag($key) { if (false !== $start = strpos($key, '{')) { if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) { $key = substr($key, $start, $end - $start); } } return $key; } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', 'SCAN' => 'Predis\Command\KeyScan', 'BITPOS' => 'Predis\Command\StringBitPos', 'SSCAN' => 'Predis\Command\SetScan', 'ZSCAN' => 'Predis\Command\ZSetScan', 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', 'HSCAN' => 'Predis\Command\HashScan', 'PUBSUB' => 'Predis\Command\PubSubPubsub', 'PFADD' => 'Predis\Command\HyperLogLogAdd', 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', 'COMMAND' => 'Predis\Command\ServerCommand', ); } } 'Predis\Profile\RedisVersion200', '2.2' => 'Predis\Profile\RedisVersion220', '2.4' => 'Predis\Profile\RedisVersion240', '2.6' => 'Predis\Profile\RedisVersion260', '2.8' => 'Predis\Profile\RedisVersion280', '3.0' => 'Predis\Profile\RedisVersion300', '3.2' => 'Predis\Profile\RedisVersion320', 'dev' => 'Predis\Profile\RedisUnstable', 'default' => 'Predis\Profile\RedisVersion320', ); private function __construct() { } public static function getDefault() { return self::get('default'); } public static function getDevelopment() { return self::get('dev'); } public static function define($alias, $class) { $reflection = new \ReflectionClass($class); if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) { throw new \InvalidArgumentException("The class '$class' is not a valid profile class."); } self::$profiles[$alias] = $class; } public static function get($version) { if (!isset(self::$profiles[$version])) { throw new ClientException("Unknown server profile: '$version'."); } $profile = self::$profiles[$version]; return new $profile(); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfo', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfo', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', 'SCAN' => 'Predis\Command\KeyScan', 'BITPOS' => 'Predis\Command\StringBitPos', 'SSCAN' => 'Predis\Command\SetScan', 'ZSCAN' => 'Predis\Command\ZSetScan', 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', 'HSCAN' => 'Predis\Command\HashScan', 'PUBSUB' => 'Predis\Command\PubSubPubsub', 'PFADD' => 'Predis\Command\HyperLogLogAdd', 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', 'COMMAND' => 'Predis\Command\ServerCommand', 'HSTRLEN' => 'Predis\Command\HashStringLength', 'BITFIELD' => 'Predis\Command\StringBitField', 'GEOADD' => 'Predis\Command\GeospatialGeoAdd', 'GEOHASH' => 'Predis\Command\GeospatialGeoHash', 'GEOPOS' => 'Predis\Command\GeospatialGeoPos', 'GEODIST' => 'Predis\Command\GeospatialGeoDist', 'GEORADIUS' => 'Predis\Command\GeospatialGeoRadius', 'GEORADIUSBYMEMBER' => 'Predis\Command\GeospatialGeoRadiusByMember', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', 'SCAN' => 'Predis\Command\KeyScan', 'BITPOS' => 'Predis\Command\StringBitPos', 'SSCAN' => 'Predis\Command\SetScan', 'ZSCAN' => 'Predis\Command\ZSetScan', 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', 'HSCAN' => 'Predis\Command\HashScan', 'PUBSUB' => 'Predis\Command\PubSubPubsub', 'PFADD' => 'Predis\Command\HyperLogLogAdd', 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', 'COMMAND' => 'Predis\Command\ServerCommand', ); } } commands = $this->getSupportedCommands(); } abstract protected function getSupportedCommands(); public function supportsCommand($commandID) { return isset($this->commands[strtoupper($commandID)]); } public function supportsCommands(array $commandIDs) { foreach ($commandIDs as $commandID) { if (!$this->supportsCommand($commandID)) { return false; } } return true; } public function getCommandClass($commandID) { if (isset($this->commands[$commandID = strtoupper($commandID)])) { return $this->commands[$commandID]; } } public function createCommand($commandID, array $arguments = array()) { $commandID = strtoupper($commandID); if (!isset($this->commands[$commandID])) { throw new ClientException("Command '$commandID' is not a registered Redis command."); } $commandClass = $this->commands[$commandID]; $command = new $commandClass(); $command->setArguments($arguments); if (isset($this->processor)) { $this->processor->process($command); } return $command; } public function defineCommand($commandID, $class) { $reflection = new \ReflectionClass($class); if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) { throw new \InvalidArgumentException("The class '$class' is not a valid command class."); } $this->commands[strtoupper($commandID)] = $class; } public function setProcessor(ProcessorInterface $processor = null) { $this->processor = $processor; } public function getProcessor() { return $this->processor; } public function __toString() { return $this->getVersion(); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfo', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', ); } } 'Predis\Command\KeyExists', 'DEL' => 'Predis\Command\KeyDelete', 'TYPE' => 'Predis\Command\KeyType', 'KEYS' => 'Predis\Command\KeyKeys', 'RANDOMKEY' => 'Predis\Command\KeyRandom', 'RENAME' => 'Predis\Command\KeyRename', 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', 'EXPIRE' => 'Predis\Command\KeyExpire', 'EXPIREAT' => 'Predis\Command\KeyExpireAt', 'TTL' => 'Predis\Command\KeyTimeToLive', 'MOVE' => 'Predis\Command\KeyMove', 'SORT' => 'Predis\Command\KeySort', 'DUMP' => 'Predis\Command\KeyDump', 'RESTORE' => 'Predis\Command\KeyRestore', 'SET' => 'Predis\Command\StringSet', 'SETNX' => 'Predis\Command\StringSetPreserve', 'MSET' => 'Predis\Command\StringSetMultiple', 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', 'GET' => 'Predis\Command\StringGet', 'MGET' => 'Predis\Command\StringGetMultiple', 'GETSET' => 'Predis\Command\StringGetSet', 'INCR' => 'Predis\Command\StringIncrement', 'INCRBY' => 'Predis\Command\StringIncrementBy', 'DECR' => 'Predis\Command\StringDecrement', 'DECRBY' => 'Predis\Command\StringDecrementBy', 'RPUSH' => 'Predis\Command\ListPushTail', 'LPUSH' => 'Predis\Command\ListPushHead', 'LLEN' => 'Predis\Command\ListLength', 'LRANGE' => 'Predis\Command\ListRange', 'LTRIM' => 'Predis\Command\ListTrim', 'LINDEX' => 'Predis\Command\ListIndex', 'LSET' => 'Predis\Command\ListSet', 'LREM' => 'Predis\Command\ListRemove', 'LPOP' => 'Predis\Command\ListPopFirst', 'RPOP' => 'Predis\Command\ListPopLast', 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', 'SADD' => 'Predis\Command\SetAdd', 'SREM' => 'Predis\Command\SetRemove', 'SPOP' => 'Predis\Command\SetPop', 'SMOVE' => 'Predis\Command\SetMove', 'SCARD' => 'Predis\Command\SetCardinality', 'SISMEMBER' => 'Predis\Command\SetIsMember', 'SINTER' => 'Predis\Command\SetIntersection', 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', 'SUNION' => 'Predis\Command\SetUnion', 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', 'SDIFF' => 'Predis\Command\SetDifference', 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', 'SMEMBERS' => 'Predis\Command\SetMembers', 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', 'ZADD' => 'Predis\Command\ZSetAdd', 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', 'ZREM' => 'Predis\Command\ZSetRemove', 'ZRANGE' => 'Predis\Command\ZSetRange', 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', 'ZCARD' => 'Predis\Command\ZSetCardinality', 'ZSCORE' => 'Predis\Command\ZSetScore', 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', 'PING' => 'Predis\Command\ConnectionPing', 'AUTH' => 'Predis\Command\ConnectionAuth', 'SELECT' => 'Predis\Command\ConnectionSelect', 'ECHO' => 'Predis\Command\ConnectionEcho', 'QUIT' => 'Predis\Command\ConnectionQuit', 'INFO' => 'Predis\Command\ServerInfoV26x', 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', 'MONITOR' => 'Predis\Command\ServerMonitor', 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', 'FLUSHALL' => 'Predis\Command\ServerFlushAll', 'SAVE' => 'Predis\Command\ServerSave', 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', 'LASTSAVE' => 'Predis\Command\ServerLastSave', 'SHUTDOWN' => 'Predis\Command\ServerShutdown', 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', 'SETEX' => 'Predis\Command\StringSetExpire', 'APPEND' => 'Predis\Command\StringAppend', 'SUBSTR' => 'Predis\Command\StringSubstr', 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', 'BRPOP' => 'Predis\Command\ListPopLastBlocking', 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', 'ZCOUNT' => 'Predis\Command\ZSetCount', 'ZRANK' => 'Predis\Command\ZSetRank', 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', 'HSET' => 'Predis\Command\HashSet', 'HSETNX' => 'Predis\Command\HashSetPreserve', 'HMSET' => 'Predis\Command\HashSetMultiple', 'HINCRBY' => 'Predis\Command\HashIncrementBy', 'HGET' => 'Predis\Command\HashGet', 'HMGET' => 'Predis\Command\HashGetMultiple', 'HDEL' => 'Predis\Command\HashDelete', 'HEXISTS' => 'Predis\Command\HashExists', 'HLEN' => 'Predis\Command\HashLength', 'HKEYS' => 'Predis\Command\HashKeys', 'HVALS' => 'Predis\Command\HashValues', 'HGETALL' => 'Predis\Command\HashGetAll', 'MULTI' => 'Predis\Command\TransactionMulti', 'EXEC' => 'Predis\Command\TransactionExec', 'DISCARD' => 'Predis\Command\TransactionDiscard', 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', 'PUBLISH' => 'Predis\Command\PubSubPublish', 'CONFIG' => 'Predis\Command\ServerConfig', 'PERSIST' => 'Predis\Command\KeyPersist', 'STRLEN' => 'Predis\Command\StringStrlen', 'SETRANGE' => 'Predis\Command\StringSetRange', 'GETRANGE' => 'Predis\Command\StringGetRange', 'SETBIT' => 'Predis\Command\StringSetBit', 'GETBIT' => 'Predis\Command\StringGetBit', 'RPUSHX' => 'Predis\Command\ListPushTailX', 'LPUSHX' => 'Predis\Command\ListPushHeadX', 'LINSERT' => 'Predis\Command\ListInsert', 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', 'WATCH' => 'Predis\Command\TransactionWatch', 'UNWATCH' => 'Predis\Command\TransactionUnwatch', 'OBJECT' => 'Predis\Command\ServerObject', 'SLOWLOG' => 'Predis\Command\ServerSlowlog', 'CLIENT' => 'Predis\Command\ServerClient', 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', 'MIGRATE' => 'Predis\Command\KeyMigrate', 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', 'BITOP' => 'Predis\Command\StringBitOp', 'BITCOUNT' => 'Predis\Command\StringBitCount', 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', 'EVAL' => 'Predis\Command\ServerEval', 'EVALSHA' => 'Predis\Command\ServerEvalSHA', 'SCRIPT' => 'Predis\Command\ServerScript', 'TIME' => 'Predis\Command\ServerTime', 'SENTINEL' => 'Predis\Command\ServerSentinel', ); } } options = $this->createOptions($options ?: array()); $this->connection = $this->createConnection($parameters ?: array()); $this->profile = $this->options->profile; } protected function createOptions($options) { if (is_array($options)) { return new Options($options); } if ($options instanceof OptionsInterface) { return $options; } throw new \InvalidArgumentException('Invalid type for client options.'); } protected function createConnection($parameters) { if ($parameters instanceof ConnectionInterface) { return $parameters; } if ($parameters instanceof ParametersInterface || is_string($parameters)) { return $this->options->connections->create($parameters); } if (is_array($parameters)) { if (!isset($parameters[0])) { return $this->options->connections->create($parameters); } $options = $this->options; if ($options->defined('aggregate')) { $initializer = $this->getConnectionInitializerWrapper($options->aggregate); $connection = $initializer($parameters, $options); } elseif ($options->defined('replication')) { $replication = $options->replication; if ($replication instanceof AggregateConnectionInterface) { $connection = $replication; $options->connections->aggregate($connection, $parameters); } else { $initializer = $this->getConnectionInitializerWrapper($replication); $connection = $initializer($parameters, $options); } } else { $connection = $options->cluster; $options->connections->aggregate($connection, $parameters); } return $connection; } if (is_callable($parameters)) { $initializer = $this->getConnectionInitializerWrapper($parameters); $connection = $initializer($this->options); return $connection; } throw new \InvalidArgumentException('Invalid type for connection parameters.'); } protected function getConnectionInitializerWrapper($callable) { return function () use ($callable) { $connection = call_user_func_array($callable, func_get_args()); if (!$connection instanceof ConnectionInterface) { throw new \UnexpectedValueException( 'The callable connection initializer returned an invalid type.' ); } return $connection; }; } public function getProfile() { return $this->profile; } public function getOptions() { return $this->options; } public function getClientFor($connectionID) { if (!$connection = $this->getConnectionById($connectionID)) { throw new \InvalidArgumentException("Invalid connection ID: $connectionID."); } return new static($connection, $this->options); } public function connect() { $this->connection->connect(); } public function disconnect() { $this->connection->disconnect(); } public function quit() { $this->disconnect(); } public function isConnected() { return $this->connection->isConnected(); } public function getConnection() { return $this->connection; } public function getConnectionById($connectionID) { if (!$this->connection instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Retrieving connections by ID is supported only by aggregate connections.' ); } return $this->connection->getConnectionById($connectionID); } public function executeRaw(array $arguments, &$error = null) { $error = false; $response = $this->connection->executeCommand( new RawCommand($arguments) ); if ($response instanceof ResponseInterface) { if ($response instanceof ErrorResponseInterface) { $error = true; } return (string) $response; } return $response; } public function __call($commandID, $arguments) { return $this->executeCommand( $this->createCommand($commandID, $arguments) ); } public function createCommand($commandID, $arguments = array()) { return $this->profile->createCommand($commandID, $arguments); } public function executeCommand(CommandInterface $command) { $response = $this->connection->executeCommand($command); if ($response instanceof ResponseInterface) { if ($response instanceof ErrorResponseInterface) { $response = $this->onErrorResponse($command, $response); } return $response; } return $command->parseResponse($response); } protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response) { if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') { $eval = $this->createCommand('EVAL'); $eval->setRawArguments($command->getEvalArguments()); $response = $this->executeCommand($eval); if (!$response instanceof ResponseInterface) { $response = $command->parseResponse($response); } return $response; } if ($this->options->exceptions) { throw new ServerException($response->getMessage()); } return $response; } private function sharedContextFactory($initializer, $argv = null) { switch (count($argv)) { case 0: return $this->$initializer(); case 1: return is_array($argv[0]) ? $this->$initializer($argv[0]) : $this->$initializer(null, $argv[0]); case 2: list($arg0, $arg1) = $argv; return $this->$initializer($arg0, $arg1); default: return $this->$initializer($this, $argv); } } public function pipeline() { return $this->sharedContextFactory('createPipeline', func_get_args()); } protected function createPipeline(array $options = null, $callable = null) { if (isset($options['atomic']) && $options['atomic']) { $class = 'Predis\Pipeline\Atomic'; } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) { $class = 'Predis\Pipeline\FireAndForget'; } else { $class = 'Predis\Pipeline\Pipeline'; } $pipeline = new $class($this); if (isset($callable)) { return $pipeline->execute($callable); } return $pipeline; } public function transaction() { return $this->sharedContextFactory('createTransaction', func_get_args()); } protected function createTransaction(array $options = null, $callable = null) { $transaction = new MultiExecTransaction($this, $options); if (isset($callable)) { return $transaction->execute($callable); } return $transaction; } public function pubSubLoop() { return $this->sharedContextFactory('createPubSub', func_get_args()); } protected function createPubSub(array $options = null, $callable = null) { $pubsub = new PubSubConsumer($this, $options); if (!isset($callable)) { return $pubsub; } foreach ($pubsub as $message) { if (call_user_func($callable, $pubsub, $message) === false) { $pubsub->stop(); } } } public function monitor() { return new MonitorConsumer($this); } public function getIterator() { $clients = array(); $connection = $this->getConnection(); if (!$connection instanceof \Traversable) { throw new ClientException('The underlying connection is not traversable'); } foreach ($connection as $node) { $clients[(string) $node] = new static($node, $this->getOptions()); } return new \ArrayIterator($clients); } } assertClient($client); $this->client = $client; $this->start(); } public function __destruct() { $this->stop(); } private function assertClient(ClientInterface $client) { if ($client->getConnection() instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Cannot initialize a monitor consumer over aggregate connections.' ); } if ($client->getProfile()->supportsCommand('MONITOR') === false) { throw new NotSupportedException("The current profile does not support 'MONITOR'."); } } protected function start() { $this->client->executeCommand( $this->client->createCommand('MONITOR') ); $this->valid = true; } public function stop() { $this->client->disconnect(); $this->valid = false; } public function rewind() { } public function current() { return $this->getValue(); } public function key() { return $this->position; } public function next() { ++$this->position; } public function valid() { return $this->valid; } private function getValue() { $database = 0; $client = null; $event = $this->client->getConnection()->read(); $callback = function ($matches) use (&$database, &$client) { if (2 === $count = count($matches)) { $database = (int) $matches[1]; } if (4 === $count) { $database = (int) $matches[2]; $client = $matches[3]; } return ' '; }; $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1); @list($timestamp, $command, $arguments) = explode(' ', $event, 3); return (object) array( 'timestamp' => (float) $timestamp, 'database' => $database, 'client' => $client, 'command' => substr($command, 1, -1), 'arguments' => $arguments, ); } } directory = $baseDirectory; $this->prefix = __NAMESPACE__.'\\'; $this->prefixLength = strlen($this->prefix); } public static function register($prepend = false) { spl_autoload_register(array(new self(), 'autoload'), true, $prepend); } public function autoload($className) { if (0 === strpos($className, $this->prefix)) { $parts = explode('\\', substr($className, $this->prefixLength)); $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php'; if (is_file($filepath)) { require $filepath; } } } } flags = 0; } public function set($flags) { $this->flags = $flags; } public function get() { return $this->flags; } public function flag($flags) { $this->flags |= $flags; } public function unflag($flags) { $this->flags &= ~$flags; } public function check($flags) { return ($this->flags & $flags) === $flags; } public function reset() { $this->flags = 0; } public function isReset() { return $this->flags === 0; } public function isInitialized() { return $this->check(self::INITIALIZED); } public function isExecuting() { return $this->check(self::INSIDEBLOCK); } public function isCAS() { return $this->check(self::CAS); } public function isWatchAllowed() { return $this->check(self::INITIALIZED) && !$this->check(self::CAS); } public function isWatching() { return $this->check(self::WATCH); } public function isDiscarded() { return $this->check(self::DISCARDED); } } assertClient($client); $this->client = $client; $this->state = new MultiExecState(); $this->configure($client, $options ?: array()); $this->reset(); } private function assertClient(ClientInterface $client) { if ($client->getConnection() instanceof AggregateConnectionInterface) { throw new NotSupportedException( 'Cannot initialize a MULTI/EXEC transaction over aggregate connections.' ); } if (!$client->getProfile()->supportsCommands(array('MULTI', 'EXEC', 'DISCARD'))) { throw new NotSupportedException( 'The current profile does not support MULTI, EXEC and DISCARD.' ); } } protected function configure(ClientInterface $client, array $options) { if (isset($options['exceptions'])) { $this->exceptions = (bool) $options['exceptions']; } else { $this->exceptions = $client->getOptions()->exceptions; } if (isset($options['cas'])) { $this->modeCAS = (bool) $options['cas']; } if (isset($options['watch']) && $keys = $options['watch']) { $this->watchKeys = $keys; } if (isset($options['retry'])) { $this->attempts = (int) $options['retry']; } } protected function reset() { $this->state->reset(); $this->commands = new \SplQueue(); } protected function initialize() { if ($this->state->isInitialized()) { return; } if ($this->modeCAS) { $this->state->flag(MultiExecState::CAS); } if ($this->watchKeys) { $this->watch($this->watchKeys); } $cas = $this->state->isCAS(); $discarded = $this->state->isDiscarded(); if (!$cas || ($cas && $discarded)) { $this->call('MULTI'); if ($discarded) { $this->state->unflag(MultiExecState::CAS); } } $this->state->unflag(MultiExecState::DISCARDED); $this->state->flag(MultiExecState::INITIALIZED); } public function __call($method, $arguments) { return $this->executeCommand( $this->client->createCommand($method, $arguments) ); } protected function call($commandID, array $arguments = array()) { $response = $this->client->executeCommand( $this->client->createCommand($commandID, $arguments) ); if ($response instanceof ErrorResponseInterface) { throw new ServerException($response->getMessage()); } return $response; } public function executeCommand(CommandInterface $command) { $this->initialize(); if ($this->state->isCAS()) { return $this->client->executeCommand($command); } $response = $this->client->getConnection()->executeCommand($command); if ($response instanceof StatusResponse && $response == 'QUEUED') { $this->commands->enqueue($command); } elseif ($response instanceof ErrorResponseInterface) { throw new AbortedMultiExecException($this, $response->getMessage()); } else { $this->onProtocolError('The server did not return a +QUEUED status response.'); } return $this; } public function watch($keys) { if (!$this->client->getProfile()->supportsCommand('WATCH')) { throw new NotSupportedException('WATCH is not supported by the current profile.'); } if ($this->state->isWatchAllowed()) { throw new ClientException('Sending WATCH after MULTI is not allowed.'); } $response = $this->call('WATCH', is_array($keys) ? $keys : array($keys)); $this->state->flag(MultiExecState::WATCH); return $response; } public function multi() { if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) { $this->state->unflag(MultiExecState::CAS); $this->call('MULTI'); } else { $this->initialize(); } return $this; } public function unwatch() { if (!$this->client->getProfile()->supportsCommand('UNWATCH')) { throw new NotSupportedException( 'UNWATCH is not supported by the current profile.' ); } $this->state->unflag(MultiExecState::WATCH); $this->__call('UNWATCH', array()); return $this; } public function discard() { if ($this->state->isInitialized()) { $this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD'); $this->reset(); $this->state->flag(MultiExecState::DISCARDED); } return $this; } public function exec() { return $this->execute(); } private function checkBeforeExecution($callable) { if ($this->state->isExecuting()) { throw new ClientException( 'Cannot invoke "execute" or "exec" inside an active transaction context.' ); } if ($callable) { if (!is_callable($callable)) { throw new \InvalidArgumentException('The argument must be a callable object.'); } if (!$this->commands->isEmpty()) { $this->discard(); throw new ClientException( 'Cannot execute a transaction block after using fluent interface.' ); } } elseif ($this->attempts) { $this->discard(); throw new ClientException( 'Automatic retries are supported only when a callable block is provided.' ); } } public function execute($callable = null) { $this->checkBeforeExecution($callable); $execResponse = null; $attempts = $this->attempts; do { if ($callable) { $this->executeTransactionBlock($callable); } if ($this->commands->isEmpty()) { if ($this->state->isWatching()) { $this->discard(); } return; } $execResponse = $this->call('EXEC'); if ($execResponse === null) { if ($attempts === 0) { throw new AbortedMultiExecException( $this, 'The current transaction has been aborted by the server.' ); } $this->reset(); continue; } break; } while ($attempts-- > 0); $response = array(); $commands = $this->commands; $size = count($execResponse); if ($size !== count($commands)) { $this->onProtocolError('EXEC returned an unexpected number of response items.'); } for ($i = 0; $i < $size; ++$i) { $cmdResponse = $execResponse[$i]; if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) { throw new ServerException($cmdResponse->getMessage()); } $response[$i] = $commands->dequeue()->parseResponse($cmdResponse); } return $response; } protected function executeTransactionBlock($callable) { $exception = null; $this->state->flag(MultiExecState::INSIDEBLOCK); try { call_user_func($callable, $this); } catch (CommunicationException $exception) { } catch (ServerException $exception) { } catch (\Exception $exception) { $this->discard(); } $this->state->unflag(MultiExecState::INSIDEBLOCK); if ($exception) { throw $exception; } } private function onProtocolError($message) { CommunicationException::handle(new ProtocolException( $this->client->getConnection(), $message )); } } transaction = $transaction; } public function getTransaction() { return $this->transaction; } } disallowed = $this->getDisallowedOperations(); $this->readonly = $this->getReadOnlyOperations(); $this->readonlySHA1 = array(); } public function isReadOperation(CommandInterface $command) { if (isset($this->disallowed[$id = $command->getId()])) { throw new NotSupportedException( "The command '$id' is not allowed in replication mode." ); } if (isset($this->readonly[$id])) { if (true === $readonly = $this->readonly[$id]) { return true; } return call_user_func($readonly, $command); } if (($eval = $id === 'EVAL') || $id === 'EVALSHA') { $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0); if (isset($this->readonlySHA1[$sha1])) { if (true === $readonly = $this->readonlySHA1[$sha1]) { return true; } return call_user_func($readonly, $command); } } return false; } public function isDisallowedOperation(CommandInterface $command) { return isset($this->disallowed[$command->getId()]); } protected function isSortReadOnly(CommandInterface $command) { $arguments = $command->getArguments(); $argc = count($arguments); if ($argc > 1) { for ($i = 1; $i < $argc; ++$i) { $argument = strtoupper($arguments[$i]); if ($argument === 'STORE') { return false; } } } return true; } protected function isBitfieldReadOnly(CommandInterface $command) { $arguments = $command->getArguments(); $argc = count($arguments); if ($argc >= 2) { for ($i = 1; $i < $argc; ++$i) { $argument = strtoupper($arguments[$i]); if ($argument === 'SET' || $argument === 'INCRBY') { return false; } } } return true; } protected function isGeoradiusReadOnly(CommandInterface $command) { $arguments = $command->getArguments(); $argc = count($arguments); $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; if ($argc > $startIndex) { for ($i = $startIndex; $i < $argc; ++$i) { $argument = strtoupper($arguments[$i]); if ($argument === 'STORE' || $argument === 'STOREDIST') { return false; } } } return true; } public function setCommandReadOnly($commandID, $readonly = true) { $commandID = strtoupper($commandID); if ($readonly) { $this->readonly[$commandID] = $readonly; } else { unset($this->readonly[$commandID]); } } public function setScriptReadOnly($script, $readonly = true) { $sha1 = sha1($script); if ($readonly) { $this->readonlySHA1[$sha1] = $readonly; } else { unset($this->readonlySHA1[$sha1]); } } protected function getDisallowedOperations() { return array( 'SHUTDOWN' => true, 'INFO' => true, 'DBSIZE' => true, 'LASTSAVE' => true, 'CONFIG' => true, 'MONITOR' => true, 'SLAVEOF' => true, 'SAVE' => true, 'BGSAVE' => true, 'BGREWRITEAOF' => true, 'SLOWLOG' => true, ); } protected function getReadOnlyOperations() { return array( 'EXISTS' => true, 'TYPE' => true, 'KEYS' => true, 'SCAN' => true, 'RANDOMKEY' => true, 'TTL' => true, 'GET' => true, 'MGET' => true, 'SUBSTR' => true, 'STRLEN' => true, 'GETRANGE' => true, 'GETBIT' => true, 'LLEN' => true, 'LRANGE' => true, 'LINDEX' => true, 'SCARD' => true, 'SISMEMBER' => true, 'SINTER' => true, 'SUNION' => true, 'SDIFF' => true, 'SMEMBERS' => true, 'SSCAN' => true, 'SRANDMEMBER' => true, 'ZRANGE' => true, 'ZREVRANGE' => true, 'ZRANGEBYSCORE' => true, 'ZREVRANGEBYSCORE' => true, 'ZCARD' => true, 'ZSCORE' => true, 'ZCOUNT' => true, 'ZRANK' => true, 'ZREVRANK' => true, 'ZSCAN' => true, 'ZLEXCOUNT' => true, 'ZRANGEBYLEX' => true, 'ZREVRANGEBYLEX' => true, 'HGET' => true, 'HMGET' => true, 'HEXISTS' => true, 'HLEN' => true, 'HKEYS' => true, 'HVALS' => true, 'HGETALL' => true, 'HSCAN' => true, 'HSTRLEN' => true, 'PING' => true, 'AUTH' => true, 'SELECT' => true, 'ECHO' => true, 'QUIT' => true, 'OBJECT' => true, 'BITCOUNT' => true, 'BITPOS' => true, 'TIME' => true, 'PFCOUNT' => true, 'SORT' => array($this, 'isSortReadOnly'), 'BITFIELD' => array($this, 'isBitfieldReadOnly'), 'GEOHASH' => true, 'GEOPOS' => true, 'GEODIST' => true, 'GEORADIUS' => array($this, 'isGeoradiusReadOnly'), 'GEORADIUSBYMEMBER' => array($this, 'isGeoradiusReadOnly'), ); } } payload = $payload; } public function __toString() { return $this->payload; } public function getPayload() { return $this->payload; } public static function get($payload) { switch ($payload) { case 'OK': case 'QUEUED': if (isset(self::$$payload)) { return self::$$payload; } return self::$$payload = new self($payload); default: return new self($payload); } } } getMessage(), 2); return $errorType; } public function toErrorResponse() { return new Error($this->getMessage()); } } checkPreconditions($iterator); $this->size = count($iterator) / 2; $this->iterator = $iterator; $this->position = $iterator->getPosition(); $this->current = $this->size > 0 ? $this->getValue() : null; } protected function checkPreconditions(MultiBulk $iterator) { if ($iterator->getPosition() !== 0) { throw new \InvalidArgumentException( 'Cannot initialize a tuple iterator using an already initiated iterator.' ); } if (($size = count($iterator)) % 2 !== 0) { throw new \UnexpectedValueException('Invalid response size for a tuple iterator.'); } } public function getInnerIterator() { return $this->iterator; } public function __destruct() { $this->iterator->drop(true); } protected function getValue() { $k = $this->iterator->current(); $this->iterator->next(); $v = $this->iterator->current(); $this->iterator->next(); return array($k, $v); } } connection = $connection; $this->size = $size; $this->position = 0; $this->current = $size > 0 ? $this->getValue() : null; } public function __destruct() { $this->drop(true); } public function drop($disconnect = false) { if ($disconnect) { if ($this->valid()) { $this->position = $this->size; $this->connection->disconnect(); } } else { while ($this->valid()) { $this->next(); } } } protected function getValue() { return $this->connection->read(); } } current; } public function key() { return $this->position; } public function next() { if (++$this->position < $this->size) { $this->current = $this->getValue(); } } public function valid() { return $this->position < $this->size; } public function count() { return $this->size; } public function getPosition() { return $this->position; } abstract protected function getValue(); } message = $message; } public function getMessage() { return $this->message; } public function getErrorType() { list($errorType) = explode(' ', $this->getMessage(), 2); return $errorType; } public function __toString() { return $this->getMessage(); } } setRequestSerializer($serializer ?: new RequestSerializer()); $this->setResponseReader($reader ?: new ResponseReader()); } public function write(CompositeConnectionInterface $connection, CommandInterface $command) { $connection->writeBuffer($this->serializer->serialize($command)); } public function read(CompositeConnectionInterface $connection) { return $this->reader->read($connection); } public function setRequestSerializer(RequestSerializerInterface $serializer) { $this->serializer = $serializer; } public function getRequestSerializer() { return $this->serializer; } public function setResponseReader(ResponseReaderInterface $reader) { $this->reader = $reader; } public function getResponseReader() { return $this->reader; } } 0) { $handlersCache = array(); $reader = $connection->getProtocol()->getResponseReader(); for ($i = 0; $i < $length; ++$i) { $header = $connection->readLine(); $prefix = $header[0]; if (isset($handlersCache[$prefix])) { $handler = $handlersCache[$prefix]; } else { $handler = $reader->getHandler($prefix); $handlersCache[$prefix] = $handler; } $list[$i] = $handler->handle($connection, substr($header, 1)); } } return $list; } } = 0) { return substr($connection->readBuffer($length + 2), 0, -2); } if ($length == -1) { return; } CommunicationException::handle(new ProtocolException( $connection, "Value '$payload' is not a valid length for a bulk response." )); return; } } getId(); $arguments = $command->getArguments(); $cmdlen = strlen($commandID); $reqlen = count($arguments) + 1; $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; foreach ($arguments as $argument) { $arglen = strlen($argument); $buffer .= "\${$arglen}\r\n{$argument}\r\n"; } return $buffer; } } mbiterable = false; $this->serializer = new RequestSerializer(); } public function write(CompositeConnectionInterface $connection, CommandInterface $command) { $request = $this->serializer->serialize($command); $connection->writeBuffer($request); } public function read(CompositeConnectionInterface $connection) { $chunk = $connection->readLine(); $prefix = $chunk[0]; $payload = substr($chunk, 1); switch ($prefix) { case '+': return new StatusResponse($payload); case '$': $size = (int) $payload; if ($size === -1) { return; } return substr($connection->readBuffer($size + 2), 0, -2); case '*': $count = (int) $payload; if ($count === -1) { return; } if ($this->mbiterable) { return new MultiBulkIterator($connection, $count); } $multibulk = array(); for ($i = 0; $i < $count; ++$i) { $multibulk[$i] = $this->read($connection); } return $multibulk; case ':': $integer = (int) $payload; return $integer == $payload ? $integer : $payload; case '-': return new ErrorResponse($payload); default: CommunicationException::handle(new ProtocolException( $connection, "Unknown response prefix: '$prefix'." )); return; } } public function useIterableMultibulk($value) { $this->mbiterable = (bool) $value; } } handlers = $this->getDefaultHandlers(); } protected function getDefaultHandlers() { return array( '+' => new Handler\StatusResponse(), '-' => new Handler\ErrorResponse(), ':' => new Handler\IntegerResponse(), '$' => new Handler\BulkResponse(), '*' => new Handler\MultiBulkResponse(), ); } public function setHandler($prefix, Handler\ResponseHandlerInterface $handler) { $this->handlers[$prefix] = $handler; } public function getHandler($prefix) { if (isset($this->handlers[$prefix])) { return $this->handlers[$prefix]; } return; } public function read(CompositeConnectionInterface $connection) { $header = $connection->readLine(); if ($header === '') { $this->onProtocolError($connection, 'Unexpected empty reponse header.'); } $prefix = $header[0]; if (!isset($this->handlers[$prefix])) { $this->onProtocolError($connection, "Unknown response prefix: '$prefix'."); } $payload = $this->handlers[$prefix]->handle($connection, substr($header, 1)); return $payload; } protected function onProtocolError(CompositeConnectionInterface $connection, $message) { CommunicationException::handle( new ProtocolException($connection, $message) ); } } client = $client; if (isset($options['gc_maxlifetime'])) { $this->ttl = (int) $options['gc_maxlifetime']; } else { $this->ttl = ini_get('session.gc_maxlifetime'); } } public function register() { if (PHP_VERSION_ID >= 50400) { session_set_save_handler($this, true); } else { session_set_save_handler( array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'gc') ); } } public function open($save_path, $session_id) { return true; } public function close() { return true; } public function gc($maxlifetime) { return true; } public function read($session_id) { if ($data = $this->client->get($session_id)) { return $data; } return ''; } public function write($session_id, $session_data) { $this->client->setex($session_id, $this->ttl, $session_data); return true; } public function destroy($session_id) { $this->client->del($session_id); return true; } public function getClient() { return $this->client; } public function getMaxLifeTime() { return $this->ttl; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } } getArguments(), CASE_UPPER); switch (strtoupper($args[0])) { case 'LIST': return $this->parseClientList($data); case 'KILL': case 'GETNAME': case 'SETNAME': default: return $data; } } protected function parseClientList($data) { $clients = array(); foreach (explode("\n", $data, -1) as $clientData) { $client = array(); foreach (explode(' ', $clientData) as $kv) { @list($k, $v) = explode('=', $kv); $client[$k] = $v; } $clients[] = $client; } return $clients; } } $score) { $arguments[] = $score; $arguments[] = $member; } } return $arguments; } } $entry) { $log[$index] = array( 'id' => $entry[0], 'timestamp' => $entry[1], 'duration' => $entry[2], 'command' => $entry[3], ); } return $log; } return $data; } } getArgument(0)); } } $v) { $flattenedKVs[] = $k; $flattenedKVs[] = $v; } return $flattenedKVs; } return $arguments; } } parseRow($row); $current[$k] = $v; } return $info; } } commandID = strtoupper(array_shift($arguments)); $this->arguments = $arguments; } public static function create($commandID ) { $arguments = func_get_args(); $command = new self($arguments); return $command; } public function getId() { return $this->commandID; } public function setArguments(array $arguments) { $this->arguments = $arguments; unset($this->slot); } public function setRawArguments(array $arguments) { $this->setArguments($arguments); } public function getArguments() { return $this->arguments; } public function getArgument($index) { if (isset($this->arguments[$index])) { return $this->arguments[$index]; } } public function setSlot($slot) { $this->slot = $slot; } public function getSlot() { if (isset($this->slot)) { return $this->slot; } } public function parseResponse($data) { return $data; } } true); $lastType = 'array'; } if ($lastType === 'array') { $options = $this->prepareOptions(array_pop($arguments)); return array_merge($arguments, $options); } } return $arguments; } protected function prepareOptions($options) { $opts = array_change_key_case($options, CASE_UPPER); $finalizedOpts = array(); if (!empty($opts['WITHSCORES'])) { $finalizedOpts[] = 'WITHSCORES'; } return $finalizedOpts; } protected function withScores() { $arguments = $this->getArguments(); if (count($arguments) < 4) { return false; } return strtoupper($arguments[3]) === 'WITHSCORES'; } public function parseResponse($data) { if ($this->withScores()) { $result = array(); for ($i = 0; $i < count($data); ++$i) { $result[$data[$i]] = $data[++$i]; } return $result; } return $data; } } $value) { $modifier = strtoupper($modifier); if ($modifier === 'COPY' && $value == true) { $arguments[] = $modifier; } if ($modifier === 'REPLACE' && $value == true) { $arguments[] = $modifier; } } } return $arguments; } } getArgument(0))) { case 'masters': case 'slaves': return self::processMastersOrSlaves($data); default: return $data; } } protected static function processMastersOrSlaves(array $servers) { foreach ($servers as $idx => $node) { $processed = array(); $count = count($node); for ($i = 0; $i < $count; ++$i) { $processed[$node[$i]] = $node[++$i]; } $servers[$idx] = $processed; } return $servers; } } 2 && is_array($arguments[$argc - 1])) { $options = $this->prepareOptions(array_pop($arguments)); } if (is_array($arguments[1])) { $arguments = array_merge( array($arguments[0], count($arguments[1])), $arguments[1] ); } return array_merge($arguments, $options); } private function prepareOptions($options) { $opts = array_change_key_case($options, CASE_UPPER); $finalizedOpts = array(); if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) { $finalizedOpts[] = 'WEIGHTS'; foreach ($opts['WEIGHTS'] as $weight) { $finalizedOpts[] = $weight; } } if (isset($opts['AGGREGATE'])) { $finalizedOpts[] = 'AGGREGATE'; $finalizedOpts[] = $opts['AGGREGATE']; } return $finalizedOpts; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } public function parseResponse($data) { if (is_array($data)) { $members = $data[1]; $result = array(); for ($i = 0; $i < count($members); ++$i) { $result[$members[$i]] = (float) $members[++$i]; } $data[1] = $result; } return $data; } } add($processor); } } public function add(ProcessorInterface $processor) { $this->processors[] = $processor; } public function remove(ProcessorInterface $processor) { if (false !== $index = array_search($processor, $this->processors, true)) { unset($this[$index]); } } public function process(CommandInterface $command) { for ($i = 0; $i < $count = count($this->processors); ++$i) { $this->processors[$i]->process($command); } } public function getProcessors() { return $this->processors; } public function getIterator() { return new \ArrayIterator($this->processors); } public function count() { return count($this->processors); } public function offsetExists($index) { return isset($this->processors[$index]); } public function offsetGet($index) { return $this->processors[$index]; } public function offsetSet($index, $processor) { if (!$processor instanceof ProcessorInterface) { throw new \InvalidArgumentException( 'A processor chain accepts only instances of '. "'Predis\Command\Processor\ProcessorInterface'." ); } $this->processors[$index] = $processor; } public function offsetUnset($index) { unset($this->processors[$index]); $this->processors = array_values($this->processors); } } prefix = $prefix; $this->commands = array( 'EXISTS' => 'static::all', 'DEL' => 'static::all', 'TYPE' => 'static::first', 'KEYS' => 'static::first', 'RENAME' => 'static::all', 'RENAMENX' => 'static::all', 'EXPIRE' => 'static::first', 'EXPIREAT' => 'static::first', 'TTL' => 'static::first', 'MOVE' => 'static::first', 'SORT' => 'static::sort', 'DUMP' => 'static::first', 'RESTORE' => 'static::first', 'SET' => 'static::first', 'SETNX' => 'static::first', 'MSET' => 'static::interleaved', 'MSETNX' => 'static::interleaved', 'GET' => 'static::first', 'MGET' => 'static::all', 'GETSET' => 'static::first', 'INCR' => 'static::first', 'INCRBY' => 'static::first', 'DECR' => 'static::first', 'DECRBY' => 'static::first', 'RPUSH' => 'static::first', 'LPUSH' => 'static::first', 'LLEN' => 'static::first', 'LRANGE' => 'static::first', 'LTRIM' => 'static::first', 'LINDEX' => 'static::first', 'LSET' => 'static::first', 'LREM' => 'static::first', 'LPOP' => 'static::first', 'RPOP' => 'static::first', 'RPOPLPUSH' => 'static::all', 'SADD' => 'static::first', 'SREM' => 'static::first', 'SPOP' => 'static::first', 'SMOVE' => 'static::skipLast', 'SCARD' => 'static::first', 'SISMEMBER' => 'static::first', 'SINTER' => 'static::all', 'SINTERSTORE' => 'static::all', 'SUNION' => 'static::all', 'SUNIONSTORE' => 'static::all', 'SDIFF' => 'static::all', 'SDIFFSTORE' => 'static::all', 'SMEMBERS' => 'static::first', 'SRANDMEMBER' => 'static::first', 'ZADD' => 'static::first', 'ZINCRBY' => 'static::first', 'ZREM' => 'static::first', 'ZRANGE' => 'static::first', 'ZREVRANGE' => 'static::first', 'ZRANGEBYSCORE' => 'static::first', 'ZCARD' => 'static::first', 'ZSCORE' => 'static::first', 'ZREMRANGEBYSCORE' => 'static::first', 'SETEX' => 'static::first', 'APPEND' => 'static::first', 'SUBSTR' => 'static::first', 'BLPOP' => 'static::skipLast', 'BRPOP' => 'static::skipLast', 'ZUNIONSTORE' => 'static::zsetStore', 'ZINTERSTORE' => 'static::zsetStore', 'ZCOUNT' => 'static::first', 'ZRANK' => 'static::first', 'ZREVRANK' => 'static::first', 'ZREMRANGEBYRANK' => 'static::first', 'HSET' => 'static::first', 'HSETNX' => 'static::first', 'HMSET' => 'static::first', 'HINCRBY' => 'static::first', 'HGET' => 'static::first', 'HMGET' => 'static::first', 'HDEL' => 'static::first', 'HEXISTS' => 'static::first', 'HLEN' => 'static::first', 'HKEYS' => 'static::first', 'HVALS' => 'static::first', 'HGETALL' => 'static::first', 'SUBSCRIBE' => 'static::all', 'UNSUBSCRIBE' => 'static::all', 'PSUBSCRIBE' => 'static::all', 'PUNSUBSCRIBE' => 'static::all', 'PUBLISH' => 'static::first', 'PERSIST' => 'static::first', 'STRLEN' => 'static::first', 'SETRANGE' => 'static::first', 'GETRANGE' => 'static::first', 'SETBIT' => 'static::first', 'GETBIT' => 'static::first', 'RPUSHX' => 'static::first', 'LPUSHX' => 'static::first', 'LINSERT' => 'static::first', 'BRPOPLPUSH' => 'static::skipLast', 'ZREVRANGEBYSCORE' => 'static::first', 'WATCH' => 'static::all', 'PTTL' => 'static::first', 'PEXPIRE' => 'static::first', 'PEXPIREAT' => 'static::first', 'PSETEX' => 'static::first', 'INCRBYFLOAT' => 'static::first', 'BITOP' => 'static::skipFirst', 'BITCOUNT' => 'static::first', 'HINCRBYFLOAT' => 'static::first', 'EVAL' => 'static::evalKeys', 'EVALSHA' => 'static::evalKeys', 'MIGRATE' => 'static::migrate', 'SSCAN' => 'static::first', 'ZSCAN' => 'static::first', 'HSCAN' => 'static::first', 'PFADD' => 'static::first', 'PFCOUNT' => 'static::all', 'PFMERGE' => 'static::all', 'ZLEXCOUNT' => 'static::first', 'ZRANGEBYLEX' => 'static::first', 'ZREMRANGEBYLEX' => 'static::first', 'ZREVRANGEBYLEX' => 'static::first', 'BITPOS' => 'static::first', 'HSTRLEN' => 'static::first', 'BITFIELD' => 'static::first', 'GEOADD' => 'static::first', 'GEOHASH' => 'static::first', 'GEOPOS' => 'static::first', 'GEODIST' => 'static::first', 'GEORADIUS' => 'static::georadius', 'GEORADIUSBYMEMBER' => 'static::georadius', ); } public function setPrefix($prefix) { $this->prefix = $prefix; } public function getPrefix() { return $this->prefix; } public function process(CommandInterface $command) { if ($command instanceof PrefixableCommandInterface) { $command->prefixKeys($this->prefix); } elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) { call_user_func($this->commands[$commandID], $command, $this->prefix); } } public function setCommandHandler($commandID, $callback = null) { $commandID = strtoupper($commandID); if (!isset($callback)) { unset($this->commands[$commandID]); return; } if (!is_callable($callback)) { throw new \InvalidArgumentException( 'Callback must be a valid callable object or NULL' ); } $this->commands[$commandID] = $callback; } public function __toString() { return $this->getPrefix(); } public static function first(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; $command->setRawArguments($arguments); } } public static function all(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { foreach ($arguments as &$key) { $key = "$prefix$key"; } $command->setRawArguments($arguments); } } public static function interleaved(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $length = count($arguments); for ($i = 0; $i < $length; $i += 2) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function skipFirst(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $length = count($arguments); for ($i = 1; $i < $length; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function skipLast(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $length = count($arguments); for ($i = 0; $i < $length - 1; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function sort(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; if (($count = count($arguments)) > 1) { for ($i = 1; $i < $count; ++$i) { switch (strtoupper($arguments[$i])) { case 'BY': case 'STORE': $arguments[$i] = "$prefix{$arguments[++$i]}"; break; case 'GET': $value = $arguments[++$i]; if ($value !== '#') { $arguments[$i] = "$prefix$value"; } break; case 'LIMIT'; $i += 2; break; } } } $command->setRawArguments($arguments); } } public static function evalKeys(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { for ($i = 2; $i < $arguments[1] + 2; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function zsetStore(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; $length = ((int) $arguments[1]) + 2; for ($i = 2; $i < $length; ++$i) { $arguments[$i] = "$prefix{$arguments[$i]}"; } $command->setRawArguments($arguments); } } public static function migrate(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[2] = "$prefix{$arguments[2]}"; $command->setRawArguments($arguments); } } public static function georadius(CommandInterface $command, $prefix) { if ($arguments = $command->getArguments()) { $arguments[0] = "$prefix{$arguments[0]}"; $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; if (($count = count($arguments)) > $startIndex) { for ($i = $startIndex; $i < $count; ++$i) { switch (strtoupper($arguments[$i])) { case 'STORE': case 'STOREDIST': $arguments[$i] = "$prefix{$arguments[++$i]}"; break; } } } $command->setRawArguments($arguments); } } } getArgument(0))) { case 'numsub': return self::processNumsub($data); default: return $data; } } protected static function processNumsub(array $channels) { $processed = array(); $count = count($channels); for ($i = 0; $i < $count; ++$i) { $processed[$channels[$i]] = $channels[++$i]; } return $processed; } } $v) { $flattenedKVs[] = $k; $flattenedKVs[] = $v; } return $flattenedKVs; } return $arguments; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } public function parseResponse($data) { if (is_array($data)) { $fields = $data[1]; $result = array(); for ($i = 0; $i < count($fields); ++$i) { $result[$fields[$i]] = $fields[++$i]; } $data[1] = $result; } return $data; } } prepareOptions(array_pop($arguments)); $arguments = array_merge($arguments, $options); } return $arguments; } protected function prepareOptions($options) { $options = array_change_key_case($options, CASE_UPPER); $normalized = array(); if (!empty($options['MATCH'])) { $normalized[] = 'MATCH'; $normalized[] = $options['MATCH']; } if (!empty($options['COUNT'])) { $normalized[] = 'COUNT'; $normalized[] = $options['COUNT']; } return $normalized; } } getArgument(0); } } parseRow($row); $info[$k] = $v; } return $info; } protected function parseRow($row) { list($k, $v) = explode(':', $row, 2); if (preg_match('/^db\d+$/', $k)) { $v = $this->parseDatabaseStats($v); } return array($k, $v); } protected function parseDatabaseStats($str) { $db = array(); foreach (explode(',', $str) as $dbvar) { list($dbvk, $dbvv) = explode('=', $dbvar); $db[trim($dbvk)] = $dbvv; } return $db; } protected function parseAllocationStats($str) { $stats = array(); foreach (explode(',', $str) as $kv) { @list($size, $objects, $extra) = explode('=', $kv); if (isset($extra)) { $size = ">=$objects"; $objects = $extra; } $stats[$size] = $objects; } return $stats; } } arguments = $this->filterArguments($arguments); unset($this->slot); } public function setRawArguments(array $arguments) { $this->arguments = $arguments; unset($this->slot); } public function getArguments() { return $this->arguments; } public function getArgument($index) { if (isset($this->arguments[$index])) { return $this->arguments[$index]; } } public function setSlot($slot) { $this->slot = $slot; } public function getSlot() { if (isset($this->slot)) { return $this->slot; } } public function parseResponse($data) { return $data; } public static function normalizeArguments(array $arguments) { if (count($arguments) === 1 && is_array($arguments[0])) { return $arguments[0]; } return $arguments; } public static function normalizeVariadic(array $arguments) { if (count($arguments) === 2 && is_array($arguments[1])) { return array_merge(array($arguments[0]), $arguments[1]); } return $arguments; } } getArguments(), 2, $this->getKeysCount()); } protected function filterArguments(array $arguments) { if (($numkeys = $this->getKeysCount()) && $numkeys < 0) { $numkeys = count($arguments) + $numkeys; } return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments); } public function getEvalArguments() { $arguments = $this->getArguments(); $arguments[0] = $this->getScript(); return $arguments; } } getArguments(); for ($i = 3; $i < count($arguments); ++$i) { switch (strtoupper($arguments[$i])) { case 'WITHSCORES': return true; case 'LIMIT': $i += 2; break; } } return false; } } connections); default: return; } } public function filter(OptionsInterface $options, $value) { if (is_string($value)) { $value = $this->createByDescription($options, $value); } if (!$value instanceof ClusterInterface) { throw new \InvalidArgumentException( "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected." ); } return $value; } public function getDefault(OptionsInterface $options) { return new PredisCluster(); } } prefix) && $profile instanceof RedisProfile) { $profile->setProcessor($options->__get('prefix')); } } public function filter(OptionsInterface $options, $value) { if (is_string($value)) { $value = Factory::get($value); $this->setProcessors($options, $value); } elseif (!$value instanceof ProfileInterface) { throw new \InvalidArgumentException('Invalid value for the profile option.'); } return $value; } public function getDefault(OptionsInterface $options) { $profile = Factory::getDefault(); $this->setProcessors($options, $profile); return $profile; } } getDefault($options); foreach ($value as $scheme => $initializer) { $factory->define($scheme, $initializer); } return $factory; } else { throw new \InvalidArgumentException( 'Invalid value provided for the connections option.' ); } } public function getDefault(OptionsInterface $options) { $factory = new Factory(); if ($options->defined('parameters')) { $factory->setDefaultParameters($options->parameters); } return $factory; } } getDefault($options) : null; } if ($value === 'sentinel') { return function ($sentinels, $options) { return new SentinelReplication($options->service, $sentinels, $options->connections); }; } if ( !is_object($value) && null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ) { return $asbool ? $this->getDefault($options) : null; } throw new \InvalidArgumentException( "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected." ); } public function getDefault(OptionsInterface $options) { $replication = new MasterSlaveReplication(); if ($options->autodiscovery) { $replication->setConnectionFactory($options->connections); $replication->setAutoDiscovery(true); } return $replication; } } input = $options; $this->options = array(); $this->handlers = $this->getHandlers(); } protected function getHandlers() { return array( 'cluster' => 'Predis\Configuration\ClusterOption', 'connections' => 'Predis\Configuration\ConnectionFactoryOption', 'exceptions' => 'Predis\Configuration\ExceptionsOption', 'prefix' => 'Predis\Configuration\PrefixOption', 'profile' => 'Predis\Configuration\ProfileOption', 'replication' => 'Predis\Configuration\ReplicationOption', ); } public function getDefault($option) { if (isset($this->handlers[$option])) { $handler = $this->handlers[$option]; $handler = new $handler(); return $handler->getDefault($this); } } public function defined($option) { return array_key_exists($option, $this->options) || array_key_exists($option, $this->input) ; } public function __isset($option) { return ( array_key_exists($option, $this->options) || array_key_exists($option, $this->input) ) && $this->__get($option) !== null; } public function __get($option) { if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { return $this->options[$option]; } if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { $value = $this->input[$option]; unset($this->input[$option]); if (is_object($value) && method_exists($value, '__invoke')) { $value = $value($this, $option); } if (isset($this->handlers[$option])) { $handler = $this->handlers[$option]; $handler = new $handler(); $value = $handler->filter($this, $value); } return $this->options[$option] = $value; } if (isset($this->handlers[$option])) { return $this->options[$option] = $this->getDefault($option); } return; } } isEmpty()) { $connection->writeRequest($commands->dequeue()); } $connection->disconnect(); return array(); } } getClient()->getConnection(); } protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) { if ($connection instanceof NodeConnectionInterface) { return $this->executeSingleNode($connection, $commands); } elseif ($connection instanceof ClusterInterface) { return $this->executeCluster($connection, $commands); } else { $class = get_class($connection); throw new NotSupportedException("The connection class '$class' is not supported."); } } protected function executeSingleNode(NodeConnectionInterface $connection, \SplQueue $commands) { $responses = array(); $sizeOfPipe = count($commands); foreach ($commands as $command) { try { $connection->writeRequest($command); } catch (CommunicationException $exception) { return array_fill(0, $sizeOfPipe, $exception); } } for ($i = 0; $i < $sizeOfPipe; ++$i) { $command = $commands->dequeue(); try { $responses[$i] = $connection->readResponse($command); } catch (CommunicationException $exception) { $add = count($commands) - count($responses); $responses = array_merge($responses, array_fill(0, $add, $exception)); break; } } return $responses; } protected function executeCluster(ClusterInterface $connection, \SplQueue $commands) { $responses = array(); $sizeOfPipe = count($commands); $exceptions = array(); foreach ($commands as $command) { $cmdConnection = $connection->getConnection($command); if (isset($exceptions[spl_object_hash($cmdConnection)])) { continue; } try { $cmdConnection->writeRequest($command); } catch (CommunicationException $exception) { $exceptions[spl_object_hash($cmdConnection)] = $exception; } } for ($i = 0; $i < $sizeOfPipe; ++$i) { $command = $commands->dequeue(); $cmdConnection = $connection->getConnection($command); $connectionHash = spl_object_hash($cmdConnection); if (isset($exceptions[$connectionHash])) { $responses[$i] = $exceptions[$connectionHash]; continue; } try { $responses[$i] = $cmdConnection->readResponse($command); } catch (CommunicationException $exception) { $responses[$i] = $exception; $exceptions[$connectionHash] = $exception; } } return $responses; } } client = $client; $this->pipeline = new \SplQueue(); } public function __call($method, $arguments) { $command = $this->client->createCommand($method, $arguments); $this->recordCommand($command); return $this; } protected function recordCommand(CommandInterface $command) { $this->pipeline->enqueue($command); } public function executeCommand(CommandInterface $command) { $this->recordCommand($command); return $this; } protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response) { $connection->disconnect(); $message = $response->getMessage(); throw new ServerException($message); } protected function getConnection() { $connection = $this->getClient()->getConnection(); if ($connection instanceof ReplicationInterface) { $connection->switchTo('master'); } return $connection; } protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) { foreach ($commands as $command) { $connection->writeRequest($command); } $responses = array(); $exceptions = $this->throwServerExceptions(); while (!$commands->isEmpty()) { $command = $commands->dequeue(); $response = $connection->readResponse($command); if (!$response instanceof ResponseInterface) { $responses[] = $command->parseResponse($response); } elseif ($response instanceof ErrorResponseInterface && $exceptions) { $this->exception($connection, $response); } else { $responses[] = $response; } } return $responses; } public function flushPipeline($send = true) { if ($send && !$this->pipeline->isEmpty()) { $responses = $this->executePipeline($this->getConnection(), $this->pipeline); $this->responses = array_merge($this->responses, $responses); } else { $this->pipeline = new \SplQueue(); } return $this; } private function setRunning($bool) { if ($bool && $this->running) { throw new ClientException('The current pipeline context is already being executed.'); } $this->running = $bool; } public function execute($callable = null) { if ($callable && !is_callable($callable)) { throw new \InvalidArgumentException('The argument must be a callable object.'); } $exception = null; $this->setRunning(true); try { if ($callable) { call_user_func($callable, $this); } $this->flushPipeline(); } catch (\Exception $exception) { } $this->setRunning(false); if ($exception) { throw $exception; } return $this->responses; } protected function throwServerExceptions() { return (bool) $this->client->getOptions()->exceptions; } public function getClient() { return $this->client; } } getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) { throw new ClientException( "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'." ); } parent::__construct($client); } protected function getConnection() { $connection = $this->getClient()->getConnection(); if (!$connection instanceof NodeConnectionInterface) { $class = __CLASS__; throw new ClientException("The class '$class' does not support aggregate connections."); } return $connection; } protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) { $profile = $this->getClient()->getProfile(); $connection->executeCommand($profile->createCommand('multi')); foreach ($commands as $command) { $connection->writeRequest($command); } foreach ($commands as $command) { $response = $connection->readResponse($command); if ($response instanceof ErrorResponseInterface) { $connection->executeCommand($profile->createCommand('discard')); throw new ServerException($response->getMessage()); } } $executed = $connection->executeCommand($profile->createCommand('exec')); if (!isset($executed)) { throw new ClientException( 'The underlying transaction has been aborted by the server.' ); } if (count($executed) !== count($commands)) { $expected = count($commands); $received = count($executed); throw new ClientException( "Invalid number of responses [expected $expected, received $received]." ); } $responses = array(); $sizeOfPipe = count($commands); $exceptions = $this->throwServerExceptions(); for ($i = 0; $i < $sizeOfPipe; ++$i) { $command = $commands->dequeue(); $response = $executed[$i]; if (!$response instanceof ResponseInterface) { $responses[] = $command->parseResponse($response); } elseif ($response instanceof ErrorResponseInterface && $exceptions) { $this->exception($connection, $response); } else { $responses[] = $response; } unset($executed[$i]); } return $responses; } } assertExtensions(); if ($parameters->scheme !== 'http') { throw new \InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'."); } $this->parameters = $parameters; $this->resource = $this->createCurl(); $this->reader = $this->createReader(); } public function __destruct() { curl_close($this->resource); phpiredis_reader_destroy($this->reader); } private function throwNotSupportedException($method) { $class = __CLASS__; throw new NotSupportedException("The method $class::$method() is not supported."); } private function assertExtensions() { if (!extension_loaded('curl')) { throw new NotSupportedException( 'The "curl" extension is required by this connection backend.' ); } if (!extension_loaded('phpiredis')) { throw new NotSupportedException( 'The "phpiredis" extension is required by this connection backend.' ); } } private function createCurl() { $parameters = $this->getParameters(); $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0) * 1000; if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { $host = "[$host]"; } $options = array( CURLOPT_FAILONERROR => true, CURLOPT_CONNECTTIMEOUT_MS => $timeout, CURLOPT_URL => "$parameters->scheme://$host:$parameters->port", CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_POST => true, CURLOPT_WRITEFUNCTION => array($this, 'feedReader'), ); if (isset($parameters->user, $parameters->pass)) { $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}"; } curl_setopt_array($resource = curl_init(), $options); return $resource; } private function createReader() { $reader = phpiredis_reader_create(); phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); return $reader; } protected function getStatusHandler() { static $statusHandler; if (!$statusHandler) { $statusHandler = function ($payload) { return StatusResponse::get($payload); }; } return $statusHandler; } protected function getErrorHandler() { static $errorHandler; if (!$errorHandler) { $errorHandler = function ($errorMessage) { return new ErrorResponse($errorMessage); }; } return $errorHandler; } protected function feedReader($resource, $buffer) { phpiredis_reader_feed($this->reader, $buffer); return strlen($buffer); } public function connect() { } public function disconnect() { } public function isConnected() { return true; } protected function getCommandId(CommandInterface $command) { switch ($commandID = $command->getId()) { case 'AUTH': case 'SELECT': case 'MULTI': case 'EXEC': case 'WATCH': case 'UNWATCH': case 'DISCARD': case 'MONITOR': throw new NotSupportedException("Command '$commandID' is not allowed by Webdis."); default: return $commandID; } } public function writeRequest(CommandInterface $command) { $this->throwNotSupportedException(__FUNCTION__); } public function readResponse(CommandInterface $command) { $this->throwNotSupportedException(__FUNCTION__); } public function executeCommand(CommandInterface $command) { $resource = $this->resource; $commandId = $this->getCommandId($command); if ($arguments = $command->getArguments()) { $arguments = implode('/', array_map('urlencode', $arguments)); $serializedCommand = "$commandId/$arguments.raw"; } else { $serializedCommand = "$commandId.raw"; } curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand); if (curl_exec($resource) === false) { $error = curl_error($resource); $errno = curl_errno($resource); throw new ConnectionException($this, trim($error), $errno); } if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) { throw new ProtocolException($this, phpiredis_reader_get_error($this->reader)); } return phpiredis_reader_get_reply($this->reader); } public function getResource() { return $this->resource; } public function getParameters() { return $this->parameters; } public function addConnectCommand(CommandInterface $command) { $this->throwNotSupportedException(__FUNCTION__); } public function read() { $this->throwNotSupportedException(__FUNCTION__); } public function __toString() { return "{$this->parameters->host}:{$this->parameters->port}"; } public function __sleep() { return array('parameters'); } public function __wakeup() { $this->assertExtensions(); $this->resource = $this->createCurl(); $this->reader = $this->createReader(); } } 'Predis\Connection\StreamConnection', 'unix' => 'Predis\Connection\StreamConnection', 'tls' => 'Predis\Connection\StreamConnection', 'redis' => 'Predis\Connection\StreamConnection', 'rediss' => 'Predis\Connection\StreamConnection', 'http' => 'Predis\Connection\WebdisConnection', ); protected function checkInitializer($initializer) { if (is_callable($initializer)) { return $initializer; } $class = new \ReflectionClass($initializer); if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) { throw new \InvalidArgumentException( 'A connection initializer must be a valid connection class or a callable object.' ); } return $initializer; } public function define($scheme, $initializer) { $this->schemes[$scheme] = $this->checkInitializer($initializer); } public function undefine($scheme) { unset($this->schemes[$scheme]); } public function create($parameters) { if (!$parameters instanceof ParametersInterface) { $parameters = $this->createParameters($parameters); } $scheme = $parameters->scheme; if (!isset($this->schemes[$scheme])) { throw new \InvalidArgumentException("Unknown connection scheme: '$scheme'."); } $initializer = $this->schemes[$scheme]; if (is_callable($initializer)) { $connection = call_user_func($initializer, $parameters, $this); } else { $connection = new $initializer($parameters); $this->prepareConnection($connection); } if (!$connection instanceof NodeConnectionInterface) { throw new \UnexpectedValueException( 'Objects returned by connection initializers must implement '. "'Predis\Connection\NodeConnectionInterface'." ); } return $connection; } public function aggregate(AggregateConnectionInterface $connection, array $parameters) { foreach ($parameters as $node) { $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node)); } } public function setDefaultParameters(array $parameters) { $this->defaults = $parameters; } public function getDefaultParameters() { return $this->defaults; } protected function createParameters($parameters) { if (is_string($parameters)) { $parameters = Parameters::parse($parameters); } else { $parameters = $parameters ?: array(); } if ($this->defaults) { $parameters += $this->defaults; } return new Parameters($parameters); } protected function prepareConnection(NodeConnectionInterface $connection) { $parameters = $connection->getParameters(); if (isset($parameters->password)) { $connection->addConnectCommand( new RawCommand(array('AUTH', $parameters->password)) ); } if (isset($parameters->database)) { $connection->addConnectCommand( new RawCommand(array('SELECT', $parameters->database)) ); } } } connections = $connections; $this->strategy = $strategy ?: new RedisClusterStrategy(); } public function setRetryLimit($retry) { $this->retryLimit = (int) $retry; } public function isConnected() { foreach ($this->pool as $connection) { if ($connection->isConnected()) { return true; } } return false; } public function connect() { if ($connection = $this->getRandomConnection()) { $connection->connect(); } } public function disconnect() { foreach ($this->pool as $connection) { $connection->disconnect(); } } public function add(NodeConnectionInterface $connection) { $this->pool[(string) $connection] = $connection; unset($this->slotsMap); } public function remove(NodeConnectionInterface $connection) { if (false !== $id = array_search($connection, $this->pool, true)) { unset( $this->pool[$id], $this->slotsMap ); $this->slots = array_diff($this->slots, array($connection)); return true; } return false; } public function removeById($connectionID) { if (isset($this->pool[$connectionID])) { unset( $this->pool[$connectionID], $this->slotsMap ); return true; } return false; } public function buildSlotsMap() { $this->slotsMap = array(); foreach ($this->pool as $connectionID => $connection) { $parameters = $connection->getParameters(); if (!isset($parameters->slots)) { continue; } foreach (explode(',', $parameters->slots) as $slotRange) { $slots = explode('-', $slotRange, 2); if (!isset($slots[1])) { $slots[1] = $slots[0]; } $this->setSlots($slots[0], $slots[1], $connectionID); } } return $this->slotsMap; } private function queryClusterNodeForSlotsMap(NodeConnectionInterface $connection) { $retries = 0; $command = RawCommand::create('CLUSTER', 'SLOTS'); RETRY_COMMAND: { try { $response = $connection->executeCommand($command); } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); $this->remove($connection); if ($retries === $this->retryLimit) { throw $exception; } if (!$connection = $this->getRandomConnection()) { throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`'); } ++$retries; goto RETRY_COMMAND; } } return $response; } public function askSlotsMap(NodeConnectionInterface $connection = null) { if (!$connection && !$connection = $this->getRandomConnection()) { return array(); } $this->resetSlotsMap(); $response = $this->queryClusterNodeForSlotsMap($connection); foreach ($response as $slots) { list($start, $end, $master) = $slots; if ($master[0] === '') { $this->setSlots($start, $end, (string) $connection); } else { $this->setSlots($start, $end, "{$master[0]}:{$master[1]}"); } } return $this->slotsMap; } public function resetSlotsMap() { $this->slotsMap = array(); } public function getSlotsMap() { if (!isset($this->slotsMap)) { $this->slotsMap = array(); } return $this->slotsMap; } public function setSlots($first, $last, $connection) { if ($first < 0x0000 || $first > 0x3FFF || $last < 0x0000 || $last > 0x3FFF || $last < $first ) { throw new \OutOfBoundsException( "Invalid slot range for $connection: [$first-$last]." ); } $slots = array_fill($first, $last - $first + 1, (string) $connection); $this->slotsMap = $this->getSlotsMap() + $slots; } protected function guessNode($slot) { if (!$this->pool) { throw new ClientException('No connections available in the pool'); } if (!isset($this->slotsMap)) { $this->buildSlotsMap(); } if (isset($this->slotsMap[$slot])) { return $this->slotsMap[$slot]; } $count = count($this->pool); $index = min((int) ($slot / (int) (16384 / $count)), $count - 1); $nodes = array_keys($this->pool); return $nodes[$index]; } protected function createConnection($connectionID) { $separator = strrpos($connectionID, ':'); return $this->connections->create(array( 'host' => substr($connectionID, 0, $separator), 'port' => substr($connectionID, $separator + 1), )); } public function getConnection(CommandInterface $command) { $slot = $this->strategy->getSlot($command); if (!isset($slot)) { throw new NotSupportedException( "Cannot use '{$command->getId()}' with redis-cluster." ); } if (isset($this->slots[$slot])) { return $this->slots[$slot]; } else { return $this->getConnectionBySlot($slot); } } public function getConnectionBySlot($slot) { if ($slot < 0x0000 || $slot > 0x3FFF) { throw new \OutOfBoundsException("Invalid slot [$slot]."); } if (isset($this->slots[$slot])) { return $this->slots[$slot]; } $connectionID = $this->guessNode($slot); if (!$connection = $this->getConnectionById($connectionID)) { $connection = $this->createConnection($connectionID); $this->pool[$connectionID] = $connection; } return $this->slots[$slot] = $connection; } public function getConnectionById($connectionID) { if (isset($this->pool[$connectionID])) { return $this->pool[$connectionID]; } } protected function getRandomConnection() { if ($this->pool) { return $this->pool[array_rand($this->pool)]; } } protected function move(NodeConnectionInterface $connection, $slot) { $this->pool[(string) $connection] = $connection; $this->slots[(int) $slot] = $connection; } protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error) { $details = explode(' ', $error->getMessage(), 2); switch ($details[0]) { case 'MOVED': return $this->onMovedResponse($command, $details[1]); case 'ASK': return $this->onAskResponse($command, $details[1]); default: return $error; } } protected function onMovedResponse(CommandInterface $command, $details) { list($slot, $connectionID) = explode(' ', $details, 2); if (!$connection = $this->getConnectionById($connectionID)) { $connection = $this->createConnection($connectionID); } if ($this->useClusterSlots) { $this->askSlotsMap($connection); } $this->move($connection, $slot); $response = $this->executeCommand($command); return $response; } protected function onAskResponse(CommandInterface $command, $details) { list($slot, $connectionID) = explode(' ', $details, 2); if (!$connection = $this->getConnectionById($connectionID)) { $connection = $this->createConnection($connectionID); } $connection->executeCommand(RawCommand::create('ASKING')); $response = $connection->executeCommand($command); return $response; } private function retryCommandOnFailure(CommandInterface $command, $method) { $failure = false; RETRY_COMMAND: { try { $response = $this->getConnection($command)->$method($command); } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); $this->remove($connection); if ($failure) { throw $exception; } elseif ($this->useClusterSlots) { $this->askSlotsMap(); } $failure = true; goto RETRY_COMMAND; } } return $response; } public function writeRequest(CommandInterface $command) { $this->retryCommandOnFailure($command, __FUNCTION__); } public function readResponse(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function executeCommand(CommandInterface $command) { $response = $this->retryCommandOnFailure($command, __FUNCTION__); if ($response instanceof ErrorResponseInterface) { return $this->onErrorResponse($command, $response); } return $response; } public function count() { return count($this->pool); } public function getIterator() { if ($this->useClusterSlots) { $slotsmap = $this->getSlotsMap() ?: $this->askSlotsMap(); } else { $slotsmap = $this->getSlotsMap() ?: $this->buildSlotsMap(); } $connections = array(); foreach (array_unique($slotsmap) as $node) { if (!$connection = $this->getConnectionById($node)) { $this->add($connection = $this->createConnection($node)); } $connections[] = $connection; } return new \ArrayIterator($connections); } public function getClusterStrategy() { return $this->strategy; } public function getConnectionFactory() { return $this->connections; } public function useClusterSlots($value) { $this->useClusterSlots = (bool) $value; } } sentinels = $sentinels; $this->service = $service; $this->connectionFactory = $connectionFactory; $this->strategy = $strategy ?: new ReplicationStrategy(); } public function setSentinelTimeout($timeout) { $this->sentinelTimeout = (float) $timeout; } public function setRetryLimit($retry) { $this->retryLimit = (int) $retry; } public function setRetryWait($seconds) { $this->retryWait = (float) $seconds; } public function setUpdateSentinels($update) { $this->updateSentinels = (bool) $update; } protected function reset() { $this->current = null; } protected function wipeServerList() { $this->reset(); $this->master = null; $this->slaves = array(); } public function add(NodeConnectionInterface $connection) { $alias = $connection->getParameters()->alias; if ($alias === 'master') { $this->master = $connection; } else { $this->slaves[$alias ?: count($this->slaves)] = $connection; } $this->reset(); } public function remove(NodeConnectionInterface $connection) { if ($connection === $this->master) { $this->master = null; $this->reset(); return true; } if (false !== $id = array_search($connection, $this->slaves, true)) { unset($this->slaves[$id]); $this->reset(); return true; } return false; } protected function createSentinelConnection($parameters) { if ($parameters instanceof NodeConnectionInterface) { return $parameters; } if (is_string($parameters)) { $parameters = Parameters::parse($parameters); } if (is_array($parameters)) { $parameters['database'] = null; $parameters['password'] = null; if (!isset($parameters['timeout'])) { $parameters['timeout'] = $this->sentinelTimeout; } } $connection = $this->connectionFactory->create($parameters); return $connection; } public function getSentinelConnection() { if (!$this->sentinelConnection) { if (!$this->sentinels) { throw new \Predis\ClientException('No sentinel server available for autodiscovery.'); } $sentinel = array_shift($this->sentinels); $this->sentinelConnection = $this->createSentinelConnection($sentinel); } return $this->sentinelConnection; } public function updateSentinels() { SENTINEL_QUERY: { $sentinel = $this->getSentinelConnection(); try { $payload = $sentinel->executeCommand( RawCommand::create('SENTINEL', 'sentinels', $this->service) ); $this->sentinels = array(); $this->sentinels[] = $sentinel->getParameters()->toArray(); foreach ($payload as $sentinel) { $this->sentinels[] = array( 'host' => $sentinel[3], 'port' => $sentinel[5], ); } } catch (ConnectionException $exception) { $this->sentinelConnection = null; goto SENTINEL_QUERY; } } } public function querySentinel() { $this->wipeServerList(); $this->updateSentinels(); $this->getMaster(); $this->getSlaves(); } private function handleSentinelErrorResponse(NodeConnectionInterface $sentinel, ErrorResponseInterface $error) { if ($error->getErrorType() === 'IDONTKNOW') { throw new ConnectionException($sentinel, $error->getMessage()); } else { throw new ServerException($error->getMessage()); } } protected function querySentinelForMaster(NodeConnectionInterface $sentinel, $service) { $payload = $sentinel->executeCommand( RawCommand::create('SENTINEL', 'get-master-addr-by-name', $service) ); if ($payload === null) { throw new ServerException('ERR No such master with that name'); } if ($payload instanceof ErrorResponseInterface) { $this->handleSentinelErrorResponse($sentinel, $payload); } return array( 'host' => $payload[0], 'port' => $payload[1], 'alias' => 'master', ); } protected function querySentinelForSlaves(NodeConnectionInterface $sentinel, $service) { $slaves = array(); $payload = $sentinel->executeCommand( RawCommand::create('SENTINEL', 'slaves', $service) ); if ($payload instanceof ErrorResponseInterface) { $this->handleSentinelErrorResponse($sentinel, $payload); } foreach ($payload as $slave) { $flags = explode(',', $slave[9]); if (array_intersect($flags, array('s_down', 'o_down', 'disconnected'))) { continue; } $slaves[] = array( 'host' => $slave[3], 'port' => $slave[5], 'alias' => "slave-$slave[1]", ); } return $slaves; } public function getCurrent() { return $this->current; } public function getMaster() { if ($this->master) { return $this->master; } if ($this->updateSentinels) { $this->updateSentinels(); } SENTINEL_QUERY: { $sentinel = $this->getSentinelConnection(); try { $masterParameters = $this->querySentinelForMaster($sentinel, $this->service); $masterConnection = $this->connectionFactory->create($masterParameters); $this->add($masterConnection); } catch (ConnectionException $exception) { $this->sentinelConnection = null; goto SENTINEL_QUERY; } } return $masterConnection; } public function getSlaves() { if ($this->slaves) { return array_values($this->slaves); } if ($this->updateSentinels) { $this->updateSentinels(); } SENTINEL_QUERY: { $sentinel = $this->getSentinelConnection(); try { $slavesParameters = $this->querySentinelForSlaves($sentinel, $this->service); foreach ($slavesParameters as $slaveParameters) { $this->add($this->connectionFactory->create($slaveParameters)); } } catch (ConnectionException $exception) { $this->sentinelConnection = null; goto SENTINEL_QUERY; } } return array_values($this->slaves ?: array()); } protected function pickSlave() { if ($slaves = $this->getSlaves()) { return $slaves[rand(1, count($slaves)) - 1]; } } private function getConnectionInternal(CommandInterface $command) { if (!$this->current) { if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { $this->current = $slave; } else { $this->current = $this->getMaster(); } return $this->current; } if ($this->current === $this->master) { return $this->current; } if (!$this->strategy->isReadOperation($command)) { $this->current = $this->getMaster(); } return $this->current; } protected function assertConnectionRole(NodeConnectionInterface $connection, $role) { $role = strtolower($role); $actualRole = $connection->executeCommand(RawCommand::create('ROLE')); if ($role !== $actualRole[0]) { throw new RoleException($connection, "Expected $role but got $actualRole[0] [$connection]"); } } public function getConnection(CommandInterface $command) { $connection = $this->getConnectionInternal($command); if (!$connection->isConnected()) { $expectedRole = $this->strategy->isReadOperation($command) && $this->slaves ? 'slave' : 'master'; $this->assertConnectionRole($connection, $expectedRole); } return $connection; } public function getConnectionById($connectionId) { if ($connectionId === 'master') { return $this->getMaster(); } $this->getSlaves(); if (isset($this->slaves[$connectionId])) { return $this->slaves[$connectionId]; } } public function switchTo($connection) { if (!$connection instanceof NodeConnectionInterface) { $connection = $this->getConnectionById($connection); } if ($connection && $connection === $this->current) { return; } if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { throw new \InvalidArgumentException('Invalid connection or connection not found.'); } $connection->connect(); if ($this->current) { $this->current->disconnect(); } $this->current = $connection; } public function switchToMaster() { $this->switchTo('master'); } public function switchToSlave() { $connection = $this->pickSlave(); $this->switchTo($connection); } public function isConnected() { return $this->current ? $this->current->isConnected() : false; } public function connect() { if (!$this->current) { if (!$this->current = $this->pickSlave()) { $this->current = $this->getMaster(); } } $this->current->connect(); } public function disconnect() { if ($this->master) { $this->master->disconnect(); } foreach ($this->slaves as $connection) { $connection->disconnect(); } } private function retryCommandOnFailure(CommandInterface $command, $method) { $retries = 0; SENTINEL_RETRY: { try { $response = $this->getConnection($command)->$method($command); } catch (CommunicationException $exception) { $this->wipeServerList(); $exception->getConnection()->disconnect(); if ($retries == $this->retryLimit) { throw $exception; } usleep($this->retryWait * 1000); ++$retries; goto SENTINEL_RETRY; } } return $response; } public function writeRequest(CommandInterface $command) { $this->retryCommandOnFailure($command, __FUNCTION__); } public function readResponse(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function executeCommand(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function getReplicationStrategy() { return $this->strategy; } public function __sleep() { return array( 'master', 'slaves', 'service', 'sentinels', 'connectionFactory', 'strategy', ); } } strategy = $strategy ?: new ReplicationStrategy(); } public function setAutoDiscovery($value) { if (!$this->connectionFactory) { throw new ClientException('Automatic discovery requires a connection factory'); } $this->autoDiscovery = (bool) $value; } public function setConnectionFactory(FactoryInterface $connectionFactory) { $this->connectionFactory = $connectionFactory; } protected function reset() { $this->current = null; } public function add(NodeConnectionInterface $connection) { $alias = $connection->getParameters()->alias; if ($alias === 'master') { $this->master = $connection; } else { $this->slaves[$alias ?: "slave-$connection"] = $connection; } $this->reset(); } public function remove(NodeConnectionInterface $connection) { if ($connection->getParameters()->alias === 'master') { $this->master = null; $this->reset(); return true; } else { if (($id = array_search($connection, $this->slaves, true)) !== false) { unset($this->slaves[$id]); $this->reset(); return true; } } return false; } public function getConnection(CommandInterface $command) { if (!$this->current) { if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) { $this->current = $slave; } else { $this->current = $this->getMasterOrDie(); } return $this->current; } if ($this->current === $master = $this->getMasterOrDie()) { return $master; } if (!$this->strategy->isReadOperation($command) || !$this->slaves) { $this->current = $master; } return $this->current; } public function getConnectionById($connectionId) { if ($connectionId === 'master') { return $this->master; } if (isset($this->slaves[$connectionId])) { return $this->slaves[$connectionId]; } return; } public function switchTo($connection) { if (!$connection instanceof NodeConnectionInterface) { $connection = $this->getConnectionById($connection); } if (!$connection) { throw new \InvalidArgumentException('Invalid connection or connection not found.'); } if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { throw new \InvalidArgumentException('Invalid connection or connection not found.'); } $this->current = $connection; } public function switchToMaster() { $this->switchTo('master'); } public function switchToSlave() { $connection = $this->pickSlave(); $this->switchTo($connection); } public function getCurrent() { return $this->current; } public function getMaster() { return $this->master; } private function getMasterOrDie() { if (!$connection = $this->getMaster()) { throw new MissingMasterException('No master server available for replication'); } return $connection; } public function getSlaves() { return array_values($this->slaves); } public function getReplicationStrategy() { return $this->strategy; } protected function pickSlave() { if ($this->slaves) { return $this->slaves[array_rand($this->slaves)]; } } public function isConnected() { return $this->current ? $this->current->isConnected() : false; } public function connect() { if (!$this->current) { if (!$this->current = $this->pickSlave()) { if (!$this->current = $this->getMaster()) { throw new ClientException('No available connection for replication'); } } } $this->current->connect(); } public function disconnect() { if ($this->master) { $this->master->disconnect(); } foreach ($this->slaves as $connection) { $connection->disconnect(); } } private function handleInfoResponse($response) { $info = array(); foreach (preg_split('/\r?\n/', $response) as $row) { if (strpos($row, ':') === false) { continue; } list($k, $v) = explode(':', $row, 2); $info[$k] = $v; } return $info; } public function discover() { if (!$this->connectionFactory) { throw new ClientException('Discovery requires a connection factory'); } RETRY_FETCH: { try { if ($connection = $this->getMaster()) { $this->discoverFromMaster($connection, $this->connectionFactory); } elseif ($connection = $this->pickSlave()) { $this->discoverFromSlave($connection, $this->connectionFactory); } else { throw new ClientException('No connection available for discovery'); } } catch (ConnectionException $exception) { $this->remove($connection); goto RETRY_FETCH; } } } protected function discoverFromMaster(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) { $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); $replication = $this->handleInfoResponse($response); if ($replication['role'] !== 'master') { throw new ClientException("Role mismatch (expected master, got slave) [$connection]"); } $this->slaves = array(); foreach ($replication as $k => $v) { $parameters = null; if (strpos($k, 'slave') === 0 && preg_match('/ip=(?P.*),port=(?P\d+)/', $v, $parameters)) { $slaveConnection = $connectionFactory->create(array( 'host' => $parameters['host'], 'port' => $parameters['port'], )); $this->add($slaveConnection); } } } protected function discoverFromSlave(NodeConnectionInterface $connection, FactoryInterface $connectionFactory) { $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION')); $replication = $this->handleInfoResponse($response); if ($replication['role'] !== 'slave') { throw new ClientException("Role mismatch (expected slave, got master) [$connection]"); } $masterConnection = $connectionFactory->create(array( 'host' => $replication['master_host'], 'port' => $replication['master_port'], 'alias' => 'master', )); $this->add($masterConnection); $this->discoverFromMaster($masterConnection, $connectionFactory); } private function retryCommandOnFailure(CommandInterface $command, $method) { RETRY_COMMAND: { try { $connection = $this->getConnection($command); $response = $connection->$method($command); if ($response instanceof ResponseErrorInterface && $response->getErrorType() === 'LOADING') { throw new ConnectionException($connection, "Redis is loading the dataset in memory [$connection]"); } } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); if ($connection === $this->master && !$this->autoDiscovery) { throw $exception; } else { $this->remove($connection); } if (!$this->slaves && !$this->master) { throw $exception; } elseif ($this->autoDiscovery) { $this->discover(); } goto RETRY_COMMAND; } catch (MissingMasterException $exception) { if ($this->autoDiscovery) { $this->discover(); } else { throw $exception; } goto RETRY_COMMAND; } } return $response; } public function writeRequest(CommandInterface $command) { $this->retryCommandOnFailure($command, __FUNCTION__); } public function readResponse(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function executeCommand(CommandInterface $command) { return $this->retryCommandOnFailure($command, __FUNCTION__); } public function __sleep() { return array('master', 'slaves', 'strategy'); } } pool = array(); $this->strategy = $strategy ?: new PredisStrategy(); $this->distributor = $this->strategy->getDistributor(); } public function isConnected() { foreach ($this->pool as $connection) { if ($connection->isConnected()) { return true; } } return false; } public function connect() { foreach ($this->pool as $connection) { $connection->connect(); } } public function disconnect() { foreach ($this->pool as $connection) { $connection->disconnect(); } } public function add(NodeConnectionInterface $connection) { $parameters = $connection->getParameters(); if (isset($parameters->alias)) { $this->pool[$parameters->alias] = $connection; } else { $this->pool[] = $connection; } $weight = isset($parameters->weight) ? $parameters->weight : null; $this->distributor->add($connection, $weight); } public function remove(NodeConnectionInterface $connection) { if (($id = array_search($connection, $this->pool, true)) !== false) { unset($this->pool[$id]); $this->distributor->remove($connection); return true; } return false; } public function removeById($connectionID) { if ($connection = $this->getConnectionById($connectionID)) { return $this->remove($connection); } return false; } public function getConnection(CommandInterface $command) { $slot = $this->strategy->getSlot($command); if (!isset($slot)) { throw new NotSupportedException( "Cannot use '{$command->getId()}' over clusters of connections." ); } $node = $this->distributor->getBySlot($slot); return $node; } public function getConnectionById($connectionID) { return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null; } public function getConnectionByKey($key) { $hash = $this->strategy->getSlotByKey($key); $node = $this->distributor->getBySlot($hash); return $node; } public function getClusterStrategy() { return $this->strategy; } public function count() { return count($this->pool); } public function getIterator() { return new \ArrayIterator($this->pool); } public function writeRequest(CommandInterface $command) { $this->getConnection($command)->writeRequest($command); } public function readResponse(CommandInterface $command) { return $this->getConnection($command)->readResponse($command); } public function executeCommand(CommandInterface $command) { return $this->getConnection($command)->executeCommand($command); } public function executeCommandOnNodes(CommandInterface $command) { $responses = array(); foreach ($this->pool as $connection) { $responses[] = $connection->executeCommand($command); } return $responses; } } parameters->persistent) && $this->parameters->persistent) { return; } $this->disconnect(); } protected function assertParameters(ParametersInterface $parameters) { switch ($parameters->scheme) { case 'tcp': case 'redis': case 'unix': break; case 'tls': case 'rediss': $this->assertSslSupport($parameters); break; default: throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); } return $parameters; } protected function assertSslSupport(ParametersInterface $parameters) { if ( filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN) && version_compare(PHP_VERSION, '7.0.0beta') < 0 ) { throw new \InvalidArgumentException('Persistent SSL connections require PHP >= 7.0.0.'); } } protected function createResource() { switch ($this->parameters->scheme) { case 'tcp': case 'redis': return $this->tcpStreamInitializer($this->parameters); case 'unix': return $this->unixStreamInitializer($this->parameters); case 'tls': case 'rediss': return $this->tlsStreamInitializer($this->parameters); default: throw new \InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'."); } } protected function createStreamSocket(ParametersInterface $parameters, $address, $flags) { $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags)) { $this->onConnectionError(trim($errstr), $errno); } if (isset($parameters->read_write_timeout)) { $rwtimeout = (float) $parameters->read_write_timeout; $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; $timeoutSeconds = floor($rwtimeout); $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000; stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds); } if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { $socket = socket_import_stream($resource); socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); } return $resource; } protected function tcpStreamInitializer(ParametersInterface $parameters) { if (!filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $address = "tcp://$parameters->host:$parameters->port"; } else { $address = "tcp://[$parameters->host]:$parameters->port"; } $flags = STREAM_CLIENT_CONNECT; if (isset($parameters->async_connect) && $parameters->async_connect) { $flags |= STREAM_CLIENT_ASYNC_CONNECT; } if (isset($parameters->persistent)) { if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $flags |= STREAM_CLIENT_PERSISTENT; if ($persistent === null) { $address = "{$address}/{$parameters->persistent}"; } } } $resource = $this->createStreamSocket($parameters, $address, $flags); return $resource; } protected function unixStreamInitializer(ParametersInterface $parameters) { if (!isset($parameters->path)) { throw new \InvalidArgumentException('Missing UNIX domain socket path.'); } $flags = STREAM_CLIENT_CONNECT; if (isset($parameters->persistent)) { if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) { $flags |= STREAM_CLIENT_PERSISTENT; if ($persistent === null) { throw new \InvalidArgumentException( 'Persistent connection IDs are not supported when using UNIX domain sockets.' ); } } } $resource = $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags); return $resource; } protected function tlsStreamInitializer(ParametersInterface $parameters) { $resource = $this->tcpStreamInitializer($parameters); $metadata = stream_get_meta_data($resource); if (isset($metadata['crypto'])) { return $resource; } if (is_array($parameters->ssl)) { $options = $parameters->ssl; } else { $options = array(); } if (!isset($options['crypto_type'])) { $options['crypto_type'] = STREAM_CRYPTO_METHOD_TLS_CLIENT; } if (!stream_context_set_option($resource, array('ssl' => $options))) { $this->onConnectionError('Error while setting SSL context options'); } if (!stream_socket_enable_crypto($resource, true, $options['crypto_type'])) { $this->onConnectionError('Error while switching to encrypted communication'); } return $resource; } public function connect() { if (parent::connect() && $this->initCommands) { foreach ($this->initCommands as $command) { $response = $this->executeCommand($command); if ($response instanceof ErrorResponseInterface) { $this->onConnectionError("`{$command->getId()}` failed: $response", 0); } } } } public function disconnect() { if ($this->isConnected()) { fclose($this->getResource()); parent::disconnect(); } } protected function write($buffer) { $socket = $this->getResource(); while (($length = strlen($buffer)) > 0) { $written = @fwrite($socket, $buffer); if ($length === $written) { return; } if ($written === false || $written === 0) { $this->onConnectionError('Error while writing bytes to the server.'); } $buffer = substr($buffer, $written); } } public function read() { $socket = $this->getResource(); $chunk = fgets($socket); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading line from the server.'); } $prefix = $chunk[0]; $payload = substr($chunk, 1, -2); switch ($prefix) { case '+': return StatusResponse::get($payload); case '$': $size = (int) $payload; if ($size === -1) { return; } $bulkData = ''; $bytesLeft = ($size += 2); do { $chunk = fread($socket, min($bytesLeft, 4096)); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading bytes from the server.'); } $bulkData .= $chunk; $bytesLeft = $size - strlen($bulkData); } while ($bytesLeft > 0); return substr($bulkData, 0, -2); case '*': $count = (int) $payload; if ($count === -1) { return; } $multibulk = array(); for ($i = 0; $i < $count; ++$i) { $multibulk[$i] = $this->read(); } return $multibulk; case ':': $integer = (int) $payload; return $integer == $payload ? $integer : $payload; case '-': return new ErrorResponse($payload); default: $this->onProtocolError("Unknown response prefix: '$prefix'."); return; } } public function writeRequest(CommandInterface $command) { $commandID = $command->getId(); $arguments = $command->getArguments(); $cmdlen = strlen($commandID); $reqlen = count($arguments) + 1; $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; foreach ($arguments as $argument) { $arglen = strlen($argument); $buffer .= "\${$arglen}\r\n{$argument}\r\n"; } $this->write($buffer); } } assertExtensions(); parent::__construct($parameters); $this->reader = $this->createReader(); } public function __destruct() { phpiredis_reader_destroy($this->reader); parent::__destruct(); } private function assertExtensions() { if (!extension_loaded('phpiredis')) { throw new NotSupportedException( 'The "phpiredis" extension is required by this connection backend.' ); } } protected function assertSslSupport(ParametersInterface $parameters) { throw new \InvalidArgumentException('SSL encryption is not supported by this connection backend.'); } protected function createStreamSocket(ParametersInterface $parameters, $address, $flags, $context = null) { $socket = null; $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); $resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags); if (!$resource) { $this->onConnectionError(trim($errstr), $errno); } if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) { $rwtimeout = (float) $parameters->read_write_timeout; $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; $timeout = array( 'sec' => $timeoutSeconds = floor($rwtimeout), 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000, ); $socket = $socket ?: socket_import_stream($resource); @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout); @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout); } if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { $socket = $socket ?: socket_import_stream($resource); socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); } return $resource; } private function createReader() { $reader = phpiredis_reader_create(); phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); return $reader; } protected function getReader() { return $this->reader; } protected function getStatusHandler() { static $statusHandler; if (!$statusHandler) { $statusHandler = function ($payload) { return StatusResponse::get($payload); }; } return $statusHandler; } protected function getErrorHandler() { static $errorHandler; if (!$errorHandler) { $errorHandler = function ($errorMessage) { return new ErrorResponse($errorMessage); }; } return $errorHandler; } public function read() { $socket = $this->getResource(); $reader = $this->reader; while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { $buffer = stream_socket_recvfrom($socket, 4096); if ($buffer === false || $buffer === '') { $this->onConnectionError('Error while reading bytes from the server.'); } phpiredis_reader_feed($reader, $buffer); } if ($state === PHPIREDIS_READER_STATE_COMPLETE) { return phpiredis_reader_get_reply($reader); } else { $this->onProtocolError(phpiredis_reader_get_error($reader)); return; } } public function writeRequest(CommandInterface $command) { $arguments = $command->getArguments(); array_unshift($arguments, $command->getId()); $this->write(phpiredis_format_command($arguments)); } public function __wakeup() { $this->assertExtensions(); $this->reader = $this->createReader(); } } assertExtensions(); parent::__construct($parameters); $this->reader = $this->createReader(); } public function __destruct() { phpiredis_reader_destroy($this->reader); parent::__destruct(); } protected function assertExtensions() { if (!extension_loaded('sockets')) { throw new NotSupportedException( 'The "sockets" extension is required by this connection backend.' ); } if (!extension_loaded('phpiredis')) { throw new NotSupportedException( 'The "phpiredis" extension is required by this connection backend.' ); } } protected function assertParameters(ParametersInterface $parameters) { switch ($parameters->scheme) { case 'tcp': case 'redis': case 'unix': break; default: throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); } if (isset($parameters->persistent)) { throw new NotSupportedException( 'Persistent connections are not supported by this connection backend.' ); } return $parameters; } private function createReader() { $reader = phpiredis_reader_create(); phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); return $reader; } protected function getReader() { return $this->reader; } protected function getStatusHandler() { static $statusHandler; if (!$statusHandler) { $statusHandler = function ($payload) { return StatusResponse::get($payload); }; } return $statusHandler; } protected function getErrorHandler() { static $errorHandler; if (!$errorHandler) { $errorHandler = function ($errorMessage) { return new ErrorResponse($errorMessage); }; } return $errorHandler; } private function emitSocketError() { $errno = socket_last_error(); $errstr = socket_strerror($errno); $this->disconnect(); $this->onConnectionError(trim($errstr), $errno); } protected static function getAddress(ParametersInterface $parameters) { if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { return $host; } if ($host === $address = gethostbyname($host)) { return false; } return $address; } protected function createResource() { $parameters = $this->parameters; if ($parameters->scheme === 'unix') { $address = $parameters->path; $domain = AF_UNIX; $protocol = 0; } else { if (false === $address = self::getAddress($parameters)) { $this->onConnectionError("Cannot resolve the address of '$parameters->host'."); } $domain = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? AF_INET6 : AF_INET; $protocol = SOL_TCP; } $socket = @socket_create($domain, SOCK_STREAM, $protocol); if (!is_resource($socket)) { $this->emitSocketError(); } $this->setSocketOptions($socket, $parameters); $this->connectWithTimeout($socket, $address, $parameters); return $socket; } private function setSocketOptions($socket, ParametersInterface $parameters) { if ($parameters->scheme !== 'unix') { if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) { $this->emitSocketError(); } if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { $this->emitSocketError(); } } if (isset($parameters->read_write_timeout)) { $rwtimeout = (float) $parameters->read_write_timeout; $timeoutSec = floor($rwtimeout); $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000; $timeout = array( 'sec' => $timeoutSec, 'usec' => $timeoutUsec, ); if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) { $this->emitSocketError(); } if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) { $this->emitSocketError(); } } } private function connectWithTimeout($socket, $address, ParametersInterface $parameters) { socket_set_nonblock($socket); if (@socket_connect($socket, $address, (int) $parameters->port) === false) { $error = socket_last_error(); if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) { $this->emitSocketError(); } } socket_set_block($socket); $null = null; $selectable = array($socket); $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0); $timeoutSecs = floor($timeout); $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000; $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs); if ($selected === 2) { $this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED); } if ($selected === 0) { $this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT); } if ($selected === false) { $this->emitSocketError(); } } public function connect() { if (parent::connect() && $this->initCommands) { foreach ($this->initCommands as $command) { $response = $this->executeCommand($command); if ($response instanceof ErrorResponseInterface) { $this->onConnectionError("`{$command->getId()}` failed: $response", 0); } } } } public function disconnect() { if ($this->isConnected()) { socket_close($this->getResource()); parent::disconnect(); } } protected function write($buffer) { $socket = $this->getResource(); while (($length = strlen($buffer)) > 0) { $written = socket_write($socket, $buffer, $length); if ($length === $written) { return; } if ($written === false) { $this->onConnectionError('Error while writing bytes to the server.'); } $buffer = substr($buffer, $written); } } public function read() { $socket = $this->getResource(); $reader = $this->reader; while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '' || $buffer === null) { $this->emitSocketError(); } phpiredis_reader_feed($reader, $buffer); } if ($state === PHPIREDIS_READER_STATE_COMPLETE) { return phpiredis_reader_get_reply($reader); } else { $this->onProtocolError(phpiredis_reader_get_error($reader)); return; } } public function writeRequest(CommandInterface $command) { $arguments = $command->getArguments(); array_unshift($arguments, $command->getId()); $this->write(phpiredis_format_command($arguments)); } public function __wakeup() { $this->assertExtensions(); $this->reader = $this->createReader(); } } 'tcp', 'host' => '127.0.0.1', 'port' => 6379, ); public function __construct(array $parameters = array()) { $this->parameters = $this->filter($parameters) + $this->getDefaults(); } protected function getDefaults() { return self::$defaults; } public static function create($parameters) { if (is_string($parameters)) { $parameters = static::parse($parameters); } return new static($parameters ?: array()); } public static function parse($uri) { if (stripos($uri, 'unix://') === 0) { $uri = str_ireplace('unix://', 'unix:', $uri); } if (!$parsed = parse_url($uri)) { throw new \InvalidArgumentException("Invalid parameters URI: $uri"); } if ( isset($parsed['host']) && false !== strpos($parsed['host'], '[') && false !== strpos($parsed['host'], ']') ) { $parsed['host'] = substr($parsed['host'], 1, -1); } if (isset($parsed['query'])) { parse_str($parsed['query'], $queryarray); unset($parsed['query']); $parsed = array_merge($parsed, $queryarray); } if (stripos($uri, 'redis') === 0) { if (isset($parsed['pass'])) { $parsed['password'] = $parsed['pass']; unset($parsed['pass']); } if (isset($parsed['path']) && preg_match('/^\/(\d+)(\/.*)?/', $parsed['path'], $path)) { $parsed['database'] = $path[1]; if (isset($path[2])) { $parsed['path'] = $path[2]; } else { unset($parsed['path']); } } } return $parsed; } protected function filter(array $parameters) { return $parameters ?: array(); } public function __get($parameter) { if (isset($this->parameters[$parameter])) { return $this->parameters[$parameter]; } } public function __isset($parameter) { return isset($this->parameters[$parameter]); } public function toArray() { return $this->parameters; } public function __sleep() { return array('parameters'); } } parameters = $this->assertParameters($parameters); $this->protocol = $protocol ?: new TextProtocolProcessor(); } public function getProtocol() { return $this->protocol; } public function writeBuffer($buffer) { $this->write($buffer); } public function readBuffer($length) { if ($length <= 0) { throw new \InvalidArgumentException('Length parameter must be greater than 0.'); } $value = ''; $socket = $this->getResource(); do { $chunk = fread($socket, $length); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading bytes from the server.'); } $value .= $chunk; } while (($length -= strlen($chunk)) > 0); return $value; } public function readLine() { $value = ''; $socket = $this->getResource(); do { $chunk = fgets($socket); if ($chunk === false || $chunk === '') { $this->onConnectionError('Error while reading line from the server.'); } $value .= $chunk; } while (substr($value, -2) !== "\r\n"); return substr($value, 0, -2); } public function writeRequest(CommandInterface $command) { $this->protocol->write($this, $command); } public function read() { return $this->protocol->read($this); } public function __sleep() { return array_merge(parent::__sleep(), array('protocol')); } } parameters = $this->assertParameters($parameters); } public function __destruct() { $this->disconnect(); } abstract protected function assertParameters(ParametersInterface $parameters); abstract protected function createResource(); public function isConnected() { return isset($this->resource); } public function connect() { if (!$this->isConnected()) { $this->resource = $this->createResource(); return true; } return false; } public function disconnect() { unset($this->resource); } public function addConnectCommand(CommandInterface $command) { $this->initCommands[] = $command; } public function executeCommand(CommandInterface $command) { $this->writeRequest($command); return $this->readResponse($command); } public function readResponse(CommandInterface $command) { return $this->read(); } private function createExceptionMessage($message) { $parameters = $this->parameters; if ($parameters->scheme === 'unix') { return "$message [$parameters->scheme:$parameters->path]"; } if (filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return "$message [$parameters->scheme://[$parameters->host]:$parameters->port]"; } return "$message [$parameters->scheme://$parameters->host:$parameters->port]"; } protected function onConnectionError($message, $code = null) { CommunicationException::handle( new ConnectionException($this, static::createExceptionMessage($message), $code) ); } protected function onProtocolError($message) { CommunicationException::handle( new ProtocolException($this, static::createExceptionMessage($message)) ); } public function getResource() { if (isset($this->resource)) { return $this->resource; } $this->connect(); return $this->resource; } public function getParameters() { return $this->parameters; } protected function getIdentifier() { if ($this->parameters->scheme === 'unix') { return $this->parameters->path; } return "{$this->parameters->host}:{$this->parameters->port}"; } public function __toString() { if (!isset($this->cachedId)) { $this->cachedId = $this->getIdentifier(); } return $this->cachedId; } public function __sleep() { return array('parameters', 'initCommands'); } } nodes = array(); $this->nodesCount = 0; } public function add($node, $weight = null) { $this->nodes[] = $node; ++$this->nodesCount; } public function remove($node) { $this->nodes = array_filter($this->nodes, function ($n) use ($node) { return $n !== $node; }); $this->nodesCount = count($this->nodes); } public function getSlot($hash) { return $this->nodesCount > 1 ? abs($hash % $this->nodesCount) : 0; } public function getBySlot($slot) { return isset($this->nodes[$slot]) ? $this->nodes[$slot] : null; } public function getByHash($hash) { if (!$this->nodesCount) { throw new RuntimeException('No connections.'); } $slot = $this->getSlot($hash); $node = $this->getBySlot($slot); return $node; } public function get($value) { $hash = $this->hash($value); $node = $this->getByHash($hash); return $node; } public function hash($value) { return crc32($value); } public function getHashGenerator() { return $this; } } $options = array( 'cluster' => function () { $distributor = new NaiveDistributor(); $strategy = new PredisStrategy($distributor); $cluster = new PredisCluster($strategy); return $cluster; }, ); $client = new Predis\Client($multiple_servers, $options); for ($i = 0; $i < 100; ++$i) { $client->set("key:$i", str_pad($i, 4, '0', 0)); $client->get("key:$i"); } $server1 = $client->getClientFor('first')->info(); $server2 = $client->getClientFor('second')->info(); if (isset($server1['Keyspace'], $server2['Keyspace'])) { $server1 = $server1['Keyspace']; $server2 = $server2['Keyspace']; } printf("Server '%s' has %d keys while server '%s' has %d keys.\n", 'first', $server1['db15']['keys'], 'second', $server2['db15']['keys'] ); '127.0.0.1', 'port' => 6379, 'database' => 15, ); $multiple_servers = array( array( 'host' => '127.0.0.1', 'port' => 6379, 'database' => 15, 'alias' => 'first', ), array( 'host' => '127.0.0.1', 'port' => 6380, 'database' => 15, 'alias' => 'second', ), ); tstart = microtime(true); parent::connect(); } private function storeDebug(CommandInterface $command, $direction) { $firtsArg = $command->getArgument(0); $timestamp = round(microtime(true) - $this->tstart, 4); $debug = $command->getId(); $debug .= isset($firtsArg) ? " $firtsArg " : ' '; $debug .= "$direction $this"; $debug .= " [{$timestamp}s]"; $this->debugBuffer[] = $debug; } public function writeRequest(CommandInterface $command) { parent::writeRequest($command); $this->storeDebug($command, '->'); } public function readResponse(CommandInterface $command) { $response = parent::readResponse($command); $this->storeDebug($command, '<-'); return $response; } public function getDebugBuffer() { return $this->debugBuffer; } } $options = array( 'connections' => array( 'tcp' => 'SimpleDebuggableConnection', ), ); $client = new Predis\Client($single_server, $options); $client->set('foo', 'bar'); $client->get('foo'); $client->info(); var_export($client->getConnection()->getDebugBuffer()); 'nrk:')); $client->mset(array('foo' => 'bar', 'lol' => 'wut')); var_export($client->mget('foo', 'lol')); var_export($client->keys('*')); '2.8')); $client->del('predis:set', 'predis:zset', 'predis:hash'); for ($i = 0; $i < 5; ++$i) { $client->sadd('predis:set', "member:$i"); $client->zadd('predis:zset', -$i, "member:$i"); $client->hset('predis:hash', "field:$i", "value:$i"); } echo 'Scan the keyspace matching only our prefixed keys:', PHP_EOL; foreach (new Iterator\Keyspace($client, 'predis:*') as $key) { echo " - $key", PHP_EOL; } echo 'Scan members of `predis:set`:', PHP_EOL; foreach (new Iterator\SetKey($client, 'predis:set') as $member) { echo " - $member", PHP_EOL; } echo 'Scan members and ranks of `predis:zset`:', PHP_EOL; foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank) { echo " - $member [rank: $rank]", PHP_EOL; } echo 'Scan fields and values of `predis:hash`:', PHP_EOL; foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) { echo " - $field => $value", PHP_EOL; } true, 'watch' => $key, 'retry' => 3, ); $client->transaction($options, function ($tx) use ($key, &$element) { @list($element) = $tx->zrange($key, 0, 0); if (isset($element)) { $tx->multi(); $tx->zrem($key, $element); } }); return $element; } $client = new Predis\Client($single_server); $zpopped = zpop($client, 'zset'); echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL; function ($options, $option) { $profile = $options->getDefault($option); $profile->defineCommand('hmgetall', 'HashMultipleGetAll'); return $profile; }, 'replication' => function () { $strategy = new ReplicationStrategy(); $strategy->setScriptReadOnly(HashMultipleGetAll::BODY); $replication = new MasterSlaveReplication($strategy); return $replication; }, ); $client = new Predis\Client($parameters, $options); $hashes = $client->hmgetall('metavars', 'servers'); $replication = $client->getConnection(); $stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave'); echo 'Is still on slave? ', $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL; var_export($hashes); true); $client = new Predis\Client($parameters, $options); $exists = $client->exists('foo') ? 'yes' : 'no'; $current = $client->getConnection()->getCurrent()->getParameters(); echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL; $client->set('foo', 'bar'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL; $bar = $client->get('foo'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL; = 5.4.0 '. "or a polyfill for SessionHandlerInterface provided by an external package.\n"); } $client = new Predis\Client($single_server, array('prefix' => 'sessions:')); $handler = new Predis\Session\Handler($client, array('gc_maxlifetime' => 5)); $handler->register(); session_id('example_session_id'); session_start(); if (isset($_SESSION['foo'])) { echo "Session has `foo` set to {$_SESSION['foo']}", PHP_EOL; } else { $_SESSION['foo'] = $value = mt_rand(); echo "Empty session, `foo` has been set with $value", PHP_EOL; } 0)); $pubsub = $client->pubSubLoop(); $dispatcher = new Predis\PubSub\DispatcherLoop($pubsub); class EventsListener implements Countable { private $events; public function __construct() { $this->events = array(); } public function count() { return count($this->events); } public function getEvents() { return $this->events; } public function __invoke($payload) { $this->events[] = $payload; } } $dispatcher->attachCallback('events', ($events = new EventsListener())); $dispatcher->attachCallback('control', function ($payload) use ($dispatcher) { if ($payload === 'terminate_dispatcher') { $dispatcher->stop(); } }); $dispatcher->run(); echo "We received {$events->count()} messages!", PHP_EOL; $version = redis_version($client->info()); echo "Goodbye from Redis $version!", PHP_EOL; 0)); $pubsub = $client->pubSubLoop(); $pubsub->subscribe('control_channel', 'notifications'); foreach ($pubsub as $message) { switch ($message->kind) { case 'subscribe': echo "Subscribed to {$message->channel}", PHP_EOL; break; case 'message': if ($message->channel == 'control_channel') { if ($message->payload == 'quit_loop') { echo 'Aborting pubsub loop...', PHP_EOL; $pubsub->unsubscribe(); } else { echo "Received an unrecognized command: {$message->payload}.", PHP_EOL; } } else { echo "Received the following message from {$message->channel}:", PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL; } break; } } unset($pubsub); $version = redis_version($client->info()); echo "Goodbye from Redis $version!", PHP_EOL; 'sentinel', 'service' => 'mymaster', )); $exists = $client->exists('foo') ? 'yes' : 'no'; $current = $client->getConnection()->getCurrent()->getParameters(); echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL; $client->set('foo', 'bar'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL; $bar = $client->get('foo'); $current = $client->getConnection()->getCurrent()->getParameters(); echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL; pipeline(function ($pipe) { $pipe->flushdb(); $pipe->incrby('counter', 10); $pipe->incrby('counter', 30); $pipe->exists('counter'); $pipe->get('counter'); $pipe->mget('does_not_exist', 'counter'); }); var_export($responses); set('library', 'predis'); $response = $client->get('library'); var_export($response); echo PHP_EOL; $mkv = array( 'uid:0001' => '1st user', 'uid:0002' => '2nd user', 'uid:0003' => '3rd user', ); $client->mset($mkv); $response = $client->mget(array_keys($mkv)); var_export($response); echo PHP_EOL; $response = $client->executeRaw(array( 'MGET', 'uid:0001', 'uid:0002', 'uid:0003', )); var_export($response); echo PHP_EOL; 0)); $timestamp = new DateTime(); foreach (($monitor = $client->monitor()) as $event) { $timestamp->setTimestamp((int) $event->timestamp); if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') { echo 'Exiting the monitor loop...', PHP_EOL; $monitor->stop(); break; } echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL; if (isset($event->arguments)) { echo " Arguments: {$event->arguments}", PHP_EOL; } } $version = redis_version($client->info()); echo "Goodbye from Redis $version!", PHP_EOL; function ($options) { $profile = $options->getDefault('profile'); $profile->defineCommand('increxby', 'IncrementExistingKeysBy'); return $profile; }, )); $client->mset('foo', 10, 'foobar', 100); var_export($client->increxby('foo', 'foofoo', 'foobar', 50)); = $limit) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value less than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); } } public static function lessThanEq($value, $limit, $message = '') { if ($value > $limit) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value less than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); } } public static function range($value, $min, $max, $message = '') { if ($value < $min || $value > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value between %2$s and %3$s. Got: %s', static::valueToString($value), static::valueToString($min), static::valueToString($max) )); } } public static function oneOf($value, array $values, $message = '') { if (!in_array($value, $values, true)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected one of: %2$s. Got: %s', static::valueToString($value), implode(', ', array_map(array('static', 'valueToString'), $values)) )); } } public static function contains($value, $subString, $message = '') { if (false === strpos($value, $subString)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain %2$s. Got: %s', static::valueToString($value), static::valueToString($subString) )); } } public static function startsWith($value, $prefix, $message = '') { if (0 !== strpos($value, $prefix)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to start with %2$s. Got: %s', static::valueToString($value), static::valueToString($prefix) )); } } public static function startsWithLetter($value, $message = '') { $valid = isset($value[0]); if ($valid) { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = ctype_alpha($value[0]); setlocale(LC_CTYPE, $locale); } if (!$valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to start with a letter. Got: %s', static::valueToString($value) )); } } public static function endsWith($value, $suffix, $message = '') { if ($suffix !== substr($value, -static::strlen($suffix))) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to end with %2$s. Got: %s', static::valueToString($value), static::valueToString($suffix) )); } } public static function regex($value, $pattern, $message = '') { if (!preg_match($pattern, $value)) { static::reportInvalidArgument(sprintf( $message ?: 'The value %s does not match the expected pattern.', static::valueToString($value) )); } } public static function alpha($value, $message = '') { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_alpha($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain only letters. Got: %s', static::valueToString($value) )); } } public static function digits($value, $message = '') { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_digit($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain digits only. Got: %s', static::valueToString($value) )); } } public static function alnum($value, $message = '') { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_alnum($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain letters and digits only. Got: %s', static::valueToString($value) )); } } public static function lower($value, $message = '') { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_lower($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain lowercase characters only. Got: %s', static::valueToString($value) )); } } public static function upper($value, $message = '') { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_upper($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain uppercase characters only. Got: %s', static::valueToString($value) )); } } public static function length($value, $length, $message = '') { if ($length !== static::strlen($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain %2$s characters. Got: %s', static::valueToString($value), $length )); } } public static function minLength($value, $min, $message = '') { if (static::strlen($value) < $min) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain at least %2$s characters. Got: %s', static::valueToString($value), $min )); } } public static function maxLength($value, $max, $message = '') { if (static::strlen($value) > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain at most %2$s characters. Got: %s', static::valueToString($value), $max )); } } public static function lengthBetween($value, $min, $max, $message = '') { $length = static::strlen($value); if ($length < $min || $length > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', static::valueToString($value), $min, $max )); } } public static function fileExists($value, $message = '') { static::string($value); if (!file_exists($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The file %s does not exist.', static::valueToString($value) )); } } public static function file($value, $message = '') { static::fileExists($value, $message); if (!is_file($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is not a file.', static::valueToString($value) )); } } public static function directory($value, $message = '') { static::fileExists($value, $message); if (!is_dir($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is no directory.', static::valueToString($value) )); } } public static function readable($value, $message = '') { if (!is_readable($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is not readable.', static::valueToString($value) )); } } public static function writable($value, $message = '') { if (!is_writable($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is not writable.', static::valueToString($value) )); } } public static function classExists($value, $message = '') { if (!class_exists($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an existing class name. Got: %s', static::valueToString($value) )); } } public static function subclassOf($value, $class, $message = '') { if (!is_subclass_of($value, $class)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a sub-class of %2$s. Got: %s', static::valueToString($value), static::valueToString($class) )); } } public static function implementsInterface($value, $interface, $message = '') { if (!in_array($interface, class_implements($value))) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an implementation of %2$s. Got: %s', static::valueToString($value), static::valueToString($interface) )); } } public static function propertyExists($classOrObject, $property, $message = '') { if (!property_exists($classOrObject, $property)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the property %s to exist.', static::valueToString($property) )); } } public static function propertyNotExists($classOrObject, $property, $message = '') { if (property_exists($classOrObject, $property)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the property %s to not exist.', static::valueToString($property) )); } } public static function methodExists($classOrObject, $method, $message = '') { if (!method_exists($classOrObject, $method)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the method %s to exist.', static::valueToString($method) )); } } public static function methodNotExists($classOrObject, $method, $message = '') { if (method_exists($classOrObject, $method)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the method %s to not exist.', static::valueToString($method) )); } } public static function keyExists($array, $key, $message = '') { if (!array_key_exists($key, $array)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the key %s to exist.', static::valueToString($key) )); } } public static function keyNotExists($array, $key, $message = '') { if (array_key_exists($key, $array)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the key %s to not exist.', static::valueToString($key) )); } } public static function count($array, $number, $message = '') { static::eq( count($array), $number, $message ?: sprintf('Expected an array to contain %d elements. Got: %d.', $number, count($array)) ); } public static function uuid($value, $message = '') { $value = str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); if ('00000000-0000-0000-0000-000000000000' === $value) { return; } if (!preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { static::reportInvalidArgument(sprintf( $message ?: 'Value %s is not a valid UUID.', static::valueToString($value) )); } } public static function throws(Closure $expression, $class = 'Exception', $message = '') { static::string($class); $actual = 'none'; try { $expression(); } catch (Exception $e) { $actual = get_class($e); if ($e instanceof $class) { return; } } catch (Throwable $e) { $actual = get_class($e); if ($e instanceof $class) { return; } } static::reportInvalidArgument($message ?: sprintf( 'Expected to throw "%s", got "%s"', $class, $actual )); } public static function __callStatic($name, $arguments) { if ('nullOr' === substr($name, 0, 6)) { if (null !== $arguments[0]) { $method = lcfirst(substr($name, 6)); call_user_func_array(array('static', $method), $arguments); } return; } if ('all' === substr($name, 0, 3)) { static::isTraversable($arguments[0]); $method = lcfirst(substr($name, 3)); $args = $arguments; foreach ($arguments[0] as $entry) { $args[0] = $entry; call_user_func_array(array('static', $method), $args); } return; } throw new BadMethodCallException('No such method: '.$name); } protected static function valueToString($value) { if (null === $value) { return 'null'; } if (true === $value) { return 'true'; } if (false === $value) { return 'false'; } if (is_array($value)) { return 'array'; } if (is_object($value)) { return get_class($value); } if (is_resource($value)) { return 'resource'; } if (is_string($value)) { return '"'.$value.'"'; } return (string) $value; } protected static function typeToString($value) { return is_object($value) ? get_class($value) : gettype($value); } protected static function strlen($value) { if (!function_exists('mb_detect_encoding')) { return strlen($value); } if (false === $encoding = mb_detect_encoding($value)) { return strlen($value); } return mb_strwidth($value, $encoding); } protected static function reportInvalidArgument($message) { throw new InvalidArgumentException($message); } private function __construct() { } } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } abstract public function log($level, $message, array $context = array()); } assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); } public function testLogsAtAllLevels($level, $message) { $logger = $this->getLogger(); $logger->{$level}($message, array('user' => 'Bob')); $logger->log($level, $message, array('user' => 'Bob')); $expected = array( $level.' message of level '.$level.' with context: Bob', $level.' message of level '.$level.' with context: Bob', ); $this->assertEquals($expected, $this->getLogs()); } public function provideLevelsAndMessages() { return array( LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), ); } public function testThrowsOnInvalidLevel() { $logger = $this->getLogger(); $logger->log('invalid level', 'Foo'); } public function testContextReplacement() { $logger = $this->getLogger(); $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); $expected = array('info {Message {nothing} Bob Bar a}'); $this->assertEquals($expected, $this->getLogs()); } public function testObjectCastToString() { if (method_exists($this, 'createPartialMock')) { $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); } else { $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); } $dummy->expects($this->once()) ->method('__toString') ->will($this->returnValue('DUMMY')); $this->getLogger()->warning($dummy); $expected = array('warning DUMMY'); $this->assertEquals($expected, $this->getLogs()); } public function testContextCanContainAnything() { $context = array( 'bool' => true, 'null' => null, 'string' => 'Foo', 'int' => 0, 'float' => 0.5, 'nested' => array('with object' => new DummyTest), 'object' => new \DateTime, 'resource' => fopen('php://memory', 'r'), ); $this->getLogger()->warning('Crazy context data', $context); $expected = array('warning Crazy context data'); $this->assertEquals($expected, $this->getLogs()); } public function testContextExceptionKeyCanBeExceptionOrOtherValues() { $logger = $this->getLogger(); $logger->warning('Random message', array('exception' => 'oops')); $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); $expected = array( 'warning Random message', 'critical Uncaught Exception!' ); $this->assertEquals($expected, $this->getLogs()); } } class DummyTest { public function __toString() { } } logger = $logger; } } setName('_completion') ->setDefinition($this->createDefinition()) ->setDescription('BASH completion hook.') ->setHelp(<<eval `[program] _completion -g`. Or for an alias: eval `[program] _completion -g -p [alias]`. END ); } public function getNativeDefinition() { return $this->createDefinition(); } protected function execute(InputInterface $input, OutputInterface $output) { $this->handler = new CompletionHandler($this->getApplication()); $handler = $this->handler; if ($input->getOption('generate-hook')) { global $argv; $program = $argv[0]; $factory = new HookFactory(); $alias = $input->getOption('program'); $multiple = (bool)$input->getOption('multiple'); if (!$alias && $multiple) { $alias = basename($program); } $hook = $factory->generateHook( $input->getOption('shell-type') ?: $this->getShellType(), $program, $alias, $multiple ); $output->write($hook, true); } else { $handler->setContext(new EnvironmentCompletionContext()); $output->write($this->runCompletion(), true); } } protected function runCompletion() { $this->configureCompletion($this->handler); return $this->handler->runCompletion(); } protected function configureCompletion(CompletionHandler $handler) { } protected function getShellType() { if (!getenv('SHELL')) { throw new \RuntimeException('Could not read SHELL environment variable. Please specify your shell type using the --shell-type option.'); } return basename(getenv('SHELL')); } protected function createDefinition() { return new InputDefinition(array( new InputOption( 'generate-hook', 'g', InputOption::VALUE_NONE, 'Generate BASH code that sets up completion for this application.' ), new InputOption( 'program', 'p', InputOption::VALUE_REQUIRED, "Program name that should trigger completion\n(defaults to the absolute application path)." ), new InputOption( 'multiple', 'm', InputOption::VALUE_NONE, "Generated hook can be used for multiple applications." ), new InputOption( 'shell-type', null, InputOption::VALUE_OPTIONAL, 'Set the shell type (zsh or bash). Otherwise this is determined automatically.' ), )); } } application = $application; $this->context = $context; $this->addHandler( new Completion( 'help', 'command_name', Completion::TYPE_ARGUMENT, array_keys($application->all()) ) ); $this->addHandler( new Completion( 'list', 'namespace', Completion::TYPE_ARGUMENT, $application->getNamespaces() ) ); } public function setContext(CompletionContext $context) { $this->context = $context; } public function getContext() { return $this->context; } public function addHandlers(array $array) { $this->helpers = array_merge($this->helpers, $array); } public function addHandler(CompletionInterface $helper) { $this->helpers[] = $helper; } public function runCompletion() { if (!$this->context) { throw new \RuntimeException('A CompletionContext must be set before requesting completion.'); } $cmdName = $this->getInput()->getFirstArgument(); try { $this->command = $this->application->find($cmdName); } catch (\InvalidArgumentException $e) { } $process = array( 'completeForOptionValues', 'completeForOptionShortcuts', 'completeForOptionShortcutValues', 'completeForOptions', 'completeForCommandName', 'completeForCommandArguments' ); foreach ($process as $methodName) { $result = $this->{$methodName}(); if (false !== $result) { return $this->filterResults((array) $result); } } return array(); } public function getInput() { $words = $this->context->getWords(); array_shift($words); $words = array_filter($words); return new ArrayInput($words); } protected function completeForOptions() { $word = $this->context->getCurrentWord(); if (substr($word, 0, 2) === '--') { $options = array(); foreach ($this->getAllOptions() as $opt) { $options[] = '--'.$opt->getName(); } return $options; } return false; } protected function completeForOptionShortcuts() { $word = $this->context->getCurrentWord(); if (strpos($word, '-') === 0 && strlen($word) == 2) { $definition = $this->command ? $this->command->getNativeDefinition() : $this->application->getDefinition(); if ($definition->hasShortcut(substr($word, 1))) { return array($word); } } return false; } protected function completeForOptionShortcutValues() { $wordIndex = $this->context->getWordIndex(); if ($this->command && $wordIndex > 1) { $left = $this->context->getWordAtIndex($wordIndex - 1); if ($left[0] == '-' && strlen($left) == 2) { $shortcut = substr($left, 1); $def = $this->command->getNativeDefinition(); if (!$def->hasShortcut($shortcut)) { return false; } $opt = $def->getOptionForShortcut($shortcut); if ($opt->isValueRequired() || $opt->isValueOptional()) { return $this->completeOption($opt); } } } return false; } protected function completeForOptionValues() { $wordIndex = $this->context->getWordIndex(); if ($this->command && $wordIndex > 1) { $left = $this->context->getWordAtIndex($wordIndex - 1); if (strpos($left, '--') === 0) { $name = substr($left, 2); $def = $this->command->getNativeDefinition(); if (!$def->hasOption($name)) { return false; } $opt = $def->getOption($name); if ($opt->isValueRequired() || $opt->isValueOptional()) { return $this->completeOption($opt); } } } return false; } protected function completeForCommandName() { if (!$this->command || (count($this->context->getWords()) == 2 && $this->context->getWordIndex() == 1)) { $commands = $this->application->all(); $names = array_keys($commands); if ($key = array_search('_completion', $names)) { unset($names[$key]); } return $names; } return false; } protected function completeForCommandArguments() { if (!$this->command || strpos($this->context->getCurrentWord(), '-') === 0) { return false; } $definition = $this->command->getNativeDefinition(); $argWords = $this->mapArgumentsToWords($definition->getArguments()); $wordIndex = $this->context->getWordIndex(); if (isset($argWords[$wordIndex])) { $name = $argWords[$wordIndex]; } elseif (!empty($argWords) && $definition->getArgument(end($argWords))->isArray()) { $name = end($argWords); } else { return false; } if ($helper = $this->getCompletionHelper($name, Completion::TYPE_ARGUMENT)) { return $helper->run(); } if ($this->command instanceof CompletionAwareInterface) { return $this->command->completeArgumentValues($name, $this->context); } return false; } protected function getCompletionHelper($name, $type) { foreach ($this->helpers as $helper) { if ($helper->getType() != $type && $helper->getType() != CompletionInterface::ALL_TYPES) { continue; } if ($helper->getCommandName() == CompletionInterface::ALL_COMMANDS || $helper->getCommandName() == $this->command->getName()) { if ($helper->getTargetName() == $name) { return $helper; } } } return null; } protected function completeOption(InputOption $option) { if ($helper = $this->getCompletionHelper($option->getName(), Completion::TYPE_OPTION)) { return $helper->run(); } if ($this->command instanceof CompletionAwareInterface) { return $this->command->completeOptionValues($option->getName(), $this->context); } return false; } protected function mapArgumentsToWords($argumentDefinitions) { $argumentPositions = array(); $argumentNumber = 0; $previousWord = null; $argumentNames = array_keys($argumentDefinitions); $optionsWithArgs = $this->getOptionWordsWithValues(); foreach ($this->context->getWords() as $wordIndex => $word) { if ($wordIndex < 2 || ($word && '-' === $word[0]) || in_array($previousWord, $optionsWithArgs)) { $previousWord = $word; continue; } else { $previousWord = $word; } if (isset($argumentNames[$argumentNumber])) { $argumentPositions[$wordIndex] = $argumentNames[$argumentNumber]; } $argumentNumber++; } return $argumentPositions; } protected function getOptionWordsWithValues() { $strings = array(); foreach ($this->getAllOptions() as $option) { if ($option->isValueRequired()) { $strings[] = '--' . $option->getName(); if ($option->getShortcut()) { $strings[] = '-' . $option->getShortcut(); } } } return $strings; } protected function filterResults(array $array) { $curWord = $this->context->getCurrentWord(); return array_filter($array, function($val) use ($curWord) { return fnmatch($curWord.'*', $val); }); } protected function getAllOptions() { if (!$this->command) { return $this->application->getDefinition()->getOptions(); } return array_merge( $this->command->getNativeDefinition()->getOptions(), $this->application->getDefinition()->getOptions() ); } } commandName = $commandName; $this->targetName = $targetName; $this->type = $type; } public function getType() { return $this->type; } public function getCommandName() { return $this->commandName; } public function getTargetName() { return $this->targetName; } public function run() { exit(self::PATH_COMPLETION_EXIT_CODE); } } commandLine = getenv('CMDLINE_CONTENTS'); $this->charIndex = intval(getenv('CMDLINE_CURSOR_INDEX')); if ($this->commandLine === false) { $message = 'Failed to configure from environment; Environment var CMDLINE_CONTENTS not set.'; if (getenv('COMP_LINE')) { $message .= "\n\nYou appear to be attempting completion using an out-dated hook. If you've just updated," . " you probably need to reinitialise the completion shell hook by reloading your shell" . " profile or starting a new shell session. If you are using a hard-coded (rather than generated)" . " hook, you will need to update that function with the new environment variable names." . "\n\nSee here for details: https://github.com/stecman/symfony-console-completion/issues/31"; } throw new \RuntimeException($message); } } public function useWordBreaksFromEnvironment() { $breaks = getenv('CMDLINE_WORDBREAKS'); if (!$breaks) { throw new \RuntimeException('Failed to read word breaks from environment; Environment var CMDLINE_WORDBREAKS not set'); } $this->wordBreaks = $breaks; } } commandLine = $commandLine; $this->reset(); } public function getCommandLine() { return $this->commandLine; } public function getCurrentWord() { if (isset($this->words[$this->wordIndex])) { return $this->words[$this->wordIndex]; } return ''; } public function getWordAtIndex($index) { if (isset($this->words[$index])) { return $this->words[$index]; } return ''; } public function getWords() { if ($this->words === null) { $this->splitCommand(); } return $this->words; } public function getWordIndex() { if ($this->wordIndex === null) { $this->splitCommand(); } return $this->wordIndex; } public function getCharIndex() { return $this->charIndex; } public function setCharIndex($index) { $this->charIndex = $index; $this->reset(); } public function setWordBreaks($charList) { $this->wordBreaks = $charList; $this->reset(); } protected function splitCommand() { $this->words = array(); $this->wordIndex = null; $cursor = 0; $breaks = preg_quote($this->wordBreaks); if (!preg_match_all("/([^$breaks]*)([$breaks]*)/", $this->commandLine, $matches)) { return; } foreach ($matches[0] as $index => $wholeMatch) { $cursor += strlen($wholeMatch); $word = $matches[1][$index]; $breaks = $matches[2][$index]; if ($this->wordIndex === null && $cursor >= $this->charIndex) { $this->wordIndex = $index; $cursorWordOffset = $this->charIndex - ($cursor - strlen($breaks)); if ($cursorWordOffset < 0) { $word = substr($word, 0, strlen($word) + $cursorWordOffset); } elseif ($cursorWordOffset > 0) { $this->wordIndex++; $this->words[] = $word; $this->words[] = ''; continue; } } if ($word !== '') { $this->words[] = $word; } } if ($this->wordIndex > count($this->words) - 1) { $this->wordIndex = count($this->words) - 1; } } protected function reset() { $this->words = null; $this->wordIndex = null; } } <<<'END' # BASH completion for %%program_path%% function %%function_name%% { # Copy BASH's completion variables to the ones the completion command expects # These line up exactly as the library was originally designed for BASH local CMDLINE_CONTENTS="$COMP_LINE" local CMDLINE_CURSOR_INDEX="$COMP_POINT" local CMDLINE_WORDBREAKS="$COMP_WORDBREAKS"; export CMDLINE_CONTENTS CMDLINE_CURSOR_INDEX CMDLINE_WORDBREAKS local RESULT STATUS; RESULT="$(%%completion_command%% &2 echo "Completion was not registered for %%program_name%%:"; >&2 echo "The 'bash-completion' package is required but doesn't appear to be installed."; fi END , 'zsh' => <<<'END' # ZSH completion for %%program_path%% function %%function_name%% { local -x CMDLINE_CONTENTS="$words" local -x CMDLINE_CURSOR_INDEX (( CMDLINE_CURSOR_INDEX = ${#${(j. .)words[1,CURRENT]}} )) local RESULT STATUS RESULT=("${(@f)$( %%completion_command%% )}") STATUS=$?; # Check if shell provided path completion is requested # @see Completion\ShellPathCompletion if [ $STATUS -eq 200 ]; then _path_files; return 0; # Bail out if PHP didn't exit cleanly elif [ $STATUS -ne 0 ]; then echo -e "$RESULT"; return $?; fi; compadd -- $RESULT }; compdef %%function_name%% "%%program_name%%"; END ); public static function getShellTypes() { return array_keys(self::$hooks); } public function generateHook($type, $programPath, $programName = null, $multiple = false) { if (!isset(self::$hooks[$type])) { throw new \RuntimeException(sprintf( "Cannot generate hook for unknown shell type '%s'. Available hooks are: %s", $type, implode(', ', self::getShellTypes()) )); } $programName = $programName ?: $programPath; if ($multiple) { $completionCommand = '$1 _completion'; } else { $completionCommand = $programPath . ' _completion'; } return str_replace( array( '%%function_name%%', '%%program_name%%', '%%program_path%%', '%%completion_command%%', ), array( $this->generateFunctionName($programPath, $programName), $programName, $programPath, $completionCommand ), $this->stripComments(self::$hooks[$type]) ); } protected function generateFunctionName($programPath, $programName) { return sprintf( '_%s_%s_complete', $this->sanitiseForFunctionName(basename($programName)), substr(md5($programPath), 0, 16) ); } protected function sanitiseForFunctionName($name) { $name = str_replace('-', '_', $name); return preg_replace('/[^A-Za-z0-9_]+/', '', $name); } protected function stripComments($script) { return preg_replace('/(^\s*\#.*$)/m', '', $script); } } commandName = $commandName; $this->targetName = $targetName; $this->type = $type; $this->completion = $completion; } public function run() { if ($this->isCallable()) { return call_user_func($this->completion); } return $this->completion; } public function getType() { return $this->type; } public function setType($type) { $this->type = $type; } public function getCommandName() { return $this->commandName; } public function setCommandName($commandName) { $this->commandName = $commandName; } public function getTargetName() { return $this->targetName; } public function setTargetName($targetName) { $this->targetName = $targetName; } public function getCompletion() { return $this->completion; } public function setCompletion($completion) { $this->completion = $completion; } public function isCallable() { return is_callable($this->completion); } } array ( 'and' => 'And|*', 'background' => 'Background', 'but' => 'But|*', 'examples' => 'Scenarios|Examples', 'feature' => 'Business Need|Feature|Ability', 'given' => 'Given|*', 'name' => 'English', 'native' => 'English', 'scenario' => 'Scenario', 'scenario_outline' => 'Scenario Template|Scenario Outline', 'then' => 'Then|*', 'when' => 'When|*', ), 'af' => array ( 'and' => 'En|*', 'background' => 'Agtergrond', 'but' => 'Maar|*', 'examples' => 'Voorbeelde', 'feature' => 'Besigheid Behoefte|Funksie|Vermoë', 'given' => 'Gegewe|*', 'name' => 'Afrikaans', 'native' => 'Afrikaans', 'scenario' => 'Situasie', 'scenario_outline' => 'Situasie Uiteensetting', 'then' => 'Dan|*', 'when' => 'Wanneer|*', ), 'am' => array ( 'and' => 'Եվ|*', 'background' => 'Կոնտեքստ', 'but' => 'Բայց|*', 'examples' => 'Օրինակներ', 'feature' => 'Ֆունկցիոնալություն|Հատկություն', 'given' => 'Դիցուք|*', 'name' => 'Armenian', 'native' => 'հայերեն', 'scenario' => 'Սցենար', 'scenario_outline' => 'Սցենարի կառուցվացքը', 'then' => 'Ապա|*', 'when' => 'Երբ|Եթե|*', ), 'ar' => array ( 'and' => '*|و', 'background' => 'الخلفية', 'but' => 'لكن|*', 'examples' => 'امثلة', 'feature' => 'خاصية', 'given' => 'بفرض|*', 'name' => 'Arabic', 'native' => 'العربية', 'scenario' => 'سيناريو', 'scenario_outline' => 'سيناريو مخطط', 'then' => 'اذاً|ثم|*', 'when' => 'عندما|متى|*', ), 'ast' => array ( 'and' => 'Ya|*|Y', 'background' => 'Antecedentes', 'but' => 'Peru|*', 'examples' => 'Exemplos', 'feature' => 'Carauterística', 'given' => 'Dada|Daos|Daes|Dáu|*', 'name' => 'Asturian', 'native' => 'asturianu', 'scenario' => 'Casu', 'scenario_outline' => 'Esbozu del casu', 'then' => 'Entós|*', 'when' => 'Cuando|*', ), 'az' => array ( 'and' => 'Həm|Və|*', 'background' => 'Kontekst|Keçmiş', 'but' => 'Ancaq|Amma|*', 'examples' => 'Nümunələr', 'feature' => 'Özəllik', 'given' => 'Tutaq ki|Verilir|*', 'name' => 'Azerbaijani', 'native' => 'Azərbaycanca', 'scenario' => 'Ssenari', 'scenario_outline' => 'Ssenarinin strukturu', 'then' => 'O halda|*', 'when' => 'Nə vaxt ki|Əgər|*', ), 'bg' => array ( 'and' => '*|И', 'background' => 'Предистория', 'but' => 'Но|*', 'examples' => 'Примери', 'feature' => 'Функционалност', 'given' => 'Дадено|*', 'name' => 'Bulgarian', 'native' => 'български', 'scenario' => 'Сценарий', 'scenario_outline' => 'Рамка на сценарий', 'then' => 'То|*', 'when' => 'Когато|*', ), 'bm' => array ( 'and' => 'Dan|*', 'background' => 'Latar Belakang', 'but' => 'Tetapi|Tapi|*', 'examples' => 'Contoh', 'feature' => 'Fungsi', 'given' => 'Diberi|Bagi|*', 'name' => 'Malay', 'native' => 'Bahasa Melayu', 'scenario' => 'Senario|Situasi|Keadaan', 'scenario_outline' => 'Garis Panduan Senario|Kerangka Senario|Kerangka Situasi|Kerangka Keadaan', 'then' => 'Kemudian|Maka|*', 'when' => 'Apabila|*', ), 'bs' => array ( 'and' => '*|I|A', 'background' => 'Pozadina', 'but' => 'Ali|*', 'examples' => 'Primjeri', 'feature' => 'Karakteristika', 'given' => 'Dato|*', 'name' => 'Bosnian', 'native' => 'Bosanski', 'scenario' => 'Scenariju|Scenario', 'scenario_outline' => 'Scenario-outline|Scenariju-obris', 'then' => 'Zatim|*', 'when' => 'Kada|*', ), 'ca' => array ( 'and' => '*|I', 'background' => 'Antecedents|Rerefons', 'but' => 'Però|*', 'examples' => 'Exemples', 'feature' => 'Característica|Funcionalitat', 'given' => 'Donada|Donat|Atesa|Atès|*', 'name' => 'Catalan', 'native' => 'català', 'scenario' => 'Escenari', 'scenario_outline' => 'Esquema de l\'escenari', 'then' => 'Aleshores|Cal|*', 'when' => 'Quan|*', ), 'cs' => array ( 'and' => 'A také|*|A', 'background' => 'Kontext|Pozadí', 'but' => 'Ale|*', 'examples' => 'Příklady', 'feature' => 'Požadavek', 'given' => 'Za předpokladu|Pokud|*', 'name' => 'Czech', 'native' => 'Česky', 'scenario' => 'Scénář', 'scenario_outline' => 'Osnova scénáře|Náčrt Scénáře', 'then' => 'Pak|*', 'when' => 'Když|*', ), 'cy-GB' => array ( 'and' => '*|A', 'background' => 'Cefndir', 'but' => 'Ond|*', 'examples' => 'Enghreifftiau', 'feature' => 'Arwedd', 'given' => 'Anrhegedig a|*', 'name' => 'Welsh', 'native' => 'Cymraeg', 'scenario' => 'Scenario', 'scenario_outline' => 'Scenario Amlinellol', 'then' => 'Yna|*', 'when' => 'Pryd|*', ), 'da' => array ( 'and' => 'Og|*', 'background' => 'Baggrund', 'but' => 'Men|*', 'examples' => 'Eksempler', 'feature' => 'Egenskab', 'given' => 'Givet|*', 'name' => 'Danish', 'native' => 'dansk', 'scenario' => 'Scenarie', 'scenario_outline' => 'Abstrakt Scenario', 'then' => 'Så|*', 'when' => 'Når|*', ), 'de' => array ( 'and' => 'Und|*', 'background' => 'Grundlage', 'but' => 'Aber|*', 'examples' => 'Beispiele', 'feature' => 'Funktionalität', 'given' => 'Gegeben seien|Gegeben sei|Angenommen|*', 'name' => 'German', 'native' => 'Deutsch', 'scenario' => 'Szenario', 'scenario_outline' => 'Szenariogrundriss', 'then' => 'Dann|*', 'when' => 'Wenn|*', ), 'el' => array ( 'and' => 'Και|*', 'background' => 'Υπόβαθρο', 'but' => 'Αλλά|*', 'examples' => 'Παραδείγματα|Σενάρια', 'feature' => 'Δυνατότητα|Λειτουργία', 'given' => 'Δεδομένου|*', 'name' => 'Greek', 'native' => 'Ελληνικά', 'scenario' => 'Σενάριο', 'scenario_outline' => 'Περιγραφή Σεναρίου', 'then' => 'Τότε|*', 'when' => 'Όταν|*', ), 'em' => array ( 'and' => '😂<|*', 'background' => '💤', 'but' => '😔<|*', 'examples' => '📓', 'feature' => '📚', 'given' => '😐<|*', 'name' => 'Emoji', 'native' => '😀', 'scenario' => '📕', 'scenario_outline' => '📖', 'then' => '🙏<|*', 'when' => '🎬<|*', ), 'en-Scouse' => array ( 'and' => 'An|*', 'background' => 'Dis is what went down', 'but' => 'Buh|*', 'examples' => 'Examples', 'feature' => 'Feature', 'given' => 'Youse know when youse got|Givun|*', 'name' => 'Scouse', 'native' => 'Scouse', 'scenario' => 'The thing of it is', 'scenario_outline' => 'Wharrimean is', 'then' => 'Den youse gotta|Dun|*', 'when' => 'Youse know like when|Wun|*', ), 'en-au' => array ( 'and' => 'Too right|*', 'background' => 'First off', 'but' => 'Yeah nah|*', 'examples' => 'You\'ll wanna', 'feature' => 'Pretty much', 'given' => 'Y\'know|*', 'name' => 'Australian', 'native' => 'Australian', 'scenario' => 'Awww, look mate', 'scenario_outline' => 'Reckon it\'s like', 'then' => 'But at the end of the day I reckon|*', 'when' => 'It\'s just unbelievable|*', ), 'en-lol' => array ( 'and' => 'AN|*', 'background' => 'B4', 'but' => 'BUT|*', 'examples' => 'EXAMPLZ', 'feature' => 'OH HAI', 'given' => 'I CAN HAZ|*', 'name' => 'LOLCAT', 'native' => 'LOLCAT', 'scenario' => 'MISHUN', 'scenario_outline' => 'MISHUN SRSLY', 'then' => 'DEN|*', 'when' => 'WEN|*', ), 'en-old' => array ( 'and' => 'Ond|*|7', 'background' => 'Aer|Ær', 'but' => 'Ac|*', 'examples' => 'Se the|Se þe|Se ðe', 'feature' => 'Hwaet|Hwæt', 'given' => 'Thurh|Þurh|Ðurh|*', 'name' => 'Old English', 'native' => 'Englisc', 'scenario' => 'Swa', 'scenario_outline' => 'Swa hwaer swa|Swa hwær swa', 'then' => 'Tha the|Þa þe|Ða ðe|Tha|Þa|Ða|*', 'when' => 'Tha|Þa|Ða|*', ), 'en-pirate' => array ( 'and' => 'Aye|*', 'background' => 'Yo-ho-ho', 'but' => 'Avast!|*', 'examples' => 'Dead men tell no tales', 'feature' => 'Ahoy matey!', 'given' => 'Gangway!|*', 'name' => 'Pirate', 'native' => 'Pirate', 'scenario' => 'Heave to', 'scenario_outline' => 'Shiver me timbers', 'then' => 'Let go and haul|*', 'when' => 'Blimey!|*', ), 'eo' => array ( 'and' => 'Kaj|*', 'background' => 'Fono', 'but' => 'Sed|*', 'examples' => 'Ekzemploj', 'feature' => 'Trajto', 'given' => 'Donitaĵo|Komence|*', 'name' => 'Esperanto', 'native' => 'Esperanto', 'scenario' => 'Scenaro|Kazo', 'scenario_outline' => 'Konturo de la scenaro|Kazo-skizo|Skizo', 'then' => 'Do|*', 'when' => 'Se|*', ), 'es' => array ( 'and' => '*|Y|E', 'background' => 'Antecedentes', 'but' => 'Pero|*', 'examples' => 'Ejemplos', 'feature' => 'Característica', 'given' => 'Dados|Dadas|Dada|Dado|*', 'name' => 'Spanish', 'native' => 'español', 'scenario' => 'Escenario', 'scenario_outline' => 'Esquema del escenario', 'then' => 'Entonces|*', 'when' => 'Cuando|*', ), 'et' => array ( 'and' => 'Ja|*', 'background' => 'Taust', 'but' => 'Kuid|*', 'examples' => 'Juhtumid', 'feature' => 'Omadus', 'given' => 'Eeldades|*', 'name' => 'Estonian', 'native' => 'eesti keel', 'scenario' => 'Stsenaarium', 'scenario_outline' => 'Raamstsenaarium', 'then' => 'Siis|*', 'when' => 'Kui|*', ), 'fa' => array ( 'and' => '*|و', 'background' => 'زمینه', 'but' => 'اما|*', 'examples' => 'نمونه ها', 'feature' => 'وِیژگی', 'given' => 'با فرض|*', 'name' => 'Persian', 'native' => 'فارسی', 'scenario' => 'سناریو', 'scenario_outline' => 'الگوی سناریو', 'then' => 'آنگاه|*', 'when' => 'هنگامی|*', ), 'fi' => array ( 'and' => 'Ja|*', 'background' => 'Tausta', 'but' => 'Mutta|*', 'examples' => 'Tapaukset', 'feature' => 'Ominaisuus', 'given' => 'Oletetaan|*', 'name' => 'Finnish', 'native' => 'suomi', 'scenario' => 'Tapaus', 'scenario_outline' => 'Tapausaihio', 'then' => 'Niin|*', 'when' => 'Kun|*', ), 'fr' => array ( 'and' => 'Et qu\'<|Et que|Et|*', 'background' => 'Contexte', 'but' => 'Mais qu\'<|Mais que|Mais|*', 'examples' => 'Exemples', 'feature' => 'Fonctionnalité', 'given' => 'Etant donné qu\'<|Étant donné qu\'<|Etant donné que|Étant donné que|Etant données|Étant données|Etant donnée|Etant donnés|Étant donnée|Étant donnés|Etant donné|Étant donné|Soit|*', 'name' => 'French', 'native' => 'français', 'scenario' => 'Scénario', 'scenario_outline' => 'Plan du scénario|Plan du Scénario', 'then' => 'Alors|*', 'when' => 'Lorsqu\'<|Lorsque|Quand|*', ), 'ga' => array ( 'and' => 'Agus<|*', 'background' => 'Cúlra', 'but' => 'Ach<|*', 'examples' => 'Samplaí', 'feature' => 'Gné', 'given' => 'Cuir i gcás nach<|Cuir i gcás gur<|Cuir i gcás nár<|Cuir i gcás go<|*', 'name' => 'Irish', 'native' => 'Gaeilge', 'scenario' => 'Cás', 'scenario_outline' => 'Cás Achomair', 'then' => 'Ansin<|*', 'when' => 'Nuair nach<|Nuair nár<|Nuair ba<|Nuair a<|*', ), 'gj' => array ( 'and' => 'અને|*', 'background' => 'બેકગ્રાઉન્ડ', 'but' => 'પણ|*', 'examples' => 'ઉદાહરણો', 'feature' => 'વ્યાપાર જરૂર|ક્ષમતા|લક્ષણ', 'given' => 'આપેલ છે|*', 'name' => 'Gujarati', 'native' => 'ગુજરાતી', 'scenario' => 'સ્થિતિ', 'scenario_outline' => 'પરિદ્દશ્ય રૂપરેખા|પરિદ્દશ્ય ઢાંચો', 'then' => 'પછી|*', 'when' => 'ક્યારે|*', ), 'gl' => array ( 'and' => '*|E', 'background' => 'Contexto', 'but' => 'Pero|Mais|*', 'examples' => 'Exemplos', 'feature' => 'Característica', 'given' => 'Dados|Dadas|Dada|Dado|*', 'name' => 'Galician', 'native' => 'galego', 'scenario' => 'Escenario', 'scenario_outline' => 'Esbozo do escenario', 'then' => 'Entón|Logo|*', 'when' => 'Cando|*', ), 'he' => array ( 'and' => 'וגם|*', 'background' => 'רקע', 'but' => 'אבל|*', 'examples' => 'דוגמאות', 'feature' => 'תכונה', 'given' => 'בהינתן|*', 'name' => 'Hebrew', 'native' => 'עברית', 'scenario' => 'תרחיש', 'scenario_outline' => 'תבנית תרחיש', 'then' => 'אזי|אז|*', 'when' => 'כאשר|*', ), 'hi' => array ( 'and' => 'तथा|और|*', 'background' => 'पृष्ठभूमि', 'but' => 'परन्तु|किन्तु|पर|*', 'examples' => 'उदाहरण', 'feature' => 'रूप लेख', 'given' => 'चूंकि|यदि|अगर|*', 'name' => 'Hindi', 'native' => 'हिंदी', 'scenario' => 'परिदृश्य', 'scenario_outline' => 'परिदृश्य रूपरेखा', 'then' => 'तदा|तब|*', 'when' => 'कदा|जब|*', ), 'hr' => array ( 'and' => '*|I', 'background' => 'Pozadina', 'but' => 'Ali|*', 'examples' => 'Scenariji|Primjeri', 'feature' => 'Mogucnost|Mogućnost|Osobina', 'given' => 'Zadani|Zadano|Zadan|*', 'name' => 'Croatian', 'native' => 'hrvatski', 'scenario' => 'Scenarij', 'scenario_outline' => 'Koncept|Skica', 'then' => 'Onda|*', 'when' => 'Kada|Kad|*', ), 'ht' => array ( 'and' => 'Epi|Ak|*|E', 'background' => 'Kontèks|Istorik', 'but' => 'Men|*', 'examples' => 'Egzanp', 'feature' => 'Karakteristik|Fonksyonalite|Mak', 'given' => 'Sipoze ke|Sipoze Ke|Sipoze|*', 'name' => 'Creole', 'native' => 'kreyòl', 'scenario' => 'Senaryo', 'scenario_outline' => 'Senaryo deskripsyon|Senaryo Deskripsyon|Dyagram senaryo|Dyagram Senaryo|Plan senaryo|Plan Senaryo', 'then' => 'Le sa a|Lè sa a|*', 'when' => 'Le|Lè|*', ), 'hu' => array ( 'and' => 'És|*', 'background' => 'Háttér', 'but' => 'De|*', 'examples' => 'Példák', 'feature' => 'Jellemző', 'given' => 'Amennyiben|Adott|*', 'name' => 'Hungarian', 'native' => 'magyar', 'scenario' => 'Forgatókönyv', 'scenario_outline' => 'Forgatókönyv vázlat', 'then' => 'Akkor|*', 'when' => 'Amikor|Majd|Ha|*', ), 'id' => array ( 'and' => 'Dan|*', 'background' => 'Dasar', 'but' => 'Tapi|*', 'examples' => 'Contoh', 'feature' => 'Fitur', 'given' => 'Dengan|*', 'name' => 'Indonesian', 'native' => 'Bahasa Indonesia', 'scenario' => 'Skenario', 'scenario_outline' => 'Skenario konsep', 'then' => 'Maka|*', 'when' => 'Ketika|*', ), 'is' => array ( 'and' => 'Og|*', 'background' => 'Bakgrunnur', 'but' => 'En|*', 'examples' => 'Atburðarásir|Dæmi', 'feature' => 'Eiginleiki', 'given' => 'Ef|*', 'name' => 'Icelandic', 'native' => 'Íslenska', 'scenario' => 'Atburðarás', 'scenario_outline' => 'Lýsing Atburðarásar|Lýsing Dæma', 'then' => 'Þá|*', 'when' => 'Þegar|*', ), 'it' => array ( 'and' => '*|E', 'background' => 'Contesto', 'but' => 'Ma|*', 'examples' => 'Esempi', 'feature' => 'Funzionalità', 'given' => 'Data|Dato|Dati|Date|*', 'name' => 'Italian', 'native' => 'italiano', 'scenario' => 'Scenario', 'scenario_outline' => 'Schema dello scenario', 'then' => 'Allora|*', 'when' => 'Quando|*', ), 'ja' => array ( 'and' => 'かつ<|*', 'background' => '背景', 'but' => 'しかし<|ただし<|但し<|*', 'examples' => 'サンプル|例', 'feature' => 'フィーチャ|機能', 'given' => '前提<|*', 'name' => 'Japanese', 'native' => '日本語', 'scenario' => 'シナリオ', 'scenario_outline' => 'シナリオアウトライン|シナリオテンプレート|シナリオテンプレ|テンプレ', 'then' => 'ならば<|*', 'when' => 'もし<|*', ), 'jv' => array ( 'and' => 'Lan|*', 'background' => 'Dasar', 'but' => 'Ananging|Nanging|Tapi|*', 'examples' => 'Contone|Conto', 'feature' => 'Fitur', 'given' => 'Nalikaning|Nalika|*', 'name' => 'Javanese', 'native' => 'Basa Jawa', 'scenario' => 'Skenario', 'scenario_outline' => 'Konsep skenario', 'then' => 'Banjur|Njuk|*', 'when' => 'Menawa|Manawa|*', ), 'kn' => array ( 'and' => 'ಮತ್ತು|*', 'background' => 'ಹಿನ್ನೆಲೆ', 'but' => 'ಆದರೆ|*', 'examples' => 'ಉದಾಹರಣೆಗಳು', 'feature' => 'ಹೆಚ್ಚಳ', 'given' => 'ನೀಡಿದ|*', 'name' => 'Kannada', 'native' => 'ಕನ್ನಡ', 'scenario' => 'ಕಥಾಸಾರಾಂಶ', 'scenario_outline' => 'ವಿವರಣೆ', 'then' => 'ನಂತರ|*', 'when' => 'ಸ್ಥಿತಿಯನ್ನು|*', ), 'ko' => array ( 'and' => '그리고<|*', 'background' => '배경', 'but' => '하지만<|단<|*', 'examples' => '예', 'feature' => '기능', 'given' => '먼저<|조건<|*', 'name' => 'Korean', 'native' => '한국어', 'scenario' => '시나리오', 'scenario_outline' => '시나리오 개요', 'then' => '그러면<|*', 'when' => '만약<|만일<|*', ), 'lt' => array ( 'and' => 'Ir|*', 'background' => 'Kontekstas', 'but' => 'Bet|*', 'examples' => 'Pavyzdžiai|Scenarijai|Variantai', 'feature' => 'Savybė', 'given' => 'Duota|*', 'name' => 'Lithuanian', 'native' => 'lietuvių kalba', 'scenario' => 'Scenarijus', 'scenario_outline' => 'Scenarijaus šablonas', 'then' => 'Tada|*', 'when' => 'Kai|*', ), 'lu' => array ( 'and' => 'an|*|a', 'background' => 'Hannergrond', 'but' => 'awer|mä|*', 'examples' => 'Beispiller', 'feature' => 'Funktionalitéit', 'given' => 'ugeholl|*', 'name' => 'Luxemburgish', 'native' => 'Lëtzebuergesch', 'scenario' => 'Szenario', 'scenario_outline' => 'Plang vum Szenario', 'then' => 'dann|*', 'when' => 'wann|*', ), 'lv' => array ( 'and' => 'Un|*', 'background' => 'Konteksts|Situācija', 'but' => 'Bet|*', 'examples' => 'Piemēri|Paraugs', 'feature' => 'Funkcionalitāte|Fīča', 'given' => 'Kad|*', 'name' => 'Latvian', 'native' => 'latviešu', 'scenario' => 'Scenārijs', 'scenario_outline' => 'Scenārijs pēc parauga', 'then' => 'Tad|*', 'when' => 'Ja|*', ), 'mn' => array ( 'and' => 'Тэгээд|Мөн|*', 'background' => 'Агуулга', 'but' => 'Гэхдээ|Харин|*', 'examples' => 'Тухайлбал', 'feature' => 'Функционал|Функц', 'given' => 'Өгөгдсөн нь|Анх|*', 'name' => 'Mongolian', 'native' => 'монгол', 'scenario' => 'Сценар', 'scenario_outline' => 'Сценарын төлөвлөгөө', 'then' => 'Үүний дараа|Тэгэхэд|*', 'when' => 'Хэрэв|*', ), 'nl' => array ( 'and' => 'En|*', 'background' => 'Achtergrond', 'but' => 'Maar|*', 'examples' => 'Voorbeelden', 'feature' => 'Functionaliteit', 'given' => 'Gegeven|Stel|*', 'name' => 'Dutch', 'native' => 'Nederlands', 'scenario' => 'Scenario', 'scenario_outline' => 'Abstract Scenario', 'then' => 'Dan|*', 'when' => 'Als|*', ), 'no' => array ( 'and' => 'Og|*', 'background' => 'Bakgrunn', 'but' => 'Men|*', 'examples' => 'Eksempler', 'feature' => 'Egenskap', 'given' => 'Gitt|*', 'name' => 'Norwegian', 'native' => 'norsk', 'scenario' => 'Scenario', 'scenario_outline' => 'Abstrakt Scenario|Scenariomal', 'then' => 'Så|*', 'when' => 'Når|*', ), 'pa' => array ( 'and' => 'ਅਤੇ|*', 'background' => 'ਪਿਛੋਕੜ', 'but' => 'ਪਰ|*', 'examples' => 'ਉਦਾਹਰਨਾਂ', 'feature' => 'ਨਕਸ਼ ਨੁਹਾਰ|ਮੁਹਾਂਦਰਾ|ਖਾਸੀਅਤ', 'given' => 'ਜਿਵੇਂ ਕਿ|ਜੇਕਰ|*', 'name' => 'Panjabi', 'native' => 'ਪੰਜਾਬੀ', 'scenario' => 'ਪਟਕਥਾ', 'scenario_outline' => 'ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ|ਪਟਕਥਾ ਢਾਂਚਾ', 'then' => 'ਤਦ|*', 'when' => 'ਜਦੋਂ|*', ), 'pl' => array ( 'and' => 'Oraz|*|I', 'background' => 'Założenia', 'but' => 'Ale|*', 'examples' => 'Przykłady', 'feature' => 'Potrzeba biznesowa|Właściwość|Funkcja|Aspekt', 'given' => 'Zakładając, że|Zakładając|Mając|*', 'name' => 'Polish', 'native' => 'polski', 'scenario' => 'Scenariusz', 'scenario_outline' => 'Szablon scenariusza', 'then' => 'Wtedy|*', 'when' => 'Jeżeli|Jeśli|Kiedy|Gdy|*', ), 'pt' => array ( 'and' => '*|E', 'background' => 'Cenario de Fundo|Cenário de Fundo|Contexto|Fundo', 'but' => 'Mas|*', 'examples' => 'Exemplos|Cenários|Cenarios', 'feature' => 'Funcionalidade|Característica|Caracteristica', 'given' => 'Dados|Dadas|Dada|Dado|*', 'name' => 'Portuguese', 'native' => 'português', 'scenario' => 'Cenário|Cenario', 'scenario_outline' => 'Delineação do Cenário|Delineacao do Cenario|Esquema do Cenário|Esquema do Cenario', 'then' => 'Entao|Então|*', 'when' => 'Quando|*', ), 'ro' => array ( 'and' => 'Și|Si|Şi|*', 'background' => 'Context', 'but' => 'Dar|*', 'examples' => 'Exemple', 'feature' => 'Functionalitate|Funcționalitate|Funcţionalitate', 'given' => 'Date fiind|Dati fiind|Dați fiind|Daţi fiind|Dat fiind|*', 'name' => 'Romanian', 'native' => 'română', 'scenario' => 'Scenariu', 'scenario_outline' => 'Structura scenariu|Structură scenariu', 'then' => 'Atunci|*', 'when' => 'Când|Cand|*', ), 'ru' => array ( 'and' => 'К тому же|Также|*|И', 'background' => 'Предыстория|Контекст', 'but' => 'Но|*|А', 'examples' => 'Примеры', 'feature' => 'Функциональность|Функционал|Свойство|Функция', 'given' => 'Допустим|Пусть|Дано|*', 'name' => 'Russian', 'native' => 'русский', 'scenario' => 'Сценарий', 'scenario_outline' => 'Структура сценария', 'then' => 'Тогда|То|*', 'when' => 'Когда|Если|*', ), 'sk' => array ( 'and' => 'A taktiež|A zároveň|A tiež|*|A', 'background' => 'Pozadie', 'but' => 'Ale|*', 'examples' => 'Príklady', 'feature' => 'Požiadavka|Vlastnosť|Funkcia', 'given' => 'Za predpokladu|Pokiaľ|*', 'name' => 'Slovak', 'native' => 'Slovensky', 'scenario' => 'Scenár', 'scenario_outline' => 'Osnova Scenára|Náčrt Scenáru|Náčrt Scenára', 'then' => 'Potom|Tak|*', 'when' => 'Keď|Ak|*', ), 'sl' => array ( 'and' => 'Ter|In', 'background' => 'Kontekst|Osnova|Ozadje', 'but' => 'Vendar|Ampak|Toda', 'examples' => 'Scenariji|Primeri', 'feature' => 'Funkcionalnost|Značilnost|Funkcija|Možnosti|Moznosti|Lastnost', 'given' => 'Privzeto|Zaradi|Podano|Dano', 'name' => 'Slovenian', 'native' => 'Slovenski', 'scenario' => 'Scenarij|Primer', 'scenario_outline' => 'Struktura scenarija|Oris scenarija|Koncept|Osnutek|Skica', 'then' => 'Takrat|Potem|Nato', 'when' => 'Kadar|Ko|Ce|Če', ), 'sr-Cyrl' => array ( 'and' => '*|И', 'background' => 'Контекст|Позадина|Основа', 'but' => 'Али|*', 'examples' => 'Сценарији|Примери', 'feature' => 'Функционалност|Могућност|Особина', 'given' => 'За дате|За дато|За дати|*', 'name' => 'Serbian', 'native' => 'Српски', 'scenario' => 'Сценарио|Пример', 'scenario_outline' => 'Структура сценарија|Концепт|Скица', 'then' => 'Онда|*', 'when' => 'Када|Кад|*', ), 'sr-Latn' => array ( 'and' => '*|I', 'background' => 'Kontekst|Pozadina|Osnova', 'but' => 'Ali|*', 'examples' => 'Scenariji|Primeri', 'feature' => 'Funkcionalnost|Mogućnost|Mogucnost|Osobina', 'given' => 'Za date|Za dato|Za dati|*', 'name' => 'Serbian (Latin)', 'native' => 'Srpski (Latinica)', 'scenario' => 'Scenario|Primer', 'scenario_outline' => 'Struktura scenarija|Koncept|Skica', 'then' => 'Onda|*', 'when' => 'Kada|Kad|*', ), 'sv' => array ( 'and' => 'Och|*', 'background' => 'Bakgrund', 'but' => 'Men|*', 'examples' => 'Exempel', 'feature' => 'Egenskap', 'given' => 'Givet|*', 'name' => 'Swedish', 'native' => 'Svenska', 'scenario' => 'Scenario', 'scenario_outline' => 'Abstrakt Scenario|Scenariomall', 'then' => 'Så|*', 'when' => 'När|*', ), 'ta' => array ( 'and' => 'மற்றும்|மேலும்|*', 'background' => 'பின்னணி', 'but' => 'ஆனால்|*', 'examples' => 'எடுத்துக்காட்டுகள்| நிலைமைகளில்|காட்சிகள்', 'feature' => 'வணிக தேவை|அம்சம்|திறன்', 'given' => 'கொடுக்கப்பட்ட|*', 'name' => 'Tamil', 'native' => 'தமிழ்', 'scenario' => 'காட்சி', 'scenario_outline' => 'காட்சி வார்ப்புரு|காட்சி சுருக்கம்', 'then' => 'அப்பொழுது|*', 'when' => 'எப்போது|*', ), 'th' => array ( 'and' => 'และ|*', 'background' => 'แนวคิด', 'but' => 'แต่|*', 'examples' => 'ชุดของเหตุการณ์|ชุดของตัวอย่าง', 'feature' => 'ความต้องการทางธุรกิจ|ความสามารถ|โครงหลัก', 'given' => 'กำหนดให้|*', 'name' => 'Thai', 'native' => 'ไทย', 'scenario' => 'เหตุการณ์', 'scenario_outline' => 'โครงสร้างของเหตุการณ์|สรุปเหตุการณ์', 'then' => 'ดังนั้น|*', 'when' => 'เมื่อ|*', ), 'tl' => array ( 'and' => 'మరియు|*', 'background' => 'నేపథ్యం', 'but' => 'కాని|*', 'examples' => 'ఉదాహరణలు', 'feature' => 'గుణము', 'given' => 'చెప్పబడినది|*', 'name' => 'Telugu', 'native' => 'తెలుగు', 'scenario' => 'సన్నివేశం', 'scenario_outline' => 'కథనం', 'then' => 'అప్పుడు|*', 'when' => 'ఈ పరిస్థితిలో|*', ), 'tlh' => array ( 'and' => 'latlh|\'ej|*', 'background' => 'mo\'', 'but' => '\'ach|\'a|*', 'examples' => 'ghantoH|lutmey', 'feature' => 'poQbogh malja\'|Qu\'meH \'ut|perbogh|Qap|laH', 'given' => 'DaH ghu\' bejlu\'|ghu\' noblu\'|*', 'name' => 'Klingon', 'native' => 'tlhIngan', 'scenario' => 'lut', 'scenario_outline' => 'lut chovnatlh', 'then' => 'vaj|*', 'when' => 'qaSDI\'|*', ), 'tr' => array ( 'and' => 'Ve|*', 'background' => 'Geçmiş', 'but' => 'Fakat|Ama|*', 'examples' => 'Örnekler', 'feature' => 'Özellik', 'given' => 'Diyelim ki|*', 'name' => 'Turkish', 'native' => 'Türkçe', 'scenario' => 'Senaryo', 'scenario_outline' => 'Senaryo taslağı', 'then' => 'O zaman|*', 'when' => 'Eğer ki|*', ), 'tt' => array ( 'and' => 'Һәм|Вә|*', 'background' => 'Кереш', 'but' => 'Ләкин|Әмма|*', 'examples' => 'Үрнәкләр|Мисаллар', 'feature' => 'Үзенчәлеклелек|Мөмкинлек', 'given' => 'Әйтик|*', 'name' => 'Tatar', 'native' => 'Татарча', 'scenario' => 'Сценарий', 'scenario_outline' => 'Сценарийның төзелеше', 'then' => 'Нәтиҗәдә|*', 'when' => 'Әгәр|*', ), 'uk' => array ( 'and' => 'А також|Та|*|І', 'background' => 'Передумова', 'but' => 'Але|*', 'examples' => 'Приклади', 'feature' => 'Функціонал', 'given' => 'Припустимо, що|Припустимо|Нехай|Дано|*', 'name' => 'Ukrainian', 'native' => 'Українська', 'scenario' => 'Сценарій', 'scenario_outline' => 'Структура сценарію', 'then' => 'Тоді|То|*', 'when' => 'Коли|Якщо|*', ), 'ur' => array ( 'and' => 'اور|*', 'background' => 'پس منظر', 'but' => 'لیکن|*', 'examples' => 'مثالیں', 'feature' => 'کاروبار کی ضرورت|صلاحیت|خصوصیت', 'given' => 'فرض کیا|بالفرض|اگر|*', 'name' => 'Urdu', 'native' => 'اردو', 'scenario' => 'منظرنامہ', 'scenario_outline' => 'منظر نامے کا خاکہ', 'then' => 'پھر|تب|*', 'when' => 'جب|*', ), 'uz' => array ( 'and' => 'Ва|*', 'background' => 'Тарих', 'but' => 'Бирок|Лекин|Аммо|*', 'examples' => 'Мисоллар', 'feature' => 'Функционал', 'given' => 'Агар|*', 'name' => 'Uzbek', 'native' => 'Узбекча', 'scenario' => 'Сценарий', 'scenario_outline' => 'Сценарий структураси', 'then' => 'Унда|*', 'when' => 'Агар|*', ), 'vi' => array ( 'and' => 'Và|*', 'background' => 'Bối cảnh', 'but' => 'Nhưng|*', 'examples' => 'Dữ liệu', 'feature' => 'Tính năng', 'given' => 'Biết|Cho|*', 'name' => 'Vietnamese', 'native' => 'Tiếng Việt', 'scenario' => 'Tình huống|Kịch bản', 'scenario_outline' => 'Khung tình huống|Khung kịch bản', 'then' => 'Thì|*', 'when' => 'Khi|*', ), 'zh-CN' => array ( 'and' => '并且<|而且<|同时<|*', 'background' => '背景', 'but' => '但是<|*', 'examples' => '例子', 'feature' => '功能', 'given' => '假设<|假如<|假定<|*', 'name' => 'Chinese simplified', 'native' => '简体中文', 'scenario' => '场景|剧本', 'scenario_outline' => '场景大纲|剧本大纲', 'then' => '那么<|*', 'when' => '当<|*', ), 'zh-TW' => array ( 'and' => '並且<|而且<|同時<|*', 'background' => '背景', 'but' => '但是<|*', 'examples' => '例子', 'feature' => '功能', 'given' => '假設<|假如<|假定<|*', 'name' => 'Chinese traditional', 'native' => '繁體中文', 'scenario' => '場景|劇本', 'scenario_outline' => '場景大綱|劇本大綱', 'then' => '那麼<|*', 'when' => '當<|*', ), );keywords = $keywords; } public function analyse($input, $language = 'en') { if ('UTF-8' !== mb_detect_encoding($input, 'UTF-8', true)) { throw new LexerException('Feature file is not in UTF8 encoding'); } $input = strtr($input, array("\r\n" => "\n", "\r" => "\n")); $this->lines = explode("\n", $input); $this->linesCount = count($this->lines); $this->line = $this->lines[0]; $this->lineNumber = 1; $this->trimmedLine = null; $this->eos = false; $this->deferredObjects = array(); $this->deferredObjectsCount = 0; $this->stashedToken = null; $this->inPyString = false; $this->pyStringSwallow = 0; $this->featureStarted = false; $this->allowMultilineArguments = false; $this->allowSteps = false; $this->keywords->setLanguage($this->language = $language); $this->keywordsCache = array(); $this->stepKeywordTypesCache = array(); } public function getLanguage() { return $this->language; } public function getAdvancedToken() { return $this->getStashedToken() ?: $this->getNextToken(); } public function deferToken(array $token) { $token['deferred'] = true; $this->deferredObjects[] = $token; ++$this->deferredObjectsCount; } public function predictToken() { if (null === $this->stashedToken) { $this->stashedToken = $this->getNextToken(); } return $this->stashedToken; } public function takeToken($type, $value = null) { return array( 'type' => $type, 'line' => $this->lineNumber, 'value' => $value ?: null, 'deferred' => false ); } protected function consumeLine() { ++$this->lineNumber; if (($this->lineNumber - 1) === $this->linesCount) { $this->eos = true; return; } $this->line = $this->lines[$this->lineNumber - 1]; $this->trimmedLine = null; } protected function getTrimmedLine() { return null !== $this->trimmedLine ? $this->trimmedLine : $this->trimmedLine = trim($this->line); } protected function getStashedToken() { $stashedToken = $this->stashedToken; $this->stashedToken = null; return $stashedToken; } protected function getDeferredToken() { if (!$this->deferredObjectsCount) { return null; } --$this->deferredObjectsCount; return array_shift($this->deferredObjects); } protected function getNextToken() { return $this->getDeferredToken() ?: $this->scanEOS() ?: $this->scanLanguage() ?: $this->scanComment() ?: $this->scanPyStringOp() ?: $this->scanPyStringContent() ?: $this->scanStep() ?: $this->scanScenario() ?: $this->scanBackground() ?: $this->scanOutline() ?: $this->scanExamples() ?: $this->scanFeature() ?: $this->scanTags() ?: $this->scanTableRow() ?: $this->scanNewline() ?: $this->scanText(); } protected function scanInput($regex, $type) { if (!preg_match($regex, $this->line, $matches)) { return null; } $token = $this->takeToken($type, $matches[1]); $this->consumeLine(); return $token; } protected function scanInputForKeywords($keywords, $type) { if (!preg_match('/^(\s*)(' . $keywords . '):\s*(.*)/u', $this->line, $matches)) { return null; } $token = $this->takeToken($type, $matches[3]); $token['keyword'] = $matches[2]; $token['indent'] = mb_strlen($matches[1], 'utf8'); $this->consumeLine(); if ('Feature' === $type) { $this->featureStarted = true; } if ('Feature' === $type || 'Scenario' === $type || 'Outline' === $type) { $this->allowMultilineArguments = false; } elseif ('Examples' === $type) { $this->allowMultilineArguments = true; } if ('Scenario' === $type || 'Background' === $type || 'Outline' === $type) { $this->allowSteps = true; } return $token; } protected function scanEOS() { if (!$this->eos) { return null; } return $this->takeToken('EOS'); } protected function getKeywords($type) { if (!isset($this->keywordsCache[$type])) { $getter = 'get' . $type . 'Keywords'; $keywords = $this->keywords->$getter(); if ('Step' === $type) { $padded = array(); foreach (explode('|', $keywords) as $keyword) { $padded[] = false !== mb_strpos($keyword, '<', 0, 'utf8') ? preg_quote(mb_substr($keyword, 0, -1, 'utf8'), '/') . '\s*' : preg_quote($keyword, '/') . '\s+'; } $keywords = implode('|', $padded); } $this->keywordsCache[$type] = $keywords; } return $this->keywordsCache[$type]; } protected function scanFeature() { return $this->scanInputForKeywords($this->getKeywords('Feature'), 'Feature'); } protected function scanBackground() { return $this->scanInputForKeywords($this->getKeywords('Background'), 'Background'); } protected function scanScenario() { return $this->scanInputForKeywords($this->getKeywords('Scenario'), 'Scenario'); } protected function scanOutline() { return $this->scanInputForKeywords($this->getKeywords('Outline'), 'Outline'); } protected function scanExamples() { return $this->scanInputForKeywords($this->getKeywords('Examples'), 'Examples'); } protected function scanStep() { if (!$this->allowSteps) { return null; } $keywords = $this->getKeywords('Step'); if (!preg_match('/^\s*(' . $keywords . ')([^\s].+)/u', $this->line, $matches)) { return null; } $keyword = trim($matches[1]); $token = $this->takeToken('Step', $keyword); $token['keyword_type'] = $this->getStepKeywordType($keyword); $token['text'] = $matches[2]; $this->consumeLine(); $this->allowMultilineArguments = true; return $token; } protected function scanPyStringOp() { if (!$this->allowMultilineArguments) { return null; } if (false === ($pos = mb_strpos($this->line, '"""', 0, 'utf8'))) { return null; } $this->inPyString = !$this->inPyString; $token = $this->takeToken('PyStringOp'); $this->pyStringSwallow = $pos; $this->consumeLine(); return $token; } protected function scanPyStringContent() { if (!$this->inPyString) { return null; } $token = $this->scanText(); $token['value'] = preg_replace('/^\s{0,' . $this->pyStringSwallow . '}/u', '', $token['value']); return $token; } protected function scanTableRow() { if (!$this->allowMultilineArguments) { return null; } $line = $this->getTrimmedLine(); if (!isset($line[0]) || '|' !== $line[0] || '|' !== substr($line, -1)) { return null; } $token = $this->takeToken('TableRow'); $line = mb_substr($line, 1, mb_strlen($line, 'utf8') - 2, 'utf8'); $columns = array_map(function ($column) { return trim(str_replace('\\|', '|', $column)); }, preg_split('/(?consumeLine(); return $token; } protected function scanTags() { $line = $this->getTrimmedLine(); if (!isset($line[0]) || '@' !== $line[0]) { return null; } $token = $this->takeToken('Tag'); $tags = explode('@', mb_substr($line, 1, mb_strlen($line, 'utf8') - 1, 'utf8')); $tags = array_map('trim', $tags); $token['tags'] = $tags; $this->consumeLine(); return $token; } protected function scanLanguage() { if ($this->featureStarted) { return null; } if ($this->inPyString) { return null; } if (0 !== mb_strpos(ltrim($this->line), '#', 0, 'utf8')) { return null; } return $this->scanInput('/^\s*\#\s*language:\s*([\w_\-]+)\s*$/', 'Language'); } protected function scanComment() { if ($this->inPyString) { return null; } $line = $this->getTrimmedLine(); if (0 !== mb_strpos($line, '#', 0, 'utf8')) { return null; } $token = $this->takeToken('Comment', $line); $this->consumeLine(); return $token; } protected function scanNewline() { if ('' !== $this->getTrimmedLine()) { return null; } $token = $this->takeToken('Newline', mb_strlen($this->line, 'utf8')); $this->consumeLine(); return $token; } protected function scanText() { $token = $this->takeToken('Text', $this->line); $this->consumeLine(); return $token; } private function getStepKeywordType($native) { if ('*' === $native) { return 'And'; } if (empty($this->stepKeywordTypesCache)) { $this->stepKeywordTypesCache = array( 'Given' => explode('|', $this->keywords->getGivenKeywords()), 'When' => explode('|', $this->keywords->getWhenKeywords()), 'Then' => explode('|', $this->keywords->getThenKeywords()), 'And' => explode('|', $this->keywords->getAndKeywords()), 'But' => explode('|', $this->keywords->getButKeywords()) ); } foreach ($this->stepKeywordTypesCache as $type => $keywords) { if (in_array($native, $keywords) || in_array($native . '<', $keywords)) { return $type; } } return 'Given'; } } 1) { throw new NodeException(sprintf( 'Steps could have only one argument, but `%s %s` have %d.', $keyword, $text, count($arguments) )); } $this->keyword = $keyword; $this->text = $text; $this->arguments = $arguments; $this->line = $line; $this->keywordType = $keywordType ?: 'Given'; } public function getNodeType() { return 'Step'; } public function getType() { return $this->getKeyword(); } public function getKeyword() { return $this->keyword; } public function getKeywordType() { return $this->keywordType; } public function getText() { return $this->text; } public function hasArguments() { return 0 < count($this->arguments); } public function getArguments() { return $this->arguments; } public function getLine() { return $this->line; } } title = $title; $this->tags = $tags; $this->outlineSteps = $outlineSteps; $this->tokens = $tokens; $this->line = $line; } public function getNodeType() { return 'Example'; } public function getKeyword() { return $this->getNodeType(); } public function getTitle() { return $this->title; } public function hasTag($tag) { return in_array($tag, $this->getTags()); } public function hasTags() { return 0 < count($this->getTags()); } public function getTags() { return $this->tags; } public function hasSteps() { return 0 < count($this->outlineSteps); } public function getSteps() { return $this->steps = $this->steps ? : $this->createExampleSteps(); } public function getTokens() { return $this->tokens; } public function getLine() { return $this->line; } protected function createExampleSteps() { $steps = array(); foreach ($this->outlineSteps as $outlineStep) { $keyword = $outlineStep->getKeyword(); $keywordType = $outlineStep->getKeywordType(); $text = $this->replaceTextTokens($outlineStep->getText()); $args = $this->replaceArgumentsTokens($outlineStep->getArguments()); $line = $outlineStep->getLine(); $steps[] = new StepNode($keyword, $text, $args, $line, $keywordType); } return $steps; } protected function replaceArgumentsTokens(array $arguments) { foreach ($arguments as $num => $argument) { if ($argument instanceof TableNode) { $arguments[$num] = $this->replaceTableArgumentTokens($argument); } if ($argument instanceof PyStringNode) { $arguments[$num] = $this->replacePyStringArgumentTokens($argument); } } return $arguments; } protected function replaceTableArgumentTokens(TableNode $argument) { $table = $argument->getTable(); foreach ($table as $line => $row) { foreach (array_keys($row) as $col) { $table[$line][$col] = $this->replaceTextTokens($table[$line][$col]); } } return new TableNode($table); } protected function replacePyStringArgumentTokens(PyStringNode $argument) { $strings = $argument->getStrings(); foreach ($strings as $line => $string) { $strings[$line] = $this->replaceTextTokens($strings[$line]); } return new PyStringNode($strings, $argument->getLine()); } protected function replaceTextTokens($text) { foreach ($this->tokens as $key => $val) { $text = str_replace('<' . $key . '>', $val, $text); } return $text; } } table = $table; $columnCount = null; foreach ($this->getRows() as $row) { if ($columnCount === null) { $columnCount = count($row); } if (count($row) !== $columnCount) { throw new NodeException('Table does not have same number of columns in every row.'); } foreach ($row as $column => $string) { if (!isset($this->maxLineLength[$column])) { $this->maxLineLength[$column] = 0; } $this->maxLineLength[$column] = max($this->maxLineLength[$column], mb_strlen($string, 'utf8')); } } } public function getNodeType() { return 'Table'; } public function getHash() { return $this->getColumnsHash(); } public function getColumnsHash() { $rows = $this->getRows(); $keys = array_shift($rows); $hash = array(); foreach ($rows as $row) { $hash[] = array_combine($keys, $row); } return $hash; } public function getRowsHash() { $hash = array(); foreach ($this->getRows() as $row) { $hash[array_shift($row)] = (1 == count($row)) ? $row[0] : $row; } return $hash; } public function getTable() { return $this->table; } public function getRows() { return array_values($this->table); } public function getLines() { return array_keys($this->table); } public function getRow($index) { $rows = $this->getRows(); if (!isset($rows[$index])) { throw new NodeException(sprintf('Rows #%d does not exist in table.', $index)); } return $rows[$index]; } public function getColumn($index) { if ($index >= count($this->getRow(0))) { throw new NodeException(sprintf('Column #%d does not exist in table.', $index)); } $rows = $this->getRows(); $column = array(); foreach ($rows as $row) { $column[] = $row[$index]; } return $column; } public function getRowLine($index) { $lines = array_keys($this->table); if (!isset($lines[$index])) { throw new NodeException(sprintf('Rows #%d does not exist in table.', $index)); } return $lines[$index]; } public function getRowAsString($rowNum) { $values = array(); foreach ($this->getRow($rowNum) as $column => $value) { $values[] = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2); } return sprintf('|%s|', implode('|', $values)); } public function getRowAsStringWithWrappedValues($rowNum, $wrapper) { $values = array(); foreach ($this->getRow($rowNum) as $column => $value) { $value = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2); $values[] = call_user_func($wrapper, $value, $column); } return sprintf('|%s|', implode('|', $values)); } public function getTableAsString() { $lines = array(); for ($i = 0; $i < count($this->getRows()); $i++) { $lines[] = $this->getRowAsString($i); } return implode("\n", $lines); } public function getLine() { return $this->getRowLine(0); } public function __toString() { return $this->getTableAsString(); } public function getIterator() { return new ArrayIterator($this->getHash()); } protected function padRight($text, $length) { while ($length > mb_strlen($text, 'utf8')) { $text = $text . ' '; } return $text; } } title = $title; $this->steps = $steps; $this->keyword = $keyword; $this->line = $line; } public function getNodeType() { return 'Background'; } public function getTitle() { return $this->title; } public function hasSteps() { return 0 < count($this->steps); } public function getSteps() { return $this->steps; } public function getKeyword() { return $this->keyword; } public function getLine() { return $this->line; } } strings = $strings; $this->line = $line; } public function getNodeType() { return 'PyString'; } public function getStrings() { return $this->strings; } public function getRaw() { return implode("\n", $this->strings); } public function __toString() { return $this->getRaw(); } public function getLine() { return $this->line; } } title = $title; $this->tags = $tags; $this->steps = $steps; $this->keyword = $keyword; $this->line = $line; } public function getNodeType() { return 'Scenario'; } public function getTitle() { return $this->title; } public function hasTag($tag) { return in_array($tag, $this->getTags()); } public function hasTags() { return 0 < count($this->getTags()); } public function getTags() { return $this->tags; } public function hasSteps() { return 0 < count($this->steps); } public function getSteps() { return $this->steps; } public function getKeyword() { return $this->keyword; } public function getLine() { return $this->line; } } keyword = $keyword; parent::__construct($table); } public function getNodeType() { return 'ExampleTable'; } public function getKeyword() { return $this->keyword; } } title = $title; $this->tags = $tags; $this->steps = $steps; $this->table = $table; $this->keyword = $keyword; $this->line = $line; } public function getNodeType() { return 'Outline'; } public function getTitle() { return $this->title; } public function hasTag($tag) { return in_array($tag, $this->getTags()); } public function hasTags() { return 0 < count($this->getTags()); } public function getTags() { return $this->tags; } public function hasSteps() { return 0 < count($this->steps); } public function getSteps() { return $this->steps; } public function hasExamples() { return 0 < count($this->table->getColumnsHash()); } public function getExampleTable() { return $this->table; } public function getExamples() { return $this->examples = $this->examples ? : $this->createExamples(); } public function getKeyword() { return $this->keyword; } public function getLine() { return $this->line; } protected function createExamples() { $examples = array(); foreach ($this->table->getColumnsHash() as $rowNum => $row) { $examples[] = new ExampleNode( $this->table->getRowAsString($rowNum + 1), $this->tags, $this->getSteps(), $row, $this->table->getRowLine($rowNum + 1) ); } return $examples; } } title = $title; $this->description = $description; $this->tags = $tags; $this->background = $background; $this->scenarios = $scenarios; $this->keyword = $keyword; $this->language = $language; $this->file = $file; $this->line = $line; } public function getNodeType() { return 'Feature'; } public function getTitle() { return $this->title; } public function hasDescription() { return !empty($this->description); } public function getDescription() { return $this->description; } public function hasTag($tag) { return in_array($tag, $this->tags); } public function hasTags() { return 0 < count($this->tags); } public function getTags() { return $this->tags; } public function hasBackground() { return null !== $this->background; } public function getBackground() { return $this->background; } public function hasScenarios() { return 0 < count($this->scenarios); } public function getScenarios() { return $this->scenarios; } public function getKeyword() { return $this->keyword; } public function getLanguage() { return $this->language; } public function getFile() { return $this->file; } public function getLine() { return $this->line; } } features[$path])) { return false; } return $this->timestamps[$path] > $timestamp; } public function read($path) { return $this->features[$path]; } public function write($path, FeatureNode $feature) { $this->features[$path] = $feature; $this->timestamps[$path] = time(); } } path = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'v'.Gherkin::VERSION; if (!is_dir($this->path)) { @mkdir($this->path, 0777, true); } if (!is_writeable($this->path)) { throw new CacheException(sprintf('Cache path "%s" is not writeable. Check your filesystem permissions or disable Gherkin file cache.', $this->path)); } } public function isFresh($path, $timestamp) { $cachePath = $this->getCachePathFor($path); if (!file_exists($cachePath)) { return false; } return filemtime($cachePath) > $timestamp; } public function read($path) { $cachePath = $this->getCachePathFor($path); $feature = unserialize(file_get_contents($cachePath)); if (!$feature instanceof FeatureNode) { throw new CacheException(sprintf('Can not load cache for a feature "%s" from "%s".', $path, $cachePath )); } return $feature; } public function write($path, FeatureNode $feature) { file_put_contents($this->getCachePathFor($path), serialize($feature)); } protected function getCachePathFor($path) { return $this->path.'/'.md5($path).'.feature.cache'; } } keywords = $keywords; } public function setLanguage($language) { if (!isset($this->keywords[$language])) { $this->language = 'en'; } else { $this->language = $language; } } public function getFeatureKeywords() { return $this->keywords[$this->language]['feature']; } public function getBackgroundKeywords() { return $this->keywords[$this->language]['background']; } public function getScenarioKeywords() { return $this->keywords[$this->language]['scenario']; } public function getOutlineKeywords() { return $this->keywords[$this->language]['scenario_outline']; } public function getExamplesKeywords() { return $this->keywords[$this->language]['examples']; } public function getGivenKeywords() { return $this->keywords[$this->language]['given']; } public function getWhenKeywords() { return $this->keywords[$this->language]['when']; } public function getThenKeywords() { return $this->keywords[$this->language]['then']; } public function getAndKeywords() { return $this->keywords[$this->language]['and']; } public function getButKeywords() { return $this->keywords[$this->language]['but']; } public function getStepKeywords() { if (!isset($this->keywordString[$this->language])) { $keywords = array_merge( explode('|', $this->getGivenKeywords()), explode('|', $this->getWhenKeywords()), explode('|', $this->getThenKeywords()), explode('|', $this->getAndKeywords()), explode('|', $this->getButKeywords()) ); usort($keywords, function ($keyword1, $keyword2) { return mb_strlen($keyword2, 'utf8') - mb_strlen($keyword1, 'utf8'); }); $this->keywordString[$this->language] = implode('|', $keywords); } return $this->keywordString[$this->language]; } } setParsedFile($file); } throw $e; } parent::__construct($content); } public function getGivenKeywords() { return $this->prepareStepString(parent::getGivenKeywords()); } public function getWhenKeywords() { return $this->prepareStepString(parent::getWhenKeywords()); } public function getThenKeywords() { return $this->prepareStepString(parent::getThenKeywords()); } public function getAndKeywords() { return $this->prepareStepString(parent::getAndKeywords()); } public function getButKeywords() { return $this->prepareStepString(parent::getButKeywords()); } private function prepareStepString($keywordsString) { if (0 === mb_strpos($keywordsString, '*|', 0, 'UTF-8')) { $keywordsString = mb_substr($keywordsString, 2, mb_strlen($keywordsString, 'utf8') - 2, 'utf8'); } return $keywordsString; } } keywords = $keywords; $this->keywordsDumper = array($this, 'dumpKeywords'); } public function setKeywordsDumperFunction($mapper) { $this->keywordsDumper = $mapper; } public function dumpKeywords(array $keywords, $isShort) { if ($isShort) { return 1 < count($keywords) ? '(' . implode('|', $keywords) . ')' : $keywords[0]; } return $keywords[0]; } public function dump($language, $short = true, $excludeAsterisk = false) { $this->keywords->setLanguage($language); $languageComment = ''; if ('en' !== $language) { $languageComment = "# language: $language\n"; } $keywords = explode('|', $this->keywords->getFeatureKeywords()); if ($short) { $keywords = call_user_func($this->keywordsDumper, $keywords, $short); return trim($languageComment . $this->dumpFeature($keywords, $short, $excludeAsterisk)); } $features = array(); foreach ($keywords as $keyword) { $keyword = call_user_func($this->keywordsDumper, array($keyword), $short); $features[] = trim($languageComment . $this->dumpFeature($keyword, $short, $excludeAsterisk)); } return $features; } protected function dumpFeature($keyword, $short = true, $excludeAsterisk = false) { $dump = <<keywords->getBackgroundKeywords()); if ($short) { $keywords = call_user_func($this->keywordsDumper, $keywords, $short); $dump .= $this->dumpBackground($keywords, $short, $excludeAsterisk); } else { $keyword = call_user_func($this->keywordsDumper, array($keywords[0]), $short); $dump .= $this->dumpBackground($keyword, $short, $excludeAsterisk); } $keywords = explode('|', $this->keywords->getScenarioKeywords()); if ($short) { $keywords = call_user_func($this->keywordsDumper, $keywords, $short); $dump .= $this->dumpScenario($keywords, $short, $excludeAsterisk); } else { foreach ($keywords as $keyword) { $keyword = call_user_func($this->keywordsDumper, array($keyword), $short); $dump .= $this->dumpScenario($keyword, $short, $excludeAsterisk); } } $keywords = explode('|', $this->keywords->getOutlineKeywords()); if ($short) { $keywords = call_user_func($this->keywordsDumper, $keywords, $short); $dump .= $this->dumpOutline($keywords, $short, $excludeAsterisk); } else { foreach ($keywords as $keyword) { $keyword = call_user_func($this->keywordsDumper, array($keyword), $short); $dump .= $this->dumpOutline($keyword, $short, $excludeAsterisk); } } return $dump; } protected function dumpBackground($keyword, $short = true, $excludeAsterisk = false) { $dump = <<dumpStep( $this->keywords->getGivenKeywords(), 'there is agent A', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getAndKeywords(), 'there is agent B', $short, $excludeAsterisk ); return $dump . "\n"; } protected function dumpScenario($keyword, $short = true, $excludeAsterisk = false) { $dump = <<dumpStep( $this->keywords->getGivenKeywords(), 'there is agent J', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getAndKeywords(), 'there is agent K', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getWhenKeywords(), 'I erase agent K\'s memory', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getThenKeywords(), 'there should be agent J', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getButKeywords(), 'there should not be agent K', $short, $excludeAsterisk ); return $dump . "\n"; } protected function dumpOutline($keyword, $short = true, $excludeAsterisk = false) { $dump = <<dumpStep( $this->keywords->getGivenKeywords(), 'there is agent ', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getAndKeywords(), 'there is agent ', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getWhenKeywords(), 'I erase agent \'s memory', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getThenKeywords(), 'there should be agent ', $short, $excludeAsterisk ); $dump .= $this->dumpStep( $this->keywords->getButKeywords(), 'there should not be agent ', $short, $excludeAsterisk ); $keywords = explode('|', $this->keywords->getExamplesKeywords()); if ($short) { $keyword = call_user_func($this->keywordsDumper, $keywords, $short); } else { $keyword = call_user_func($this->keywordsDumper, array($keywords[0]), $short); } $dump .= <<keywordsDumper, $keywords, $short); $dump .= <<keywordsDumper, array($keyword), $short); $dump .= <<lexer = $lexer; } public function parse($input, $file = null) { $this->languageSpecifierLine = null; $this->input = $input; $this->file = $file; $this->tags = array(); try { $this->lexer->analyse($this->input, 'en'); } catch (LexerException $e) { throw new ParserException( sprintf('Lexer exception "%s" thrown for file %s', $e->getMessage(), $file), 0, $e ); } $feature = null; while ('EOS' !== ($predicted = $this->predictTokenType())) { $node = $this->parseExpression(); if (null === $node || "\n" === $node) { continue; } if (!$feature && $node instanceof FeatureNode) { $feature = $node; continue; } if ($feature && $node instanceof FeatureNode) { throw new ParserException(sprintf( 'Only one feature is allowed per feature file. But %s got multiple.', $this->file )); } if (is_string($node)) { throw new ParserException(sprintf( 'Expected Feature, but got text: "%s"%s', $node, $this->file ? ' in file: ' . $this->file : '' )); } if (!$node instanceof FeatureNode) { throw new ParserException(sprintf( 'Expected Feature, but got %s on line: %d%s', $node->getKeyword(), $node->getLine(), $this->file ? ' in file: ' . $this->file : '' )); } } return $feature; } protected function expectTokenType($type) { $types = (array) $type; if (in_array($this->predictTokenType(), $types)) { return $this->lexer->getAdvancedToken(); } $token = $this->lexer->predictToken(); throw new ParserException(sprintf( 'Expected %s token, but got %s on line: %d%s', implode(' or ', $types), $this->predictTokenType(), $token['line'], $this->file ? ' in file: ' . $this->file : '' )); } protected function acceptTokenType($type) { if ($type !== $this->predictTokenType()) { return null; } return $this->lexer->getAdvancedToken(); } protected function predictTokenType() { $token = $this->lexer->predictToken(); return $token['type']; } protected function parseExpression() { switch ($type = $this->predictTokenType()) { case 'Feature': return $this->parseFeature(); case 'Background': return $this->parseBackground(); case 'Scenario': return $this->parseScenario(); case 'Outline': return $this->parseOutline(); case 'Examples': return $this->parseExamples(); case 'TableRow': return $this->parseTable(); case 'PyStringOp': return $this->parsePyString(); case 'Step': return $this->parseStep(); case 'Text': return $this->parseText(); case 'Newline': return $this->parseNewline(); case 'Tag': return $this->parseTags(); case 'Comment': return $this->parseComment(); case 'Language': return $this->parseLanguage(); case 'EOS': return ''; } throw new ParserException(sprintf('Unknown token type: %s', $type)); } protected function parseFeature() { $token = $this->expectTokenType('Feature'); $title = trim($token['value']) ?: null; $description = null; $tags = $this->popTags(); $background = null; $scenarios = array(); $keyword = $token['keyword']; $language = $this->lexer->getLanguage(); $file = $this->file; $line = $token['line']; while ('EOS' !== $this->predictTokenType()) { $node = $this->parseExpression(); if (is_string($node)) { $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node); $description .= (null !== $description ? "\n" : '') . $text; continue; } if (!$background && $node instanceof BackgroundNode) { $background = $node; continue; } if ($node instanceof ScenarioInterface) { $scenarios[] = $node; continue; } if ($background instanceof BackgroundNode && $node instanceof BackgroundNode) { throw new ParserException(sprintf( 'Each Feature could have only one Background, but found multiple on lines %d and %d%s', $background->getLine(), $node->getLine(), $this->file ? ' in file: ' . $this->file : '' )); } if (!$node instanceof ScenarioNode) { throw new ParserException(sprintf( 'Expected Scenario, Outline or Background, but got %s on line: %d%s', $node->getNodeType(), $node->getLine(), $this->file ? ' in file: ' . $this->file : '' )); } } return new FeatureNode( rtrim($title) ?: null, rtrim($description) ?: null, $tags, $background, $scenarios, $keyword, $language, $file, $line ); } protected function parseBackground() { $token = $this->expectTokenType('Background'); $title = trim($token['value']); $keyword = $token['keyword']; $line = $token['line']; if (count($this->popTags())) { throw new ParserException(sprintf( 'Background can not be tagged, but it is on line: %d%s', $line, $this->file ? ' in file: ' . $this->file : '' )); } $steps = array(); $allowedTokenTypes = array('Step', 'Newline', 'Text', 'Comment'); while (in_array($this->predictTokenType(), $allowedTokenTypes)) { $node = $this->parseExpression(); if ($node instanceof StepNode) { $steps[] = $this->normalizeStepNodeKeywordType($node, $steps); continue; } if (!count($steps) && is_string($node)) { $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node); $title .= "\n" . $text; continue; } if ("\n" === $node) { continue; } if (is_string($node)) { throw new ParserException(sprintf( 'Expected Step, but got text: "%s"%s', $node, $this->file ? ' in file: ' . $this->file : '' )); } if (!$node instanceof StepNode) { throw new ParserException(sprintf( 'Expected Step, but got %s on line: %d%s', $node->getNodeType(), $node->getLine(), $this->file ? ' in file: ' . $this->file : '' )); } } return new BackgroundNode(rtrim($title) ?: null, $steps, $keyword, $line); } protected function parseScenario() { $token = $this->expectTokenType('Scenario'); $title = trim($token['value']); $tags = $this->popTags(); $keyword = $token['keyword']; $line = $token['line']; $steps = array(); while (in_array($this->predictTokenType(), array('Step', 'Newline', 'Text', 'Comment'))) { $node = $this->parseExpression(); if ($node instanceof StepNode) { $steps[] = $this->normalizeStepNodeKeywordType($node, $steps); continue; } if (!count($steps) && is_string($node)) { $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node); $title .= "\n" . $text; continue; } if ("\n" === $node) { continue; } if (is_string($node)) { throw new ParserException(sprintf( 'Expected Step, but got text: "%s"%s', $node, $this->file ? ' in file: ' . $this->file : '' )); } if (!$node instanceof StepNode) { throw new ParserException(sprintf( 'Expected Step, but got %s on line: %d%s', $node->getNodeType(), $node->getLine(), $this->file ? ' in file: ' . $this->file : '' )); } } return new ScenarioNode(rtrim($title) ?: null, $tags, $steps, $keyword, $line); } protected function parseOutline() { $token = $this->expectTokenType('Outline'); $title = trim($token['value']); $tags = $this->popTags(); $keyword = $token['keyword']; $examples = null; $line = $token['line']; $steps = array(); while (in_array($this->predictTokenType(), array('Step', 'Examples', 'Newline', 'Text', 'Comment'))) { $node = $this->parseExpression(); if ($node instanceof StepNode) { $steps[] = $this->normalizeStepNodeKeywordType($node, $steps); continue; } if ($node instanceof ExampleTableNode) { $examples = $node; continue; } if (!count($steps) && is_string($node)) { $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node); $title .= "\n" . $text; continue; } if ("\n" === $node) { continue; } if (is_string($node)) { throw new ParserException(sprintf( 'Expected Step or Examples table, but got text: "%s"%s', $node, $this->file ? ' in file: ' . $this->file : '' )); } if (!$node instanceof StepNode) { throw new ParserException(sprintf( 'Expected Step or Examples table, but got %s on line: %d%s', $node->getNodeType(), $node->getLine(), $this->file ? ' in file: ' . $this->file : '' )); } } if (null === $examples) { throw new ParserException(sprintf( 'Outline should have examples table, but got none for outline "%s" on line: %d%s', rtrim($title), $line, $this->file ? ' in file: ' . $this->file : '' )); } return new OutlineNode(rtrim($title) ?: null, $tags, $steps, $examples, $keyword, $line); } protected function parseStep() { $token = $this->expectTokenType('Step'); $keyword = $token['value']; $keywordType = $token['keyword_type']; $text = trim($token['text']); $line = $token['line']; $arguments = array(); while (in_array($predicted = $this->predictTokenType(), array('PyStringOp', 'TableRow', 'Newline', 'Comment'))) { if ('Comment' === $predicted || 'Newline' === $predicted) { $this->acceptTokenType($predicted); continue; } $node = $this->parseExpression(); if ($node instanceof PyStringNode || $node instanceof TableNode) { $arguments[] = $node; } } return new StepNode($keyword, $text, $arguments, $line, $keywordType); } protected function parseExamples() { $token = $this->expectTokenType('Examples'); $keyword = $token['keyword']; return new ExampleTableNode($this->parseTableRows(), $keyword); } protected function parseTable() { return new TableNode($this->parseTableRows()); } protected function parsePyString() { $token = $this->expectTokenType('PyStringOp'); $line = $token['line']; $strings = array(); while ('PyStringOp' !== ($predicted = $this->predictTokenType()) && 'Text' === $predicted) { $token = $this->expectTokenType('Text'); $strings[] = $token['value']; } $this->expectTokenType('PyStringOp'); return new PyStringNode($strings, $line); } protected function parseTags() { $token = $this->expectTokenType('Tag'); $this->tags = array_merge($this->tags, $token['tags']); return $this->parseExpression(); } protected function popTags() { $tags = $this->tags; $this->tags = array(); return $tags; } protected function parseText() { $token = $this->expectTokenType('Text'); return $token['value']; } protected function parseNewline() { $this->expectTokenType('Newline'); return "\n"; } protected function parseComment() { $this->expectTokenType('Comment'); return $this->parseExpression(); } protected function parseLanguage() { $token = $this->expectTokenType('Language'); if (null === $this->languageSpecifierLine) { $this->lexer->analyse($this->input, $token['value']); $this->languageSpecifierLine = $token['line']; } elseif ($token['line'] !== $this->languageSpecifierLine) { throw new ParserException(sprintf( 'Ambiguous language specifiers on lines: %d and %d%s', $this->languageSpecifierLine, $token['line'], $this->file ? ' in file: ' . $this->file : '' )); } return $this->parseExpression(); } private function parseTableRows() { $table = array(); while (in_array($predicted = $this->predictTokenType(), array('TableRow', 'Newline', 'Comment'))) { if ('Comment' === $predicted || 'Newline' === $predicted) { $this->acceptTokenType($predicted); continue; } $token = $this->expectTokenType('TableRow'); $table[$token['line']] = $token['columns']; } return $table; } private function normalizeStepNodeKeywordType(StepNode $node, array $steps = array()) { if (in_array($node->getKeywordType(), array('And', 'But'))) { if (($prev = end($steps))) { $keywordType = $prev->getKeywordType(); } else { $keywordType = 'Given'; } $node = new StepNode( $node->getKeyword(), $node->getText(), $node->getArguments(), $node->getLine(), $keywordType ); } return $node; } } regex = $regex; } public function isFeatureMatch(FeatureNode $feature) { return 1 === preg_match($this->regex, $feature->getDescription()); } public function isScenarioMatch(ScenarioInterface $scenario) { return false; } } filterLine = intval($filterLine); } public function isFeatureMatch(FeatureNode $feature) { return $this->filterLine === $feature->getLine(); } public function isScenarioMatch(ScenarioInterface $scenario) { if ($this->filterLine === $scenario->getLine()) { return true; } if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { return $this->filterLine === $scenario->getLine() || in_array($this->filterLine, $scenario->getExampleTable()->getLines()); } return false; } public function filterFeature(FeatureNode $feature) { $scenarios = array(); foreach ($feature->getScenarios() as $scenario) { if (!$this->isScenarioMatch($scenario)) { continue; } if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { $table = $scenario->getExampleTable()->getTable(); $lines = array_keys($table); if (in_array($this->filterLine, $lines)) { $filteredTable = array($lines[0] => $table[$lines[0]]); if ($lines[0] !== $this->filterLine) { $filteredTable[$this->filterLine] = $table[$this->filterLine]; } $scenario = new OutlineNode( $scenario->getTitle(), $scenario->getTags(), $scenario->getSteps(), new ExampleTableNode($filteredTable, $scenario->getExampleTable()->getKeyword()), $scenario->getKeyword(), $scenario->getLine() ); } } $scenarios[] = $scenario; } return new FeatureNode( $feature->getTitle(), $feature->getDescription(), $feature->getTags(), $feature->getBackground(), $scenarios, $feature->getKeyword(), $feature->getLanguage(), $feature->getFile(), $feature->getLine() ); } } filterMinLine = intval($filterMinLine); if ($filterMaxLine == '*') { $this->filterMaxLine = PHP_INT_MAX; } else { $this->filterMaxLine = intval($filterMaxLine); } } public function isFeatureMatch(FeatureNode $feature) { return $this->filterMinLine <= $feature->getLine() && $this->filterMaxLine >= $feature->getLine(); } public function isScenarioMatch(ScenarioInterface $scenario) { if ($this->filterMinLine <= $scenario->getLine() && $this->filterMaxLine >= $scenario->getLine()) { return true; } if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { foreach ($scenario->getExampleTable()->getLines() as $line) { if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) { return true; } } } return false; } public function filterFeature(FeatureNode $feature) { $scenarios = array(); foreach ($feature->getScenarios() as $scenario) { if (!$this->isScenarioMatch($scenario)) { continue; } if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { $table = $scenario->getExampleTable()->getTable(); $lines = array_keys($table); $filteredTable = array($lines[0] => $table[$lines[0]]); unset($table[$lines[0]]); foreach ($table as $line => $row) { if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) { $filteredTable[$line] = $row; } } $scenario = new OutlineNode( $scenario->getTitle(), $scenario->getTags(), $scenario->getSteps(), new ExampleTableNode($filteredTable, $scenario->getExampleTable()->getKeyword()), $scenario->getKeyword(), $scenario->getLine() ); } $scenarios[] = $scenario; } return new FeatureNode( $feature->getTitle(), $feature->getDescription(), $feature->getTags(), $feature->getBackground(), $scenarios, $feature->getKeyword(), $feature->getLanguage(), $feature->getFile(), $feature->getLine() ); } } getScenarios() as $scenario) { if (!$this->isScenarioMatch($feature, $scenario)) { continue; } $scenarios[] = $scenario; } return new FeatureNode( $feature->getTitle(), $feature->getDescription(), $feature->getTags(), $feature->getBackground(), $scenarios, $feature->getKeyword(), $feature->getLanguage(), $feature->getFile(), $feature->getLine() ); } } pattern = '/as an? ' . strtr(preg_quote($role, '/'), array( '\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']' )) . '[$\n]/i'; } public function isFeatureMatch(FeatureNode $feature) { return 1 === preg_match($this->pattern, $feature->getDescription()); } public function isScenarioMatch(ScenarioInterface $scenario) { return false; } } isFeatureMatch($feature)) { return $feature; } $scenarios = array(); foreach ($feature->getScenarios() as $scenario) { if (!$this->isScenarioMatch($scenario)) { continue; } $scenarios[] = $scenario; } return new FeatureNode( $feature->getTitle(), $feature->getDescription(), $feature->getTags(), $feature->getBackground(), $scenarios, $feature->getKeyword(), $feature->getLanguage(), $feature->getFile(), $feature->getLine() ); } } filterString = trim($filterString); } public function isFeatureMatch(FeatureNode $feature) { return $this->isTagsMatchCondition($feature->getTags()); } public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario) { return $this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags())); } protected function isTagsMatchCondition($tags) { $satisfies = true; foreach (explode('&&', $this->filterString) as $andTags) { $satisfiesComma = false; foreach (explode(',', $andTags) as $tag) { $tag = str_replace('@', '', trim($tag)); if ('~' === $tag[0]) { $tag = mb_substr($tag, 1, mb_strlen($tag, 'utf8') - 1, 'utf8'); $satisfiesComma = !in_array($tag, $tags) || $satisfiesComma; } else { $satisfiesComma = in_array($tag, $tags) || $satisfiesComma; } } $satisfies = (false !== $satisfiesComma && $satisfies && $satisfiesComma) || false; } return $satisfies; } } filterString = trim($filterString); } public function isFeatureMatch(FeatureNode $feature) { if ('/' === $this->filterString[0]) { return 1 === preg_match($this->filterString, $feature->getTitle()); } return false !== mb_strpos($feature->getTitle(), $this->filterString, 0, 'utf8'); } public function isScenarioMatch(ScenarioInterface $scenario) { if ('/' === $this->filterString[0] && 1 === preg_match($this->filterString, $scenario->getTitle())) { return true; } elseif (false !== mb_strpos($scenario->getTitle(), $this->filterString, 0, 'utf8')) { return true; } return false; } } filterPaths = array_map( function ($realpath) { return rtrim($realpath, DIRECTORY_SEPARATOR) . (is_dir($realpath) ? DIRECTORY_SEPARATOR : ''); }, array_filter( array_map('realpath', $paths) ) ); } public function isFeatureMatch(FeatureNode $feature) { foreach ($this->filterPaths as $path) { if (0 === strpos($feature->getFile(), $path)) { return true; } } return false; } public function isScenarioMatch(ScenarioInterface $scenario) { return false; } } loader = new ArrayLoader(); } public function supports($path) { return is_string($path) && is_file($absolute = $this->findAbsolutePath($path)) && 'yml' === pathinfo($absolute, PATHINFO_EXTENSION); } public function load($path) { $path = $this->findAbsolutePath($path); $hash = Yaml::parse(file_get_contents($path)); $features = $this->loader->load($hash); $filename = $this->findRelativePath($path); return array_map(function (FeatureNode $feature) use ($filename) { return new FeatureNode( $feature->getTitle(), $feature->getDescription(), $feature->getTags(), $feature->getBackground(), $feature->getScenarios(), $feature->getKeyword(), $feature->getLanguage(), $filename, $feature->getLine() ); }, $features); } } parser = $parser; $this->cache = $cache; } public function setCache(CacheInterface $cache) { $this->cache = $cache; } public function supports($path) { return is_string($path) && is_file($absolute = $this->findAbsolutePath($path)) && 'feature' === pathinfo($absolute, PATHINFO_EXTENSION); } public function load($path) { $path = $this->findAbsolutePath($path); if ($this->cache) { if ($this->cache->isFresh($path, filemtime($path))) { $feature = $this->cache->read($path); } elseif (null !== $feature = $this->parseFeature($path)) { $this->cache->write($path, $feature); } } else { $feature = $this->parseFeature($path); } return null !== $feature ? array($feature) : array(); } protected function parseFeature($path) { $filename = $this->findRelativePath($path); $content = file_get_contents($path); $feature = $this->parser->parse($content, $filename); return $feature; } } basePath = realpath($path); } protected function findRelativePath($path) { if (null !== $this->basePath) { return strtr($path, array($this->basePath . DIRECTORY_SEPARATOR => '')); } return $path; } protected function findAbsolutePath($path) { if (is_file($path) || is_dir($path)) { return realpath($path); } if (null === $this->basePath) { return false; } if (is_file($this->basePath . DIRECTORY_SEPARATOR . $path) || is_dir($this->basePath . DIRECTORY_SEPARATOR . $path)) { return realpath($this->basePath . DIRECTORY_SEPARATOR . $path); } return false; } } $hash) { $feature = $this->loadFeatureHash($hash, $iterator); $features[] = $feature; } } elseif (isset($resource['feature'])) { $feature = $this->loadFeatureHash($resource['feature']); $features[] = $feature; } return $features; } protected function loadFeatureHash(array $hash, $line = 0) { $hash = array_merge( array( 'title' => null, 'description' => null, 'tags' => array(), 'keyword' => 'Feature', 'language' => 'en', 'line' => $line, 'scenarios' => array(), ), $hash ); $background = isset($hash['background']) ? $this->loadBackgroundHash($hash['background']) : null; $scenarios = array(); foreach ((array) $hash['scenarios'] as $scenarioIterator => $scenarioHash) { if (isset($scenarioHash['type']) && 'outline' === $scenarioHash['type']) { $scenarios[] = $this->loadOutlineHash($scenarioHash, $scenarioIterator); } else { $scenarios[] = $this->loadScenarioHash($scenarioHash, $scenarioIterator); } } return new FeatureNode($hash['title'], $hash['description'], $hash['tags'], $background, $scenarios, $hash['keyword'], $hash['language'], null, $hash['line']); } protected function loadBackgroundHash(array $hash) { $hash = array_merge( array( 'title' => null, 'keyword' => 'Background', 'line' => 0, 'steps' => array(), ), $hash ); $steps = $this->loadStepsHash($hash['steps']); return new BackgroundNode($hash['title'], $steps, $hash['keyword'], $hash['line']); } protected function loadScenarioHash(array $hash, $line = 0) { $hash = array_merge( array( 'title' => null, 'tags' => array(), 'keyword' => 'Scenario', 'line' => $line, 'steps' => array(), ), $hash ); $steps = $this->loadStepsHash($hash['steps']); return new ScenarioNode($hash['title'], $hash['tags'], $steps, $hash['keyword'], $hash['line']); } protected function loadOutlineHash(array $hash, $line = 0) { $hash = array_merge( array( 'title' => null, 'tags' => array(), 'keyword' => 'Scenario Outline', 'line' => $line, 'steps' => array(), 'examples' => array(), ), $hash ); $steps = $this->loadStepsHash($hash['steps']); if (isset($hash['examples']['keyword'])) { $examplesKeyword = $hash['examples']['keyword']; unset($hash['examples']['keyword']); } else { $examplesKeyword = 'Examples'; } $examples = new ExampleTableNode($hash['examples'], $examplesKeyword); return new OutlineNode($hash['title'], $hash['tags'], $steps, $examples, $hash['keyword'], $hash['line']); } private function loadStepsHash(array $hash) { $steps = array(); foreach ($hash as $stepIterator => $stepHash) { $steps[] = $this->loadStepHash($stepHash, $stepIterator); } return $steps; } protected function loadStepHash(array $hash, $line = 0) { $hash = array_merge( array( 'keyword_type' => 'Given', 'type' => 'Given', 'text' => null, 'keyword' => 'Scenario', 'line' => $line, 'arguments' => array(), ), $hash ); $arguments = array(); foreach ($hash['arguments'] as $argumentHash) { if ('table' === $argumentHash['type']) { $arguments[] = $this->loadTableHash($argumentHash['rows']); } elseif ('pystring' === $argumentHash['type']) { $arguments[] = $this->loadPyStringHash($argumentHash, $hash['line'] + 1); } } return new StepNode($hash['type'], $hash['text'], $arguments, $hash['line'], $hash['keyword_type']); } protected function loadTableHash(array $hash) { return new TableNode($hash); } protected function loadPyStringHash(array $hash, $line = 0) { $line = isset($hash['line']) ? $hash['line'] : $line; $strings = array(); foreach (explode("\n", $hash['text']) as $string) { $strings[] = $string; } return new PyStringNode($strings, $line); } } gherkin = $gherkin; } public function supports($path) { return is_string($path) && is_dir($this->findAbsolutePath($path)); } public function load($path) { $path = $this->findAbsolutePath($path); $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS) ); $paths = array_map('strval', iterator_to_array($iterator)); uasort($paths, 'strnatcasecmp'); $features = array(); foreach ($paths as $path) { $path = (string) $path; $loader = $this->gherkin->resolveLoader($path); if (null !== $loader) { $features = array_merge($features, $loader->load($path)); } } return $features; } } loaders[] = $loader; } public function addFilter(FeatureFilterInterface $filter) { $this->filters[] = $filter; } public function setFilters(array $filters) { $this->filters = array(); array_map(array($this, 'addFilter'), $filters); } public function setBasePath($path) { foreach ($this->loaders as $loader) { if ($loader instanceof FileLoaderInterface) { $loader->setBasePath($path); } } } public function load($resource, array $filters = array()) { $filters = array_merge($this->filters, $filters); $matches = array(); if (preg_match('/^(.*)\:(\d+)-(\d+|\*)$/', $resource, $matches)) { $resource = $matches[1]; $filters[] = new LineRangeFilter($matches[2], $matches[3]); } elseif (preg_match('/^(.*)\:(\d+)$/', $resource, $matches)) { $resource = $matches[1]; $filters[] = new LineFilter($matches[2]); } $loader = $this->resolveLoader($resource); if (null === $loader) { return array(); } $features = array(); foreach ($loader->load($resource) as $feature) { foreach ($filters as $filter) { $feature = $filter->filterFeature($feature); if (!$feature->hasScenarios() && !$filter->isFeatureMatch($feature)) { continue 2; } } $features[] = $feature; } return $features; } public function resolveLoader($resource) { foreach ($this->loaders as $loader) { if ($loader->supports($resource)) { return $loader; } } return null; } } path = $path; $this->element = $element; $this->condition = $condition; if ($starPrefix) { $this->addStarPrefix(); } } public function getElement() { return $this->element; } public function addCondition($condition) { $this->condition = $this->condition ? sprintf('%s and (%s)', $this->condition, $condition) : $condition; return $this; } public function getCondition() { return $this->condition; } public function addNameTest() { if ('*' !== $this->element) { $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); $this->element = '*'; } return $this; } public function addStarPrefix() { $this->path .= '*/'; return $this; } public function join($combiner, XPathExpr $expr) { $path = $this->__toString().$combiner; if ('*/' !== $expr->path) { $path .= $expr->path; } $this->path = $path; $this->element = $expr->element; $this->condition = $expr->condition; return $this; } public function __toString() { $path = $this->path.$this->element; $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; return $path.$condition; } } mainParser = $parser ?: new Parser(); $this ->registerExtension(new Extension\NodeExtension()) ->registerExtension(new Extension\CombinationExtension()) ->registerExtension(new Extension\FunctionExtension()) ->registerExtension(new Extension\PseudoClassExtension()) ->registerExtension(new Extension\AttributeMatchingExtension()) ; } public static function getXpathLiteral($element) { if (false === strpos($element, "'")) { return "'".$element."'"; } if (false === strpos($element, '"')) { return '"'.$element.'"'; } $string = $element; $parts = array(); while (true) { if (false !== $pos = strpos($string, "'")) { $parts[] = sprintf("'%s'", substr($string, 0, $pos)); $parts[] = "\"'\""; $string = substr($string, $pos + 1); } else { $parts[] = "'$string'"; break; } } return sprintf('concat(%s)', implode($parts, ', ')); } public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::') { $selectors = $this->parseSelectors($cssExpr); foreach ($selectors as $index => $selector) { if (null !== $selector->getPseudoElement()) { throw new ExpressionErrorException('Pseudo-elements are not supported.'); } $selectors[$index] = $this->selectorToXPath($selector, $prefix); } return implode(' | ', $selectors); } public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::') { return ($prefix ?: '').$this->nodeToXPath($selector); } public function registerExtension(Extension\ExtensionInterface $extension) { $this->extensions[$extension->getName()] = $extension; $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators()); $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators()); $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators()); $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators()); $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators()); return $this; } public function getExtension($name) { if (!isset($this->extensions[$name])) { throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name)); } return $this->extensions[$name]; } public function registerParserShortcut(ParserInterface $shortcut) { $this->shortcutParsers[] = $shortcut; return $this; } public function nodeToXPath(NodeInterface $node) { if (!isset($this->nodeTranslators[$node->getNodeName()])) { throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName())); } return call_user_func($this->nodeTranslators[$node->getNodeName()], $node, $this); } public function addCombination($combiner, NodeInterface $xpath, NodeInterface $combinedXpath) { if (!isset($this->combinationTranslators[$combiner])) { throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner)); } return call_user_func($this->combinationTranslators[$combiner], $this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath)); } public function addFunction(XPathExpr $xpath, FunctionNode $function) { if (!isset($this->functionTranslators[$function->getName()])) { throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName())); } return call_user_func($this->functionTranslators[$function->getName()], $xpath, $function); } public function addPseudoClass(XPathExpr $xpath, $pseudoClass) { if (!isset($this->pseudoClassTranslators[$pseudoClass])) { throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass)); } return call_user_func($this->pseudoClassTranslators[$pseudoClass], $xpath); } public function addAttributeMatching(XPathExpr $xpath, $operator, $attribute, $value) { if (!isset($this->attributeMatchingTranslators[$operator])) { throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator)); } return call_user_func($this->attributeMatchingTranslators[$operator], $xpath, $attribute, $value); } private function parseSelectors($css) { foreach ($this->shortcutParsers as $shortcut) { $tokens = $shortcut->parse($css); if (!empty($tokens)) { return $tokens; } } return $this->mainParser->parse($css); } } array($this, 'translateRoot'), 'first-child' => array($this, 'translateFirstChild'), 'last-child' => array($this, 'translateLastChild'), 'first-of-type' => array($this, 'translateFirstOfType'), 'last-of-type' => array($this, 'translateLastOfType'), 'only-child' => array($this, 'translateOnlyChild'), 'only-of-type' => array($this, 'translateOnlyOfType'), 'empty' => array($this, 'translateEmpty'), ); } public function translateRoot(XPathExpr $xpath) { return $xpath->addCondition('not(parent::*)'); } public function translateFirstChild(XPathExpr $xpath) { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('position() = 1'); } public function translateLastChild(XPathExpr $xpath) { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('position() = last()'); } public function translateFirstOfType(XPathExpr $xpath) { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:first-of-type" is not implemented.'); } return $xpath ->addStarPrefix() ->addCondition('position() = 1'); } public function translateLastOfType(XPathExpr $xpath) { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:last-of-type" is not implemented.'); } return $xpath ->addStarPrefix() ->addCondition('position() = last()'); } public function translateOnlyChild(XPathExpr $xpath) { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('last() = 1'); } public function translateOnlyOfType(XPathExpr $xpath) { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:only-of-type" is not implemented.'); } return $xpath->addCondition('last() = 1'); } public function translateEmpty(XPathExpr $xpath) { return $xpath->addCondition('not(*) and not(string-length())'); } public function getName() { return 'pseudo-class'; } } flags = $flags; } public function setFlag($flag, $on) { if ($on && !$this->hasFlag($flag)) { $this->flags += $flag; } if (!$on && $this->hasFlag($flag)) { $this->flags -= $flag; } return $this; } public function hasFlag($flag) { return (bool) ($this->flags & $flag); } public function getNodeTranslators() { return array( 'Selector' => array($this, 'translateSelector'), 'CombinedSelector' => array($this, 'translateCombinedSelector'), 'Negation' => array($this, 'translateNegation'), 'Function' => array($this, 'translateFunction'), 'Pseudo' => array($this, 'translatePseudo'), 'Attribute' => array($this, 'translateAttribute'), 'Class' => array($this, 'translateClass'), 'Hash' => array($this, 'translateHash'), 'Element' => array($this, 'translateElement'), ); } public function translateSelector(Node\SelectorNode $node, Translator $translator) { return $translator->nodeToXPath($node->getTree()); } public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator) { return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector()); } public function translateNegation(Node\NegationNode $node, Translator $translator) { $xpath = $translator->nodeToXPath($node->getSelector()); $subXpath = $translator->nodeToXPath($node->getSubSelector()); $subXpath->addNameTest(); if ($subXpath->getCondition()) { return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition())); } return $xpath->addCondition('0'); } public function translateFunction(Node\FunctionNode $node, Translator $translator) { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addFunction($xpath, $node); } public function translatePseudo(Node\PseudoNode $node, Translator $translator) { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addPseudoClass($xpath, $node->getIdentifier()); } public function translateAttribute(Node\AttributeNode $node, Translator $translator) { $name = $node->getAttribute(); $safe = $this->isSafeName($name); if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) { $name = strtolower($name); } if ($node->getNamespace()) { $name = sprintf('%s:%s', $node->getNamespace(), $name); $safe = $safe && $this->isSafeName($node->getNamespace()); } $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name)); $value = $node->getValue(); $xpath = $translator->nodeToXPath($node->getSelector()); if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) { $value = strtolower($value); } return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value); } public function translateClass(Node\ClassNode $node, Translator $translator) { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName()); } public function translateHash(Node\HashNode $node, Translator $translator) { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId()); } public function translateElement(Node\ElementNode $node) { $element = $node->getElement(); if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) { $element = strtolower($element); } if ($element) { $safe = $this->isSafeName($element); } else { $element = '*'; $safe = true; } if ($node->getNamespace()) { $element = sprintf('%s:%s', $node->getNamespace(), $element); $safe = $safe && $this->isSafeName($node->getNamespace()); } $xpath = new XPathExpr('', $element); if (!$safe) { $xpath->addNameTest(); } return $xpath; } public function getName() { return 'node'; } private function isSafeName($name) { return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name); } } array($this, 'translateExists'), '=' => array($this, 'translateEquals'), '~=' => array($this, 'translateIncludes'), '|=' => array($this, 'translateDashMatch'), '^=' => array($this, 'translatePrefixMatch'), '$=' => array($this, 'translateSuffixMatch'), '*=' => array($this, 'translateSubstringMatch'), '!=' => array($this, 'translateDifferent'), ); } public function translateExists(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition($attribute); } public function translateEquals(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value))); } public function translateIncludes(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition($value ? sprintf( '%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)', $attribute, Translator::getXpathLiteral(' '.$value.' ') ) : '0'); } public function translateDashMatch(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition(sprintf( '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))', $attribute, Translator::getXpathLiteral($value), Translator::getXpathLiteral($value.'-') )); } public function translatePrefixMatch(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition($value ? sprintf( '%1$s and starts-with(%1$s, %2$s)', $attribute, Translator::getXpathLiteral($value) ) : '0'); } public function translateSuffixMatch(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition($value ? sprintf( '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s', $attribute, strlen($value) - 1, Translator::getXpathLiteral($value) ) : '0'); } public function translateSubstringMatch(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition($value ? sprintf( '%1$s and contains(%1$s, %2$s)', $attribute, Translator::getXpathLiteral($value) ) : '0'); } public function translateDifferent(XPathExpr $xpath, $attribute, $value) { return $xpath->addCondition(sprintf( $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s', $attribute, Translator::getXpathLiteral($value) )); } public function getName() { return 'attribute-matching'; } } array($this, 'translateNthChild'), 'nth-last-child' => array($this, 'translateNthLastChild'), 'nth-of-type' => array($this, 'translateNthOfType'), 'nth-last-of-type' => array($this, 'translateNthLastOfType'), 'contains' => array($this, 'translateContains'), 'lang' => array($this, 'translateLang'), ); } public function translateNthChild(XPathExpr $xpath, FunctionNode $function, $last = false, $addNameTest = true) { try { list($a, $b) = Parser::parseSeries($function->getArguments()); } catch (SyntaxErrorException $e) { throw new ExpressionErrorException(sprintf('Invalid series: %s', implode(', ', $function->getArguments())), 0, $e); } $xpath->addStarPrefix(); if ($addNameTest) { $xpath->addNameTest(); } if (0 === $a) { return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b)); } if ($a < 0) { if ($b < 1) { return $xpath->addCondition('false()'); } $sign = '<='; } else { $sign = '>='; } $expr = 'position()'; if ($last) { $expr = 'last() - '.$expr; --$b; } if (0 !== $b) { $expr .= ' - '.$b; } $conditions = array(sprintf('%s %s 0', $expr, $sign)); if (1 !== $a && -1 !== $a) { $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a); } return $xpath->addCondition(implode(' and ', $conditions)); } public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function) { return $this->translateNthChild($xpath, $function, true); } public function translateNthOfType(XPathExpr $xpath, FunctionNode $function) { return $this->translateNthChild($xpath, $function, false, false); } public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function) { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.'); } return $this->translateNthChild($xpath, $function, true, false); } public function translateContains(XPathExpr $xpath, FunctionNode $function) { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException( 'Expected a single string or identifier for :contains(), got ' .implode(', ', $arguments) ); } } return $xpath->addCondition(sprintf( 'contains(string(.), %s)', Translator::getXpathLiteral($arguments[0]->getValue()) )); } public function translateLang(XPathExpr $xpath, FunctionNode $function) { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException( 'Expected a single string or identifier for :lang(), got ' .implode(', ', $arguments) ); } } return $xpath->addCondition(sprintf( 'lang(%s)', Translator::getXpathLiteral($arguments[0]->getValue()) )); } public function getName() { return 'function'; } } array($this, 'translateDescendant'), '>' => array($this, 'translateChild'), '+' => array($this, 'translateDirectAdjacent'), '~' => array($this, 'translateIndirectAdjacent'), ); } public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath) { return $xpath->join('/descendant-or-self::*/', $combinedXpath); } public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath) { return $xpath->join('/', $combinedXpath); } public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) { return $xpath ->join('/following-sibling::', $combinedXpath) ->addNameTest() ->addCondition('position() = 1'); } public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) { return $xpath->join('/following-sibling::', $combinedXpath); } public function getName() { return 'combination'; } } getExtension('node') ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true) ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); } public function getPseudoClassTranslators() { return array( 'checked' => array($this, 'translateChecked'), 'link' => array($this, 'translateLink'), 'disabled' => array($this, 'translateDisabled'), 'enabled' => array($this, 'translateEnabled'), 'selected' => array($this, 'translateSelected'), 'invalid' => array($this, 'translateInvalid'), 'hover' => array($this, 'translateHover'), 'visited' => array($this, 'translateVisited'), ); } public function getFunctionTranslators() { return array( 'lang' => array($this, 'translateLang'), ); } public function translateChecked(XPathExpr $xpath) { return $xpath->addCondition( '(@checked ' ."and (name(.) = 'input' or name(.) = 'command')" ."and (@type = 'checkbox' or @type = 'radio'))" ); } public function translateLink(XPathExpr $xpath) { return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"); } public function translateDisabled(XPathExpr $xpath) { return $xpath->addCondition( '(' .'@disabled and' .'(' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" ." or name(.) = 'command'" ." or name(.) = 'fieldset'" ." or name(.) = 'optgroup'" ." or name(.) = 'option'" .')' .') or (' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" .')' .' and ancestor::fieldset[@disabled]' ); } public function translateEnabled(XPathExpr $xpath) { return $xpath->addCondition( '(' .'@href and (' ."name(.) = 'a'" ." or name(.) = 'link'" ." or name(.) = 'area'" .')' .') or (' .'(' ."name(.) = 'command'" ." or name(.) = 'fieldset'" ." or name(.) = 'optgroup'" .')' .' and not(@disabled)' .') or (' .'(' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" ." or name(.) = 'keygen'" .')' .' and not (@disabled or ancestor::fieldset[@disabled])' .') or (' ."name(.) = 'option' and not(" .'@disabled or ancestor::optgroup[@disabled]' .')' .')' ); } public function translateLang(XPathExpr $xpath, FunctionNode $function) { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException( 'Expected a single string or identifier for :lang(), got ' .implode(', ', $arguments) ); } } return $xpath->addCondition(sprintf( 'ancestor-or-self::*[@lang][1][starts-with(concat(' ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')" .', %s)]', 'lang', Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-') )); } public function translateSelected(XPathExpr $xpath) { return $xpath->addCondition("(@selected and name(.) = 'option')"); } public function translateInvalid(XPathExpr $xpath) { return $xpath->addCondition('0'); } public function translateHover(XPathExpr $xpath) { return $xpath->addCondition('0'); } public function translateVisited(XPathExpr $xpath) { return $xpath->addCondition('0'); } public function getName() { return 'html'; } } selector = $selector; $this->name = $name; } public function getSelector() { return $this->selector; } public function getName() { return $this->name; } public function getSpecificity() { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString() { return sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name); } } tree = $tree; $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; } public function getTree() { return $this->tree; } public function getPseudoElement() { return $this->pseudoElement; } public function getSpecificity() { return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); } public function __toString() { return sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : ''); } } a = $a; $this->b = $b; $this->c = $c; } public function plus(Specificity $specificity) { return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); } public function getValue() { return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR; } public function compareTo(Specificity $specificity) { if ($this->a !== $specificity->a) { return $this->a > $specificity->a ? 1 : -1; } if ($this->b !== $specificity->b) { return $this->b > $specificity->b ? 1 : -1; } if ($this->c !== $specificity->c) { return $this->c > $specificity->c ? 1 : -1; } return 0; } } selector = $selector; $this->name = strtolower($name); $this->arguments = $arguments; } public function getSelector() { return $this->selector; } public function getName() { return $this->name; } public function getArguments() { return $this->arguments; } public function getSpecificity() { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString() { $arguments = implode(', ', array_map(function (Token $token) { return "'".$token->getValue()."'"; }, $this->arguments)); return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); } } selector = $selector; $this->identifier = strtolower($identifier); } public function getSelector() { return $this->selector; } public function getIdentifier() { return $this->identifier; } public function getSpecificity() { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString() { return sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier); } } namespace = $namespace; $this->element = $element; } public function getNamespace() { return $this->namespace; } public function getElement() { return $this->element; } public function getSpecificity() { return new Specificity(0, 0, $this->element ? 1 : 0); } public function __toString() { $element = $this->element ?: '*'; return sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element); } } selector = $selector; $this->subSelector = $subSelector; } public function getSelector() { return $this->selector; } public function getSubSelector() { return $this->subSelector; } public function getSpecificity() { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); } public function __toString() { return sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector); } } nodeName) { $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', get_called_class()); } return $this->nodeName; } } selector = $selector; $this->id = $id; } public function getSelector() { return $this->selector; } public function getId() { return $this->id; } public function getSpecificity() { return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); } public function __toString() { return sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id); } } selector = $selector; $this->combinator = $combinator; $this->subSelector = $subSelector; } public function getSelector() { return $this->selector; } public function getCombinator() { return $this->combinator; } public function getSubSelector() { return $this->subSelector; } public function getSpecificity() { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); } public function __toString() { $combinator = ' ' === $this->combinator ? '' : $this->combinator; return sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector); } } selector = $selector; $this->namespace = $namespace; $this->attribute = $attribute; $this->operator = $operator; $this->value = $value; } public function getSelector() { return $this->selector; } public function getNamespace() { return $this->namespace; } public function getAttribute() { return $this->attribute; } public function getOperator() { return $this->operator; } public function getValue() { return $this->value; } public function getSpecificity() { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString() { $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute; return 'exists' === $this->operator ? sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute) : sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value); } } patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream) { $match = $reader->findPattern($this->patterns->getHashPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[1]); $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); $reader->moveForward(strlen($match[0])); return true; } } findPattern('~^[ \t\r\n\f]+~'); if (false === $match) { return false; } $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); $reader->moveForward(strlen($match[0])); return true; } } patterns = $patterns; } public function handle(Reader $reader, TokenStream $stream) { $match = $reader->findPattern($this->patterns->getNumberPattern()); if (!$match) { return false; } $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); $reader->moveForward(strlen($match[0])); return true; } } patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream) { $match = $reader->findPattern($this->patterns->getIdentifierPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[0]); $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); $reader->moveForward(strlen($match[0])); return true; } } getSubstring(2)) { return false; } $offset = $reader->getOffset('*/'); if (false === $offset) { $reader->moveToEnd(); } else { $reader->moveForward($offset + 2); } return true; } } patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream) { $quote = $reader->getSubstring(1); if (!in_array($quote, array("'", '"'))) { return false; } $reader->moveForward(1); $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); if (!$match) { throw new InternalErrorException(sprintf('Should have found at least an empty match at %s.', $reader->getPosition())); } if (strlen($match[0]) === $reader->getRemainingLength()) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } if ($quote !== $reader->getSubstring(1, strlen($match[0]))) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); $reader->moveForward(strlen($match[0]) + 1); return true; } } type = $type; $this->value = $value; $this->position = $position; } public function getType() { return $this->type; } public function getValue() { return $this->value; } public function getPosition() { return $this->position; } public function isFileEnd() { return self::TYPE_FILE_END === $this->type; } public function isDelimiter(array $values = array()) { if (self::TYPE_DELIMITER !== $this->type) { return false; } if (empty($values)) { return true; } return in_array($this->value, $values); } public function isWhitespace() { return self::TYPE_WHITESPACE === $this->type; } public function isIdentifier() { return self::TYPE_IDENTIFIER === $this->type; } public function isHash() { return self::TYPE_HASH === $this->type; } public function isNumber() { return self::TYPE_NUMBER === $this->type; } public function isString() { return self::TYPE_STRING === $this->type; } public function __toString() { if ($this->value) { return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); } return sprintf('<%s at %s>', $this->type, $this->position); } } tokens[] = $token; return $this; } public function freeze() { $this->frozen = true; return $this; } public function getNext() { if ($this->peeking) { $this->peeking = false; $this->used[] = $this->peeked; return $this->peeked; } if (!isset($this->tokens[$this->cursor])) { throw new InternalErrorException('Unexpected token stream end.'); } return $this->tokens[$this->cursor++]; } public function getPeek() { if (!$this->peeking) { $this->peeked = $this->getNext(); $this->peeking = true; } return $this->peeked; } public function getUsed() { return $this->used; } public function getNextIdentifier() { $next = $this->getNext(); if (!$next->isIdentifier()) { throw SyntaxErrorException::unexpectedToken('identifier', $next); } return $next->getValue(); } public function getNextIdentifierOrStar() { $next = $this->getNext(); if ($next->isIdentifier()) { return $next->getValue(); } if ($next->isDelimiter(array('*'))) { return; } throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); } public function skipWhitespace() { $peek = $this->getPeek(); if ($peek->isWhitespace()) { $this->getNext(); } } } tokenizer = $tokenizer ?: new Tokenizer(); } public function parse($source) { $reader = new Reader($source); $stream = $this->tokenizer->tokenize($reader); return $this->parseSelectorList($stream); } public static function parseSeries(array $tokens) { foreach ($tokens as $token) { if ($token->isString()) { throw SyntaxErrorException::stringAsFunctionArgument(); } } $joined = trim(implode('', array_map(function (Token $token) { return $token->getValue(); }, $tokens))); $int = function ($string) { if (!is_numeric($string)) { throw SyntaxErrorException::stringAsFunctionArgument(); } return (int) $string; }; switch (true) { case 'odd' === $joined: return array(2, 1); case 'even' === $joined: return array(2, 0); case 'n' === $joined: return array(1, 0); case false === strpos($joined, 'n'): return array(0, $int($joined)); } $split = explode('n', $joined); $first = isset($split[0]) ? $split[0] : null; return array( $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1, isset($split[1]) && $split[1] ? $int($split[1]) : 0, ); } private function parseSelectorList(TokenStream $stream) { $stream->skipWhitespace(); $selectors = array(); while (true) { $selectors[] = $this->parserSelectorNode($stream); if ($stream->getPeek()->isDelimiter(array(','))) { $stream->getNext(); $stream->skipWhitespace(); } else { break; } } return $selectors; } private function parserSelectorNode(TokenStream $stream) { list($result, $pseudoElement) = $this->parseSimpleSelector($stream); while (true) { $stream->skipWhitespace(); $peek = $stream->getPeek(); if ($peek->isFileEnd() || $peek->isDelimiter(array(','))) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isDelimiter(array('+', '>', '~'))) { $combinator = $stream->getNext()->getValue(); $stream->skipWhitespace(); } else { $combinator = ' '; } list($nextSelector, $pseudoElement) = $this->parseSimpleSelector($stream); $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); } return new Node\SelectorNode($result, $pseudoElement); } private function parseSimpleSelector(TokenStream $stream, $insideNegation = false) { $stream->skipWhitespace(); $selectorStart = count($stream->getUsed()); $result = $this->parseElementNode($stream); $pseudoElement = null; while (true) { $peek = $stream->getPeek(); if ($peek->isWhitespace() || $peek->isFileEnd() || $peek->isDelimiter(array(',', '+', '>', '~')) || ($insideNegation && $peek->isDelimiter(array(')'))) ) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isHash()) { $result = new Node\HashNode($result, $stream->getNext()->getValue()); } elseif ($peek->isDelimiter(array('.'))) { $stream->getNext(); $result = new Node\ClassNode($result, $stream->getNextIdentifier()); } elseif ($peek->isDelimiter(array('['))) { $stream->getNext(); $result = $this->parseAttributeNode($result, $stream); } elseif ($peek->isDelimiter(array(':'))) { $stream->getNext(); if ($stream->getPeek()->isDelimiter(array(':'))) { $stream->getNext(); $pseudoElement = $stream->getNextIdentifier(); continue; } $identifier = $stream->getNextIdentifier(); if (in_array(strtolower($identifier), array('first-line', 'first-letter', 'before', 'after'))) { $pseudoElement = $identifier; continue; } if (!$stream->getPeek()->isDelimiter(array('('))) { $result = new Node\PseudoNode($result, $identifier); continue; } $stream->getNext(); $stream->skipWhitespace(); if ('not' === strtolower($identifier)) { if ($insideNegation) { throw SyntaxErrorException::nestedNot(); } list($argument, $argumentPseudoElement) = $this->parseSimpleSelector($stream, true); $next = $stream->getNext(); if (null !== $argumentPseudoElement) { throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()'); } if (!$next->isDelimiter(array(')'))) { throw SyntaxErrorException::unexpectedToken('")"', $next); } $result = new Node\NegationNode($result, $argument); } else { $arguments = array(); $next = null; while (true) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isIdentifier() || $next->isString() || $next->isNumber() || $next->isDelimiter(array('+', '-')) ) { $arguments[] = $next; } elseif ($next->isDelimiter(array(')'))) { break; } else { throw SyntaxErrorException::unexpectedToken('an argument', $next); } } if (empty($arguments)) { throw SyntaxErrorException::unexpectedToken('at least one argument', $next); } $result = new Node\FunctionNode($result, $identifier, $arguments); } } else { throw SyntaxErrorException::unexpectedToken('selector', $peek); } } if (count($stream->getUsed()) === $selectorStart) { throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); } return array($result, $pseudoElement); } private function parseElementNode(TokenStream $stream) { $peek = $stream->getPeek(); if ($peek->isIdentifier() || $peek->isDelimiter(array('*'))) { if ($peek->isIdentifier()) { $namespace = $stream->getNext()->getValue(); } else { $stream->getNext(); $namespace = null; } if ($stream->getPeek()->isDelimiter(array('|'))) { $stream->getNext(); $element = $stream->getNextIdentifierOrStar(); } else { $element = $namespace; $namespace = null; } } else { $element = $namespace = null; } return new Node\ElementNode($namespace, $element); } private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream) { $stream->skipWhitespace(); $attribute = $stream->getNextIdentifierOrStar(); if (null === $attribute && !$stream->getPeek()->isDelimiter(array('|'))) { throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek()); } if ($stream->getPeek()->isDelimiter(array('|'))) { $stream->getNext(); if ($stream->getPeek()->isDelimiter(array('='))) { $namespace = null; $stream->getNext(); $operator = '|='; } else { $namespace = $attribute; $attribute = $stream->getNextIdentifier(); $operator = null; } } else { $namespace = $operator = null; } if (null === $operator) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isDelimiter(array(']'))) { return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null); } elseif ($next->isDelimiter(array('='))) { $operator = '='; } elseif ($next->isDelimiter(array('^', '$', '*', '~', '|', '!')) && $stream->getPeek()->isDelimiter(array('=')) ) { $operator = $next->getValue().'='; $stream->getNext(); } else { throw SyntaxErrorException::unexpectedToken('operator', $next); } } $stream->skipWhitespace(); $value = $stream->getNext(); if ($value->isNumber()) { $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition()); } if (!($value->isIdentifier() || $value->isString())) { throw SyntaxErrorException::unexpectedToken('string or identifier', $value); } $stream->skipWhitespace(); $next = $stream->getNext(); if (!$next->isDelimiter(array(']'))) { throw SyntaxErrorException::unexpectedToken('"]"', $next); } return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue()); } } source = $source; $this->length = strlen($source); } public function isEOF() { return $this->position >= $this->length; } public function getPosition() { return $this->position; } public function getRemainingLength() { return $this->length - $this->position; } public function getSubstring($length, $offset = 0) { return substr($this->source, $this->position + $offset, $length); } public function getOffset($string) { $position = strpos($this->source, $string, $this->position); return false === $position ? false : $position - $this->position; } public function findPattern($pattern) { $source = substr($this->source, $this->position); if (preg_match($pattern, $source, $matches)) { return $matches; } return false; } public function moveForward($length) { $this->position += $length; } public function moveToEnd() { $this->position = $this->length; } } unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; $this->simpleEscapePattern = '\\\\(.)'; $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; $this->nonAsciiPattern = '[^\x00-\x7F]'; $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->identifierPattern = '(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; $this->quotedStringPattern = '([^\n\r\f%s]|'.$this->stringEscapePattern.')*'; } public function getNewLineEscapePattern() { return '~^'.$this->newLineEscapePattern.'~'; } public function getSimpleEscapePattern() { return '~^'.$this->simpleEscapePattern.'~'; } public function getUnicodeEscapePattern() { return '~^'.$this->unicodeEscapePattern.'~i'; } public function getIdentifierPattern() { return '~^'.$this->identifierPattern.'~i'; } public function getHashPattern() { return '~^'.$this->hashPattern.'~i'; } public function getNumberPattern() { return '~^'.$this->numberPattern.'~'; } public function getQuotedStringPattern($quote) { return '~^'.sprintf($this->quotedStringPattern, $quote).'~i'; } } patterns = $patterns; } public function escapeUnicode($value) { $value = $this->replaceUnicodeSequences($value); return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); } public function escapeUnicodeAndNewLine($value) { $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); return $this->escapeUnicode($value); } private function replaceUnicodeSequences($value) { return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) { $c = hexdec($match[1]); if (0x80 > $c %= 0x200000) { return chr($c); } if (0x800 > $c) { return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); } if (0x10000 > $c) { return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); } }, $value); } } handlers = array( new Handler\WhitespaceHandler(), new Handler\IdentifierHandler($patterns, $escaping), new Handler\HashHandler($patterns, $escaping), new Handler\StringHandler($patterns, $escaping), new Handler\NumberHandler($patterns), new Handler\CommentHandler(), ); } public function tokenize(Reader $reader) { $stream = new TokenStream(); while (!$reader->isEOF()) { foreach ($this->handlers as $handler) { if ($handler->handle($reader, $stream)) { continue 2; } } $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); $reader->moveForward(1); } return $stream ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) ->freeze(); } } translator = new Translator(); if ($html) { $this->translator->registerExtension(new HtmlExtension($this->translator)); } $this->translator ->registerParserShortcut(new EmptyStringParser()) ->registerParserShortcut(new ElementParser()) ->registerParserShortcut(new ClassParser()) ->registerParserShortcut(new HashParser()) ; } public function toXPath($cssExpr, $prefix = 'descendant-or-self::') { return $this->translator->cssToXPath($cssExpr, $prefix); } } 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', 'À' => 'à', 'Á' => 'á', 'Â' => 'â', 'Ã' => 'ã', 'Ä' => 'ä', 'Å' => 'å', 'Æ' => 'æ', 'Ç' => 'ç', 'È' => 'è', 'É' => 'é', 'Ê' => 'ê', 'Ë' => 'ë', 'Ì' => 'ì', 'Í' => 'í', 'Î' => 'î', 'Ï' => 'ï', 'Ð' => 'ð', 'Ñ' => 'ñ', 'Ò' => 'ò', 'Ó' => 'ó', 'Ô' => 'ô', 'Õ' => 'õ', 'Ö' => 'ö', 'Ø' => 'ø', 'Ù' => 'ù', 'Ú' => 'ú', 'Û' => 'û', 'Ü' => 'ü', 'Ý' => 'ý', 'Þ' => 'þ', 'Ā' => 'ā', 'Ă' => 'ă', 'Ą' => 'ą', 'Ć' => 'ć', 'Ĉ' => 'ĉ', 'Ċ' => 'ċ', 'Č' => 'č', 'Ď' => 'ď', 'Đ' => 'đ', 'Ē' => 'ē', 'Ĕ' => 'ĕ', 'Ė' => 'ė', 'Ę' => 'ę', 'Ě' => 'ě', 'Ĝ' => 'ĝ', 'Ğ' => 'ğ', 'Ġ' => 'ġ', 'Ģ' => 'ģ', 'Ĥ' => 'ĥ', 'Ħ' => 'ħ', 'Ĩ' => 'ĩ', 'Ī' => 'ī', 'Ĭ' => 'ĭ', 'Į' => 'į', 'İ' => 'i', 'IJ' => 'ij', 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', 'Ĺ' => 'ĺ', 'Ļ' => 'ļ', 'Ľ' => 'ľ', 'Ŀ' => 'ŀ', 'Ł' => 'ł', 'Ń' => 'ń', 'Ņ' => 'ņ', 'Ň' => 'ň', 'Ŋ' => 'ŋ', 'Ō' => 'ō', 'Ŏ' => 'ŏ', 'Ő' => 'ő', 'Œ' => 'œ', 'Ŕ' => 'ŕ', 'Ŗ' => 'ŗ', 'Ř' => 'ř', 'Ś' => 'ś', 'Ŝ' => 'ŝ', 'Ş' => 'ş', 'Š' => 'š', 'Ţ' => 'ţ', 'Ť' => 'ť', 'Ŧ' => 'ŧ', 'Ũ' => 'ũ', 'Ū' => 'ū', 'Ŭ' => 'ŭ', 'Ů' => 'ů', 'Ű' => 'ű', 'Ų' => 'ų', 'Ŵ' => 'ŵ', 'Ŷ' => 'ŷ', 'Ÿ' => 'ÿ', 'Ź' => 'ź', 'Ż' => 'ż', 'Ž' => 'ž', 'Ɓ' => 'ɓ', 'Ƃ' => 'ƃ', 'Ƅ' => 'ƅ', 'Ɔ' => 'ɔ', 'Ƈ' => 'ƈ', 'Ɖ' => 'ɖ', 'Ɗ' => 'ɗ', 'Ƌ' => 'ƌ', 'Ǝ' => 'ǝ', 'Ə' => 'ə', 'Ɛ' => 'ɛ', 'Ƒ' => 'ƒ', 'Ɠ' => 'ɠ', 'Ɣ' => 'ɣ', 'Ɩ' => 'ɩ', 'Ɨ' => 'ɨ', 'Ƙ' => 'ƙ', 'Ɯ' => 'ɯ', 'Ɲ' => 'ɲ', 'Ɵ' => 'ɵ', 'Ơ' => 'ơ', 'Ƣ' => 'ƣ', 'Ƥ' => 'ƥ', 'Ʀ' => 'ʀ', 'Ƨ' => 'ƨ', 'Ʃ' => 'ʃ', 'Ƭ' => 'ƭ', 'Ʈ' => 'ʈ', 'Ư' => 'ư', 'Ʊ' => 'ʊ', 'Ʋ' => 'ʋ', 'Ƴ' => 'ƴ', 'Ƶ' => 'ƶ', 'Ʒ' => 'ʒ', 'Ƹ' => 'ƹ', 'Ƽ' => 'ƽ', 'DŽ' => 'dž', 'Dž' => 'dž', 'LJ' => 'lj', 'Lj' => 'lj', 'NJ' => 'nj', 'Nj' => 'nj', 'Ǎ' => 'ǎ', 'Ǐ' => 'ǐ', 'Ǒ' => 'ǒ', 'Ǔ' => 'ǔ', 'Ǖ' => 'ǖ', 'Ǘ' => 'ǘ', 'Ǚ' => 'ǚ', 'Ǜ' => 'ǜ', 'Ǟ' => 'ǟ', 'Ǡ' => 'ǡ', 'Ǣ' => 'ǣ', 'Ǥ' => 'ǥ', 'Ǧ' => 'ǧ', 'Ǩ' => 'ǩ', 'Ǫ' => 'ǫ', 'Ǭ' => 'ǭ', 'Ǯ' => 'ǯ', 'DZ' => 'dz', 'Dz' => 'dz', 'Ǵ' => 'ǵ', 'Ƕ' => 'ƕ', 'Ƿ' => 'ƿ', 'Ǹ' => 'ǹ', 'Ǻ' => 'ǻ', 'Ǽ' => 'ǽ', 'Ǿ' => 'ǿ', 'Ȁ' => 'ȁ', 'Ȃ' => 'ȃ', 'Ȅ' => 'ȅ', 'Ȇ' => 'ȇ', 'Ȉ' => 'ȉ', 'Ȋ' => 'ȋ', 'Ȍ' => 'ȍ', 'Ȏ' => 'ȏ', 'Ȑ' => 'ȑ', 'Ȓ' => 'ȓ', 'Ȕ' => 'ȕ', 'Ȗ' => 'ȗ', 'Ș' => 'ș', 'Ț' => 'ț', 'Ȝ' => 'ȝ', 'Ȟ' => 'ȟ', 'Ƞ' => 'ƞ', 'Ȣ' => 'ȣ', 'Ȥ' => 'ȥ', 'Ȧ' => 'ȧ', 'Ȩ' => 'ȩ', 'Ȫ' => 'ȫ', 'Ȭ' => 'ȭ', 'Ȯ' => 'ȯ', 'Ȱ' => 'ȱ', 'Ȳ' => 'ȳ', 'Ⱥ' => 'ⱥ', 'Ȼ' => 'ȼ', 'Ƚ' => 'ƚ', 'Ⱦ' => 'ⱦ', 'Ɂ' => 'ɂ', 'Ƀ' => 'ƀ', 'Ʉ' => 'ʉ', 'Ʌ' => 'ʌ', 'Ɇ' => 'ɇ', 'Ɉ' => 'ɉ', 'Ɋ' => 'ɋ', 'Ɍ' => 'ɍ', 'Ɏ' => 'ɏ', 'Ͱ' => 'ͱ', 'Ͳ' => 'ͳ', 'Ͷ' => 'ͷ', 'Ϳ' => 'ϳ', 'Ά' => 'ά', 'Έ' => 'έ', 'Ή' => 'ή', 'Ί' => 'ί', 'Ό' => 'ό', 'Ύ' => 'ύ', 'Ώ' => 'ώ', 'Α' => 'α', 'Β' => 'β', 'Γ' => 'γ', 'Δ' => 'δ', 'Ε' => 'ε', 'Ζ' => 'ζ', 'Η' => 'η', 'Θ' => 'θ', 'Ι' => 'ι', 'Κ' => 'κ', 'Λ' => 'λ', 'Μ' => 'μ', 'Ν' => 'ν', 'Ξ' => 'ξ', 'Ο' => 'ο', 'Π' => 'π', 'Ρ' => 'ρ', 'Σ' => 'σ', 'Τ' => 'τ', 'Υ' => 'υ', 'Φ' => 'φ', 'Χ' => 'χ', 'Ψ' => 'ψ', 'Ω' => 'ω', 'Ϊ' => 'ϊ', 'Ϋ' => 'ϋ', 'Ϗ' => 'ϗ', 'Ϙ' => 'ϙ', 'Ϛ' => 'ϛ', 'Ϝ' => 'ϝ', 'Ϟ' => 'ϟ', 'Ϡ' => 'ϡ', 'Ϣ' => 'ϣ', 'Ϥ' => 'ϥ', 'Ϧ' => 'ϧ', 'Ϩ' => 'ϩ', 'Ϫ' => 'ϫ', 'Ϭ' => 'ϭ', 'Ϯ' => 'ϯ', 'ϴ' => 'θ', 'Ϸ' => 'ϸ', 'Ϲ' => 'ϲ', 'Ϻ' => 'ϻ', 'Ͻ' => 'ͻ', 'Ͼ' => 'ͼ', 'Ͽ' => 'ͽ', 'Ѐ' => 'ѐ', 'Ё' => 'ё', 'Ђ' => 'ђ', 'Ѓ' => 'ѓ', 'Є' => 'є', 'Ѕ' => 'ѕ', 'І' => 'і', 'Ї' => 'ї', 'Ј' => 'ј', 'Љ' => 'љ', 'Њ' => 'њ', 'Ћ' => 'ћ', 'Ќ' => 'ќ', 'Ѝ' => 'ѝ', 'Ў' => 'ў', 'Џ' => 'џ', 'А' => 'а', 'Б' => 'б', 'В' => 'в', 'Г' => 'г', 'Д' => 'д', 'Е' => 'е', 'Ж' => 'ж', 'З' => 'з', 'И' => 'и', 'Й' => 'й', 'К' => 'к', 'Л' => 'л', 'М' => 'м', 'Н' => 'н', 'О' => 'о', 'П' => 'п', 'Р' => 'р', 'С' => 'с', 'Т' => 'т', 'У' => 'у', 'Ф' => 'ф', 'Х' => 'х', 'Ц' => 'ц', 'Ч' => 'ч', 'Ш' => 'ш', 'Щ' => 'щ', 'Ъ' => 'ъ', 'Ы' => 'ы', 'Ь' => 'ь', 'Э' => 'э', 'Ю' => 'ю', 'Я' => 'я', 'Ѡ' => 'ѡ', 'Ѣ' => 'ѣ', 'Ѥ' => 'ѥ', 'Ѧ' => 'ѧ', 'Ѩ' => 'ѩ', 'Ѫ' => 'ѫ', 'Ѭ' => 'ѭ', 'Ѯ' => 'ѯ', 'Ѱ' => 'ѱ', 'Ѳ' => 'ѳ', 'Ѵ' => 'ѵ', 'Ѷ' => 'ѷ', 'Ѹ' => 'ѹ', 'Ѻ' => 'ѻ', 'Ѽ' => 'ѽ', 'Ѿ' => 'ѿ', 'Ҁ' => 'ҁ', 'Ҋ' => 'ҋ', 'Ҍ' => 'ҍ', 'Ҏ' => 'ҏ', 'Ґ' => 'ґ', 'Ғ' => 'ғ', 'Ҕ' => 'ҕ', 'Җ' => 'җ', 'Ҙ' => 'ҙ', 'Қ' => 'қ', 'Ҝ' => 'ҝ', 'Ҟ' => 'ҟ', 'Ҡ' => 'ҡ', 'Ң' => 'ң', 'Ҥ' => 'ҥ', 'Ҧ' => 'ҧ', 'Ҩ' => 'ҩ', 'Ҫ' => 'ҫ', 'Ҭ' => 'ҭ', 'Ү' => 'ү', 'Ұ' => 'ұ', 'Ҳ' => 'ҳ', 'Ҵ' => 'ҵ', 'Ҷ' => 'ҷ', 'Ҹ' => 'ҹ', 'Һ' => 'һ', 'Ҽ' => 'ҽ', 'Ҿ' => 'ҿ', 'Ӏ' => 'ӏ', 'Ӂ' => 'ӂ', 'Ӄ' => 'ӄ', 'Ӆ' => 'ӆ', 'Ӈ' => 'ӈ', 'Ӊ' => 'ӊ', 'Ӌ' => 'ӌ', 'Ӎ' => 'ӎ', 'Ӑ' => 'ӑ', 'Ӓ' => 'ӓ', 'Ӕ' => 'ӕ', 'Ӗ' => 'ӗ', 'Ә' => 'ә', 'Ӛ' => 'ӛ', 'Ӝ' => 'ӝ', 'Ӟ' => 'ӟ', 'Ӡ' => 'ӡ', 'Ӣ' => 'ӣ', 'Ӥ' => 'ӥ', 'Ӧ' => 'ӧ', 'Ө' => 'ө', 'Ӫ' => 'ӫ', 'Ӭ' => 'ӭ', 'Ӯ' => 'ӯ', 'Ӱ' => 'ӱ', 'Ӳ' => 'ӳ', 'Ӵ' => 'ӵ', 'Ӷ' => 'ӷ', 'Ӹ' => 'ӹ', 'Ӻ' => 'ӻ', 'Ӽ' => 'ӽ', 'Ӿ' => 'ӿ', 'Ԁ' => 'ԁ', 'Ԃ' => 'ԃ', 'Ԅ' => 'ԅ', 'Ԇ' => 'ԇ', 'Ԉ' => 'ԉ', 'Ԋ' => 'ԋ', 'Ԍ' => 'ԍ', 'Ԏ' => 'ԏ', 'Ԑ' => 'ԑ', 'Ԓ' => 'ԓ', 'Ԕ' => 'ԕ', 'Ԗ' => 'ԗ', 'Ԙ' => 'ԙ', 'Ԛ' => 'ԛ', 'Ԝ' => 'ԝ', 'Ԟ' => 'ԟ', 'Ԡ' => 'ԡ', 'Ԣ' => 'ԣ', 'Ԥ' => 'ԥ', 'Ԧ' => 'ԧ', 'Ԩ' => 'ԩ', 'Ԫ' => 'ԫ', 'Ԭ' => 'ԭ', 'Ԯ' => 'ԯ', 'Ա' => 'ա', 'Բ' => 'բ', 'Գ' => 'գ', 'Դ' => 'դ', 'Ե' => 'ե', 'Զ' => 'զ', 'Է' => 'է', 'Ը' => 'ը', 'Թ' => 'թ', 'Ժ' => 'ժ', 'Ի' => 'ի', 'Լ' => 'լ', 'Խ' => 'խ', 'Ծ' => 'ծ', 'Կ' => 'կ', 'Հ' => 'հ', 'Ձ' => 'ձ', 'Ղ' => 'ղ', 'Ճ' => 'ճ', 'Մ' => 'մ', 'Յ' => 'յ', 'Ն' => 'ն', 'Շ' => 'շ', 'Ո' => 'ո', 'Չ' => 'չ', 'Պ' => 'պ', 'Ջ' => 'ջ', 'Ռ' => 'ռ', 'Ս' => 'ս', 'Վ' => 'վ', 'Տ' => 'տ', 'Ր' => 'ր', 'Ց' => 'ց', 'Ւ' => 'ւ', 'Փ' => 'փ', 'Ք' => 'ք', 'Օ' => 'օ', 'Ֆ' => 'ֆ', 'Ⴀ' => 'ⴀ', 'Ⴁ' => 'ⴁ', 'Ⴂ' => 'ⴂ', 'Ⴃ' => 'ⴃ', 'Ⴄ' => 'ⴄ', 'Ⴅ' => 'ⴅ', 'Ⴆ' => 'ⴆ', 'Ⴇ' => 'ⴇ', 'Ⴈ' => 'ⴈ', 'Ⴉ' => 'ⴉ', 'Ⴊ' => 'ⴊ', 'Ⴋ' => 'ⴋ', 'Ⴌ' => 'ⴌ', 'Ⴍ' => 'ⴍ', 'Ⴎ' => 'ⴎ', 'Ⴏ' => 'ⴏ', 'Ⴐ' => 'ⴐ', 'Ⴑ' => 'ⴑ', 'Ⴒ' => 'ⴒ', 'Ⴓ' => 'ⴓ', 'Ⴔ' => 'ⴔ', 'Ⴕ' => 'ⴕ', 'Ⴖ' => 'ⴖ', 'Ⴗ' => 'ⴗ', 'Ⴘ' => 'ⴘ', 'Ⴙ' => 'ⴙ', 'Ⴚ' => 'ⴚ', 'Ⴛ' => 'ⴛ', 'Ⴜ' => 'ⴜ', 'Ⴝ' => 'ⴝ', 'Ⴞ' => 'ⴞ', 'Ⴟ' => 'ⴟ', 'Ⴠ' => 'ⴠ', 'Ⴡ' => 'ⴡ', 'Ⴢ' => 'ⴢ', 'Ⴣ' => 'ⴣ', 'Ⴤ' => 'ⴤ', 'Ⴥ' => 'ⴥ', 'Ⴧ' => 'ⴧ', 'Ⴭ' => 'ⴭ', 'Ḁ' => 'ḁ', 'Ḃ' => 'ḃ', 'Ḅ' => 'ḅ', 'Ḇ' => 'ḇ', 'Ḉ' => 'ḉ', 'Ḋ' => 'ḋ', 'Ḍ' => 'ḍ', 'Ḏ' => 'ḏ', 'Ḑ' => 'ḑ', 'Ḓ' => 'ḓ', 'Ḕ' => 'ḕ', 'Ḗ' => 'ḗ', 'Ḙ' => 'ḙ', 'Ḛ' => 'ḛ', 'Ḝ' => 'ḝ', 'Ḟ' => 'ḟ', 'Ḡ' => 'ḡ', 'Ḣ' => 'ḣ', 'Ḥ' => 'ḥ', 'Ḧ' => 'ḧ', 'Ḩ' => 'ḩ', 'Ḫ' => 'ḫ', 'Ḭ' => 'ḭ', 'Ḯ' => 'ḯ', 'Ḱ' => 'ḱ', 'Ḳ' => 'ḳ', 'Ḵ' => 'ḵ', 'Ḷ' => 'ḷ', 'Ḹ' => 'ḹ', 'Ḻ' => 'ḻ', 'Ḽ' => 'ḽ', 'Ḿ' => 'ḿ', 'Ṁ' => 'ṁ', 'Ṃ' => 'ṃ', 'Ṅ' => 'ṅ', 'Ṇ' => 'ṇ', 'Ṉ' => 'ṉ', 'Ṋ' => 'ṋ', 'Ṍ' => 'ṍ', 'Ṏ' => 'ṏ', 'Ṑ' => 'ṑ', 'Ṓ' => 'ṓ', 'Ṕ' => 'ṕ', 'Ṗ' => 'ṗ', 'Ṙ' => 'ṙ', 'Ṛ' => 'ṛ', 'Ṝ' => 'ṝ', 'Ṟ' => 'ṟ', 'Ṡ' => 'ṡ', 'Ṣ' => 'ṣ', 'Ṥ' => 'ṥ', 'Ṧ' => 'ṧ', 'Ṩ' => 'ṩ', 'Ṫ' => 'ṫ', 'Ṭ' => 'ṭ', 'Ṯ' => 'ṯ', 'Ṱ' => 'ṱ', 'Ṳ' => 'ṳ', 'Ṵ' => 'ṵ', 'Ṷ' => 'ṷ', 'Ṹ' => 'ṹ', 'Ṻ' => 'ṻ', 'Ṽ' => 'ṽ', 'Ṿ' => 'ṿ', 'Ẁ' => 'ẁ', 'Ẃ' => 'ẃ', 'Ẅ' => 'ẅ', 'Ẇ' => 'ẇ', 'Ẉ' => 'ẉ', 'Ẋ' => 'ẋ', 'Ẍ' => 'ẍ', 'Ẏ' => 'ẏ', 'Ẑ' => 'ẑ', 'Ẓ' => 'ẓ', 'Ẕ' => 'ẕ', 'ẞ' => 'ß', 'Ạ' => 'ạ', 'Ả' => 'ả', 'Ấ' => 'ấ', 'Ầ' => 'ầ', 'Ẩ' => 'ẩ', 'Ẫ' => 'ẫ', 'Ậ' => 'ậ', 'Ắ' => 'ắ', 'Ằ' => 'ằ', 'Ẳ' => 'ẳ', 'Ẵ' => 'ẵ', 'Ặ' => 'ặ', 'Ẹ' => 'ẹ', 'Ẻ' => 'ẻ', 'Ẽ' => 'ẽ', 'Ế' => 'ế', 'Ề' => 'ề', 'Ể' => 'ể', 'Ễ' => 'ễ', 'Ệ' => 'ệ', 'Ỉ' => 'ỉ', 'Ị' => 'ị', 'Ọ' => 'ọ', 'Ỏ' => 'ỏ', 'Ố' => 'ố', 'Ồ' => 'ồ', 'Ổ' => 'ổ', 'Ỗ' => 'ỗ', 'Ộ' => 'ộ', 'Ớ' => 'ớ', 'Ờ' => 'ờ', 'Ở' => 'ở', 'Ỡ' => 'ỡ', 'Ợ' => 'ợ', 'Ụ' => 'ụ', 'Ủ' => 'ủ', 'Ứ' => 'ứ', 'Ừ' => 'ừ', 'Ử' => 'ử', 'Ữ' => 'ữ', 'Ự' => 'ự', 'Ỳ' => 'ỳ', 'Ỵ' => 'ỵ', 'Ỷ' => 'ỷ', 'Ỹ' => 'ỹ', 'Ỻ' => 'ỻ', 'Ỽ' => 'ỽ', 'Ỿ' => 'ỿ', 'Ἀ' => 'ἀ', 'Ἁ' => 'ἁ', 'Ἂ' => 'ἂ', 'Ἃ' => 'ἃ', 'Ἄ' => 'ἄ', 'Ἅ' => 'ἅ', 'Ἆ' => 'ἆ', 'Ἇ' => 'ἇ', 'Ἐ' => 'ἐ', 'Ἑ' => 'ἑ', 'Ἒ' => 'ἒ', 'Ἓ' => 'ἓ', 'Ἔ' => 'ἔ', 'Ἕ' => 'ἕ', 'Ἠ' => 'ἠ', 'Ἡ' => 'ἡ', 'Ἢ' => 'ἢ', 'Ἣ' => 'ἣ', 'Ἤ' => 'ἤ', 'Ἥ' => 'ἥ', 'Ἦ' => 'ἦ', 'Ἧ' => 'ἧ', 'Ἰ' => 'ἰ', 'Ἱ' => 'ἱ', 'Ἲ' => 'ἲ', 'Ἳ' => 'ἳ', 'Ἴ' => 'ἴ', 'Ἵ' => 'ἵ', 'Ἶ' => 'ἶ', 'Ἷ' => 'ἷ', 'Ὀ' => 'ὀ', 'Ὁ' => 'ὁ', 'Ὂ' => 'ὂ', 'Ὃ' => 'ὃ', 'Ὄ' => 'ὄ', 'Ὅ' => 'ὅ', 'Ὑ' => 'ὑ', 'Ὓ' => 'ὓ', 'Ὕ' => 'ὕ', 'Ὗ' => 'ὗ', 'Ὠ' => 'ὠ', 'Ὡ' => 'ὡ', 'Ὢ' => 'ὢ', 'Ὣ' => 'ὣ', 'Ὤ' => 'ὤ', 'Ὥ' => 'ὥ', 'Ὦ' => 'ὦ', 'Ὧ' => 'ὧ', 'ᾈ' => 'ᾀ', 'ᾉ' => 'ᾁ', 'ᾊ' => 'ᾂ', 'ᾋ' => 'ᾃ', 'ᾌ' => 'ᾄ', 'ᾍ' => 'ᾅ', 'ᾎ' => 'ᾆ', 'ᾏ' => 'ᾇ', 'ᾘ' => 'ᾐ', 'ᾙ' => 'ᾑ', 'ᾚ' => 'ᾒ', 'ᾛ' => 'ᾓ', 'ᾜ' => 'ᾔ', 'ᾝ' => 'ᾕ', 'ᾞ' => 'ᾖ', 'ᾟ' => 'ᾗ', 'ᾨ' => 'ᾠ', 'ᾩ' => 'ᾡ', 'ᾪ' => 'ᾢ', 'ᾫ' => 'ᾣ', 'ᾬ' => 'ᾤ', 'ᾭ' => 'ᾥ', 'ᾮ' => 'ᾦ', 'ᾯ' => 'ᾧ', 'Ᾰ' => 'ᾰ', 'Ᾱ' => 'ᾱ', 'Ὰ' => 'ὰ', 'Ά' => 'ά', 'ᾼ' => 'ᾳ', 'Ὲ' => 'ὲ', 'Έ' => 'έ', 'Ὴ' => 'ὴ', 'Ή' => 'ή', 'ῌ' => 'ῃ', 'Ῐ' => 'ῐ', 'Ῑ' => 'ῑ', 'Ὶ' => 'ὶ', 'Ί' => 'ί', 'Ῠ' => 'ῠ', 'Ῡ' => 'ῡ', 'Ὺ' => 'ὺ', 'Ύ' => 'ύ', 'Ῥ' => 'ῥ', 'Ὸ' => 'ὸ', 'Ό' => 'ό', 'Ὼ' => 'ὼ', 'Ώ' => 'ώ', 'ῼ' => 'ῳ', 'Ω' => 'ω', 'K' => 'k', 'Å' => 'å', 'Ⅎ' => 'ⅎ', 'Ⅰ' => 'ⅰ', 'Ⅱ' => 'ⅱ', 'Ⅲ' => 'ⅲ', 'Ⅳ' => 'ⅳ', 'Ⅴ' => 'ⅴ', 'Ⅵ' => 'ⅵ', 'Ⅶ' => 'ⅶ', 'Ⅷ' => 'ⅷ', 'Ⅸ' => 'ⅸ', 'Ⅹ' => 'ⅹ', 'Ⅺ' => 'ⅺ', 'Ⅻ' => 'ⅻ', 'Ⅼ' => 'ⅼ', 'Ⅽ' => 'ⅽ', 'Ⅾ' => 'ⅾ', 'Ⅿ' => 'ⅿ', 'Ↄ' => 'ↄ', 'Ⓐ' => 'ⓐ', 'Ⓑ' => 'ⓑ', 'Ⓒ' => 'ⓒ', 'Ⓓ' => 'ⓓ', 'Ⓔ' => 'ⓔ', 'Ⓕ' => 'ⓕ', 'Ⓖ' => 'ⓖ', 'Ⓗ' => 'ⓗ', 'Ⓘ' => 'ⓘ', 'Ⓙ' => 'ⓙ', 'Ⓚ' => 'ⓚ', 'Ⓛ' => 'ⓛ', 'Ⓜ' => 'ⓜ', 'Ⓝ' => 'ⓝ', 'Ⓞ' => 'ⓞ', 'Ⓟ' => 'ⓟ', 'Ⓠ' => 'ⓠ', 'Ⓡ' => 'ⓡ', 'Ⓢ' => 'ⓢ', 'Ⓣ' => 'ⓣ', 'Ⓤ' => 'ⓤ', 'Ⓥ' => 'ⓥ', 'Ⓦ' => 'ⓦ', 'Ⓧ' => 'ⓧ', 'Ⓨ' => 'ⓨ', 'Ⓩ' => 'ⓩ', 'Ⰰ' => 'ⰰ', 'Ⰱ' => 'ⰱ', 'Ⰲ' => 'ⰲ', 'Ⰳ' => 'ⰳ', 'Ⰴ' => 'ⰴ', 'Ⰵ' => 'ⰵ', 'Ⰶ' => 'ⰶ', 'Ⰷ' => 'ⰷ', 'Ⰸ' => 'ⰸ', 'Ⰹ' => 'ⰹ', 'Ⰺ' => 'ⰺ', 'Ⰻ' => 'ⰻ', 'Ⰼ' => 'ⰼ', 'Ⰽ' => 'ⰽ', 'Ⰾ' => 'ⰾ', 'Ⰿ' => 'ⰿ', 'Ⱀ' => 'ⱀ', 'Ⱁ' => 'ⱁ', 'Ⱂ' => 'ⱂ', 'Ⱃ' => 'ⱃ', 'Ⱄ' => 'ⱄ', 'Ⱅ' => 'ⱅ', 'Ⱆ' => 'ⱆ', 'Ⱇ' => 'ⱇ', 'Ⱈ' => 'ⱈ', 'Ⱉ' => 'ⱉ', 'Ⱊ' => 'ⱊ', 'Ⱋ' => 'ⱋ', 'Ⱌ' => 'ⱌ', 'Ⱍ' => 'ⱍ', 'Ⱎ' => 'ⱎ', 'Ⱏ' => 'ⱏ', 'Ⱐ' => 'ⱐ', 'Ⱑ' => 'ⱑ', 'Ⱒ' => 'ⱒ', 'Ⱓ' => 'ⱓ', 'Ⱔ' => 'ⱔ', 'Ⱕ' => 'ⱕ', 'Ⱖ' => 'ⱖ', 'Ⱗ' => 'ⱗ', 'Ⱘ' => 'ⱘ', 'Ⱙ' => 'ⱙ', 'Ⱚ' => 'ⱚ', 'Ⱛ' => 'ⱛ', 'Ⱜ' => 'ⱜ', 'Ⱝ' => 'ⱝ', 'Ⱞ' => 'ⱞ', 'Ⱡ' => 'ⱡ', 'Ɫ' => 'ɫ', 'Ᵽ' => 'ᵽ', 'Ɽ' => 'ɽ', 'Ⱨ' => 'ⱨ', 'Ⱪ' => 'ⱪ', 'Ⱬ' => 'ⱬ', 'Ɑ' => 'ɑ', 'Ɱ' => 'ɱ', 'Ɐ' => 'ɐ', 'Ɒ' => 'ɒ', 'Ⱳ' => 'ⱳ', 'Ⱶ' => 'ⱶ', 'Ȿ' => 'ȿ', 'Ɀ' => 'ɀ', 'Ⲁ' => 'ⲁ', 'Ⲃ' => 'ⲃ', 'Ⲅ' => 'ⲅ', 'Ⲇ' => 'ⲇ', 'Ⲉ' => 'ⲉ', 'Ⲋ' => 'ⲋ', 'Ⲍ' => 'ⲍ', 'Ⲏ' => 'ⲏ', 'Ⲑ' => 'ⲑ', 'Ⲓ' => 'ⲓ', 'Ⲕ' => 'ⲕ', 'Ⲗ' => 'ⲗ', 'Ⲙ' => 'ⲙ', 'Ⲛ' => 'ⲛ', 'Ⲝ' => 'ⲝ', 'Ⲟ' => 'ⲟ', 'Ⲡ' => 'ⲡ', 'Ⲣ' => 'ⲣ', 'Ⲥ' => 'ⲥ', 'Ⲧ' => 'ⲧ', 'Ⲩ' => 'ⲩ', 'Ⲫ' => 'ⲫ', 'Ⲭ' => 'ⲭ', 'Ⲯ' => 'ⲯ', 'Ⲱ' => 'ⲱ', 'Ⲳ' => 'ⲳ', 'Ⲵ' => 'ⲵ', 'Ⲷ' => 'ⲷ', 'Ⲹ' => 'ⲹ', 'Ⲻ' => 'ⲻ', 'Ⲽ' => 'ⲽ', 'Ⲿ' => 'ⲿ', 'Ⳁ' => 'ⳁ', 'Ⳃ' => 'ⳃ', 'Ⳅ' => 'ⳅ', 'Ⳇ' => 'ⳇ', 'Ⳉ' => 'ⳉ', 'Ⳋ' => 'ⳋ', 'Ⳍ' => 'ⳍ', 'Ⳏ' => 'ⳏ', 'Ⳑ' => 'ⳑ', 'Ⳓ' => 'ⳓ', 'Ⳕ' => 'ⳕ', 'Ⳗ' => 'ⳗ', 'Ⳙ' => 'ⳙ', 'Ⳛ' => 'ⳛ', 'Ⳝ' => 'ⳝ', 'Ⳟ' => 'ⳟ', 'Ⳡ' => 'ⳡ', 'Ⳣ' => 'ⳣ', 'Ⳬ' => 'ⳬ', 'Ⳮ' => 'ⳮ', 'Ⳳ' => 'ⳳ', 'Ꙁ' => 'ꙁ', 'Ꙃ' => 'ꙃ', 'Ꙅ' => 'ꙅ', 'Ꙇ' => 'ꙇ', 'Ꙉ' => 'ꙉ', 'Ꙋ' => 'ꙋ', 'Ꙍ' => 'ꙍ', 'Ꙏ' => 'ꙏ', 'Ꙑ' => 'ꙑ', 'Ꙓ' => 'ꙓ', 'Ꙕ' => 'ꙕ', 'Ꙗ' => 'ꙗ', 'Ꙙ' => 'ꙙ', 'Ꙛ' => 'ꙛ', 'Ꙝ' => 'ꙝ', 'Ꙟ' => 'ꙟ', 'Ꙡ' => 'ꙡ', 'Ꙣ' => 'ꙣ', 'Ꙥ' => 'ꙥ', 'Ꙧ' => 'ꙧ', 'Ꙩ' => 'ꙩ', 'Ꙫ' => 'ꙫ', 'Ꙭ' => 'ꙭ', 'Ꚁ' => 'ꚁ', 'Ꚃ' => 'ꚃ', 'Ꚅ' => 'ꚅ', 'Ꚇ' => 'ꚇ', 'Ꚉ' => 'ꚉ', 'Ꚋ' => 'ꚋ', 'Ꚍ' => 'ꚍ', 'Ꚏ' => 'ꚏ', 'Ꚑ' => 'ꚑ', 'Ꚓ' => 'ꚓ', 'Ꚕ' => 'ꚕ', 'Ꚗ' => 'ꚗ', 'Ꚙ' => 'ꚙ', 'Ꚛ' => 'ꚛ', 'Ꜣ' => 'ꜣ', 'Ꜥ' => 'ꜥ', 'Ꜧ' => 'ꜧ', 'Ꜩ' => 'ꜩ', 'Ꜫ' => 'ꜫ', 'Ꜭ' => 'ꜭ', 'Ꜯ' => 'ꜯ', 'Ꜳ' => 'ꜳ', 'Ꜵ' => 'ꜵ', 'Ꜷ' => 'ꜷ', 'Ꜹ' => 'ꜹ', 'Ꜻ' => 'ꜻ', 'Ꜽ' => 'ꜽ', 'Ꜿ' => 'ꜿ', 'Ꝁ' => 'ꝁ', 'Ꝃ' => 'ꝃ', 'Ꝅ' => 'ꝅ', 'Ꝇ' => 'ꝇ', 'Ꝉ' => 'ꝉ', 'Ꝋ' => 'ꝋ', 'Ꝍ' => 'ꝍ', 'Ꝏ' => 'ꝏ', 'Ꝑ' => 'ꝑ', 'Ꝓ' => 'ꝓ', 'Ꝕ' => 'ꝕ', 'Ꝗ' => 'ꝗ', 'Ꝙ' => 'ꝙ', 'Ꝛ' => 'ꝛ', 'Ꝝ' => 'ꝝ', 'Ꝟ' => 'ꝟ', 'Ꝡ' => 'ꝡ', 'Ꝣ' => 'ꝣ', 'Ꝥ' => 'ꝥ', 'Ꝧ' => 'ꝧ', 'Ꝩ' => 'ꝩ', 'Ꝫ' => 'ꝫ', 'Ꝭ' => 'ꝭ', 'Ꝯ' => 'ꝯ', 'Ꝺ' => 'ꝺ', 'Ꝼ' => 'ꝼ', 'Ᵹ' => 'ᵹ', 'Ꝿ' => 'ꝿ', 'Ꞁ' => 'ꞁ', 'Ꞃ' => 'ꞃ', 'Ꞅ' => 'ꞅ', 'Ꞇ' => 'ꞇ', 'Ꞌ' => 'ꞌ', 'Ɥ' => 'ɥ', 'Ꞑ' => 'ꞑ', 'Ꞓ' => 'ꞓ', 'Ꞗ' => 'ꞗ', 'Ꞙ' => 'ꞙ', 'Ꞛ' => 'ꞛ', 'Ꞝ' => 'ꞝ', 'Ꞟ' => 'ꞟ', 'Ꞡ' => 'ꞡ', 'Ꞣ' => 'ꞣ', 'Ꞥ' => 'ꞥ', 'Ꞧ' => 'ꞧ', 'Ꞩ' => 'ꞩ', 'Ɦ' => 'ɦ', 'Ɜ' => 'ɜ', 'Ɡ' => 'ɡ', 'Ɬ' => 'ɬ', 'Ʞ' => 'ʞ', 'Ʇ' => 'ʇ', 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', '𐐀' => '𐐨', '𐐁' => '𐐩', '𐐂' => '𐐪', '𐐃' => '𐐫', '𐐄' => '𐐬', '𐐅' => '𐐭', '𐐆' => '𐐮', '𐐇' => '𐐯', '𐐈' => '𐐰', '𐐉' => '𐐱', '𐐊' => '𐐲', '𐐋' => '𐐳', '𐐌' => '𐐴', '𐐍' => '𐐵', '𐐎' => '𐐶', '𐐏' => '𐐷', '𐐐' => '𐐸', '𐐑' => '𐐹', '𐐒' => '𐐺', '𐐓' => '𐐻', '𐐔' => '𐐼', '𐐕' => '𐐽', '𐐖' => '𐐾', '𐐗' => '𐐿', '𐐘' => '𐑀', '𐐙' => '𐑁', '𐐚' => '𐑂', '𐐛' => '𐑃', '𐐜' => '𐑄', '𐐝' => '𐑅', '𐐞' => '𐑆', '𐐟' => '𐑇', '𐐠' => '𐑈', '𐐡' => '𐑉', '𐐢' => '𐑊', '𐐣' => '𐑋', '𐐤' => '𐑌', '𐐥' => '𐑍', '𐐦' => '𐑎', '𐐧' => '𐑏', '𑢠' => '𑣀', '𑢡' => '𑣁', '𑢢' => '𑣂', '𑢣' => '𑣃', '𑢤' => '𑣄', '𑢥' => '𑣅', '𑢦' => '𑣆', '𑢧' => '𑣇', '𑢨' => '𑣈', '𑢩' => '𑣉', '𑢪' => '𑣊', '𑢫' => '𑣋', '𑢬' => '𑣌', '𑢭' => '𑣍', '𑢮' => '𑣎', '𑢯' => '𑣏', '𑢰' => '𑣐', '𑢱' => '𑣑', '𑢲' => '𑣒', '𑢳' => '𑣓', '𑢴' => '𑣔', '𑢵' => '𑣕', '𑢶' => '𑣖', '𑢷' => '𑣗', '𑢸' => '𑣘', '𑢹' => '𑣙', '𑢺' => '𑣚', '𑢻' => '𑣛', '𑢼' => '𑣜', '𑢽' => '𑣝', '𑢾' => '𑣞', '𑢿' => '𑣟', ); $result =& $data; unset($data); return $result; 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', 'µ' => 'Μ', 'à' => 'À', 'á' => 'Á', 'â' => 'Â', 'ã' => 'Ã', 'ä' => 'Ä', 'å' => 'Å', 'æ' => 'Æ', 'ç' => 'Ç', 'è' => 'È', 'é' => 'É', 'ê' => 'Ê', 'ë' => 'Ë', 'ì' => 'Ì', 'í' => 'Í', 'î' => 'Î', 'ï' => 'Ï', 'ð' => 'Ð', 'ñ' => 'Ñ', 'ò' => 'Ò', 'ó' => 'Ó', 'ô' => 'Ô', 'õ' => 'Õ', 'ö' => 'Ö', 'ø' => 'Ø', 'ù' => 'Ù', 'ú' => 'Ú', 'û' => 'Û', 'ü' => 'Ü', 'ý' => 'Ý', 'þ' => 'Þ', 'ÿ' => 'Ÿ', 'ā' => 'Ā', 'ă' => 'Ă', 'ą' => 'Ą', 'ć' => 'Ć', 'ĉ' => 'Ĉ', 'ċ' => 'Ċ', 'č' => 'Č', 'ď' => 'Ď', 'đ' => 'Đ', 'ē' => 'Ē', 'ĕ' => 'Ĕ', 'ė' => 'Ė', 'ę' => 'Ę', 'ě' => 'Ě', 'ĝ' => 'Ĝ', 'ğ' => 'Ğ', 'ġ' => 'Ġ', 'ģ' => 'Ģ', 'ĥ' => 'Ĥ', 'ħ' => 'Ħ', 'ĩ' => 'Ĩ', 'ī' => 'Ī', 'ĭ' => 'Ĭ', 'į' => 'Į', 'ı' => 'I', 'ij' => 'IJ', 'ĵ' => 'Ĵ', 'ķ' => 'Ķ', 'ĺ' => 'Ĺ', 'ļ' => 'Ļ', 'ľ' => 'Ľ', 'ŀ' => 'Ŀ', 'ł' => 'Ł', 'ń' => 'Ń', 'ņ' => 'Ņ', 'ň' => 'Ň', 'ŋ' => 'Ŋ', 'ō' => 'Ō', 'ŏ' => 'Ŏ', 'ő' => 'Ő', 'œ' => 'Œ', 'ŕ' => 'Ŕ', 'ŗ' => 'Ŗ', 'ř' => 'Ř', 'ś' => 'Ś', 'ŝ' => 'Ŝ', 'ş' => 'Ş', 'š' => 'Š', 'ţ' => 'Ţ', 'ť' => 'Ť', 'ŧ' => 'Ŧ', 'ũ' => 'Ũ', 'ū' => 'Ū', 'ŭ' => 'Ŭ', 'ů' => 'Ů', 'ű' => 'Ű', 'ų' => 'Ų', 'ŵ' => 'Ŵ', 'ŷ' => 'Ŷ', 'ź' => 'Ź', 'ż' => 'Ż', 'ž' => 'Ž', 'ſ' => 'S', 'ƀ' => 'Ƀ', 'ƃ' => 'Ƃ', 'ƅ' => 'Ƅ', 'ƈ' => 'Ƈ', 'ƌ' => 'Ƌ', 'ƒ' => 'Ƒ', 'ƕ' => 'Ƕ', 'ƙ' => 'Ƙ', 'ƚ' => 'Ƚ', 'ƞ' => 'Ƞ', 'ơ' => 'Ơ', 'ƣ' => 'Ƣ', 'ƥ' => 'Ƥ', 'ƨ' => 'Ƨ', 'ƭ' => 'Ƭ', 'ư' => 'Ư', 'ƴ' => 'Ƴ', 'ƶ' => 'Ƶ', 'ƹ' => 'Ƹ', 'ƽ' => 'Ƽ', 'ƿ' => 'Ƿ', 'Dž' => 'DŽ', 'dž' => 'DŽ', 'Lj' => 'LJ', 'lj' => 'LJ', 'Nj' => 'NJ', 'nj' => 'NJ', 'ǎ' => 'Ǎ', 'ǐ' => 'Ǐ', 'ǒ' => 'Ǒ', 'ǔ' => 'Ǔ', 'ǖ' => 'Ǖ', 'ǘ' => 'Ǘ', 'ǚ' => 'Ǚ', 'ǜ' => 'Ǜ', 'ǝ' => 'Ǝ', 'ǟ' => 'Ǟ', 'ǡ' => 'Ǡ', 'ǣ' => 'Ǣ', 'ǥ' => 'Ǥ', 'ǧ' => 'Ǧ', 'ǩ' => 'Ǩ', 'ǫ' => 'Ǫ', 'ǭ' => 'Ǭ', 'ǯ' => 'Ǯ', 'Dz' => 'DZ', 'dz' => 'DZ', 'ǵ' => 'Ǵ', 'ǹ' => 'Ǹ', 'ǻ' => 'Ǻ', 'ǽ' => 'Ǽ', 'ǿ' => 'Ǿ', 'ȁ' => 'Ȁ', 'ȃ' => 'Ȃ', 'ȅ' => 'Ȅ', 'ȇ' => 'Ȇ', 'ȉ' => 'Ȉ', 'ȋ' => 'Ȋ', 'ȍ' => 'Ȍ', 'ȏ' => 'Ȏ', 'ȑ' => 'Ȑ', 'ȓ' => 'Ȓ', 'ȕ' => 'Ȕ', 'ȗ' => 'Ȗ', 'ș' => 'Ș', 'ț' => 'Ț', 'ȝ' => 'Ȝ', 'ȟ' => 'Ȟ', 'ȣ' => 'Ȣ', 'ȥ' => 'Ȥ', 'ȧ' => 'Ȧ', 'ȩ' => 'Ȩ', 'ȫ' => 'Ȫ', 'ȭ' => 'Ȭ', 'ȯ' => 'Ȯ', 'ȱ' => 'Ȱ', 'ȳ' => 'Ȳ', 'ȼ' => 'Ȼ', 'ȿ' => 'Ȿ', 'ɀ' => 'Ɀ', 'ɂ' => 'Ɂ', 'ɇ' => 'Ɇ', 'ɉ' => 'Ɉ', 'ɋ' => 'Ɋ', 'ɍ' => 'Ɍ', 'ɏ' => 'Ɏ', 'ɐ' => 'Ɐ', 'ɑ' => 'Ɑ', 'ɒ' => 'Ɒ', 'ɓ' => 'Ɓ', 'ɔ' => 'Ɔ', 'ɖ' => 'Ɖ', 'ɗ' => 'Ɗ', 'ə' => 'Ə', 'ɛ' => 'Ɛ', 'ɜ' => 'Ɜ', 'ɠ' => 'Ɠ', 'ɡ' => 'Ɡ', 'ɣ' => 'Ɣ', 'ɥ' => 'Ɥ', 'ɦ' => 'Ɦ', 'ɨ' => 'Ɨ', 'ɩ' => 'Ɩ', 'ɫ' => 'Ɫ', 'ɬ' => 'Ɬ', 'ɯ' => 'Ɯ', 'ɱ' => 'Ɱ', 'ɲ' => 'Ɲ', 'ɵ' => 'Ɵ', 'ɽ' => 'Ɽ', 'ʀ' => 'Ʀ', 'ʃ' => 'Ʃ', 'ʇ' => 'Ʇ', 'ʈ' => 'Ʈ', 'ʉ' => 'Ʉ', 'ʊ' => 'Ʊ', 'ʋ' => 'Ʋ', 'ʌ' => 'Ʌ', 'ʒ' => 'Ʒ', 'ʞ' => 'Ʞ', 'ͅ' => 'Ι', 'ͱ' => 'Ͱ', 'ͳ' => 'Ͳ', 'ͷ' => 'Ͷ', 'ͻ' => 'Ͻ', 'ͼ' => 'Ͼ', 'ͽ' => 'Ͽ', 'ά' => 'Ά', 'έ' => 'Έ', 'ή' => 'Ή', 'ί' => 'Ί', 'α' => 'Α', 'β' => 'Β', 'γ' => 'Γ', 'δ' => 'Δ', 'ε' => 'Ε', 'ζ' => 'Ζ', 'η' => 'Η', 'θ' => 'Θ', 'ι' => 'Ι', 'κ' => 'Κ', 'λ' => 'Λ', 'μ' => 'Μ', 'ν' => 'Ν', 'ξ' => 'Ξ', 'ο' => 'Ο', 'π' => 'Π', 'ρ' => 'Ρ', 'ς' => 'Σ', 'σ' => 'Σ', 'τ' => 'Τ', 'υ' => 'Υ', 'φ' => 'Φ', 'χ' => 'Χ', 'ψ' => 'Ψ', 'ω' => 'Ω', 'ϊ' => 'Ϊ', 'ϋ' => 'Ϋ', 'ό' => 'Ό', 'ύ' => 'Ύ', 'ώ' => 'Ώ', 'ϐ' => 'Β', 'ϑ' => 'Θ', 'ϕ' => 'Φ', 'ϖ' => 'Π', 'ϗ' => 'Ϗ', 'ϙ' => 'Ϙ', 'ϛ' => 'Ϛ', 'ϝ' => 'Ϝ', 'ϟ' => 'Ϟ', 'ϡ' => 'Ϡ', 'ϣ' => 'Ϣ', 'ϥ' => 'Ϥ', 'ϧ' => 'Ϧ', 'ϩ' => 'Ϩ', 'ϫ' => 'Ϫ', 'ϭ' => 'Ϭ', 'ϯ' => 'Ϯ', 'ϰ' => 'Κ', 'ϱ' => 'Ρ', 'ϲ' => 'Ϲ', 'ϳ' => 'Ϳ', 'ϵ' => 'Ε', 'ϸ' => 'Ϸ', 'ϻ' => 'Ϻ', 'а' => 'А', 'б' => 'Б', 'в' => 'В', 'г' => 'Г', 'д' => 'Д', 'е' => 'Е', 'ж' => 'Ж', 'з' => 'З', 'и' => 'И', 'й' => 'Й', 'к' => 'К', 'л' => 'Л', 'м' => 'М', 'н' => 'Н', 'о' => 'О', 'п' => 'П', 'р' => 'Р', 'с' => 'С', 'т' => 'Т', 'у' => 'У', 'ф' => 'Ф', 'х' => 'Х', 'ц' => 'Ц', 'ч' => 'Ч', 'ш' => 'Ш', 'щ' => 'Щ', 'ъ' => 'Ъ', 'ы' => 'Ы', 'ь' => 'Ь', 'э' => 'Э', 'ю' => 'Ю', 'я' => 'Я', 'ѐ' => 'Ѐ', 'ё' => 'Ё', 'ђ' => 'Ђ', 'ѓ' => 'Ѓ', 'є' => 'Є', 'ѕ' => 'Ѕ', 'і' => 'І', 'ї' => 'Ї', 'ј' => 'Ј', 'љ' => 'Љ', 'њ' => 'Њ', 'ћ' => 'Ћ', 'ќ' => 'Ќ', 'ѝ' => 'Ѝ', 'ў' => 'Ў', 'џ' => 'Џ', 'ѡ' => 'Ѡ', 'ѣ' => 'Ѣ', 'ѥ' => 'Ѥ', 'ѧ' => 'Ѧ', 'ѩ' => 'Ѩ', 'ѫ' => 'Ѫ', 'ѭ' => 'Ѭ', 'ѯ' => 'Ѯ', 'ѱ' => 'Ѱ', 'ѳ' => 'Ѳ', 'ѵ' => 'Ѵ', 'ѷ' => 'Ѷ', 'ѹ' => 'Ѹ', 'ѻ' => 'Ѻ', 'ѽ' => 'Ѽ', 'ѿ' => 'Ѿ', 'ҁ' => 'Ҁ', 'ҋ' => 'Ҋ', 'ҍ' => 'Ҍ', 'ҏ' => 'Ҏ', 'ґ' => 'Ґ', 'ғ' => 'Ғ', 'ҕ' => 'Ҕ', 'җ' => 'Җ', 'ҙ' => 'Ҙ', 'қ' => 'Қ', 'ҝ' => 'Ҝ', 'ҟ' => 'Ҟ', 'ҡ' => 'Ҡ', 'ң' => 'Ң', 'ҥ' => 'Ҥ', 'ҧ' => 'Ҧ', 'ҩ' => 'Ҩ', 'ҫ' => 'Ҫ', 'ҭ' => 'Ҭ', 'ү' => 'Ү', 'ұ' => 'Ұ', 'ҳ' => 'Ҳ', 'ҵ' => 'Ҵ', 'ҷ' => 'Ҷ', 'ҹ' => 'Ҹ', 'һ' => 'Һ', 'ҽ' => 'Ҽ', 'ҿ' => 'Ҿ', 'ӂ' => 'Ӂ', 'ӄ' => 'Ӄ', 'ӆ' => 'Ӆ', 'ӈ' => 'Ӈ', 'ӊ' => 'Ӊ', 'ӌ' => 'Ӌ', 'ӎ' => 'Ӎ', 'ӏ' => 'Ӏ', 'ӑ' => 'Ӑ', 'ӓ' => 'Ӓ', 'ӕ' => 'Ӕ', 'ӗ' => 'Ӗ', 'ә' => 'Ә', 'ӛ' => 'Ӛ', 'ӝ' => 'Ӝ', 'ӟ' => 'Ӟ', 'ӡ' => 'Ӡ', 'ӣ' => 'Ӣ', 'ӥ' => 'Ӥ', 'ӧ' => 'Ӧ', 'ө' => 'Ө', 'ӫ' => 'Ӫ', 'ӭ' => 'Ӭ', 'ӯ' => 'Ӯ', 'ӱ' => 'Ӱ', 'ӳ' => 'Ӳ', 'ӵ' => 'Ӵ', 'ӷ' => 'Ӷ', 'ӹ' => 'Ӹ', 'ӻ' => 'Ӻ', 'ӽ' => 'Ӽ', 'ӿ' => 'Ӿ', 'ԁ' => 'Ԁ', 'ԃ' => 'Ԃ', 'ԅ' => 'Ԅ', 'ԇ' => 'Ԇ', 'ԉ' => 'Ԉ', 'ԋ' => 'Ԋ', 'ԍ' => 'Ԍ', 'ԏ' => 'Ԏ', 'ԑ' => 'Ԑ', 'ԓ' => 'Ԓ', 'ԕ' => 'Ԕ', 'ԗ' => 'Ԗ', 'ԙ' => 'Ԙ', 'ԛ' => 'Ԛ', 'ԝ' => 'Ԝ', 'ԟ' => 'Ԟ', 'ԡ' => 'Ԡ', 'ԣ' => 'Ԣ', 'ԥ' => 'Ԥ', 'ԧ' => 'Ԧ', 'ԩ' => 'Ԩ', 'ԫ' => 'Ԫ', 'ԭ' => 'Ԭ', 'ԯ' => 'Ԯ', 'ա' => 'Ա', 'բ' => 'Բ', 'գ' => 'Գ', 'դ' => 'Դ', 'ե' => 'Ե', 'զ' => 'Զ', 'է' => 'Է', 'ը' => 'Ը', 'թ' => 'Թ', 'ժ' => 'Ժ', 'ի' => 'Ի', 'լ' => 'Լ', 'խ' => 'Խ', 'ծ' => 'Ծ', 'կ' => 'Կ', 'հ' => 'Հ', 'ձ' => 'Ձ', 'ղ' => 'Ղ', 'ճ' => 'Ճ', 'մ' => 'Մ', 'յ' => 'Յ', 'ն' => 'Ն', 'շ' => 'Շ', 'ո' => 'Ո', 'չ' => 'Չ', 'պ' => 'Պ', 'ջ' => 'Ջ', 'ռ' => 'Ռ', 'ս' => 'Ս', 'վ' => 'Վ', 'տ' => 'Տ', 'ր' => 'Ր', 'ց' => 'Ց', 'ւ' => 'Ւ', 'փ' => 'Փ', 'ք' => 'Ք', 'օ' => 'Օ', 'ֆ' => 'Ֆ', 'ᵹ' => 'Ᵹ', 'ᵽ' => 'Ᵽ', 'ḁ' => 'Ḁ', 'ḃ' => 'Ḃ', 'ḅ' => 'Ḅ', 'ḇ' => 'Ḇ', 'ḉ' => 'Ḉ', 'ḋ' => 'Ḋ', 'ḍ' => 'Ḍ', 'ḏ' => 'Ḏ', 'ḑ' => 'Ḑ', 'ḓ' => 'Ḓ', 'ḕ' => 'Ḕ', 'ḗ' => 'Ḗ', 'ḙ' => 'Ḙ', 'ḛ' => 'Ḛ', 'ḝ' => 'Ḝ', 'ḟ' => 'Ḟ', 'ḡ' => 'Ḡ', 'ḣ' => 'Ḣ', 'ḥ' => 'Ḥ', 'ḧ' => 'Ḧ', 'ḩ' => 'Ḩ', 'ḫ' => 'Ḫ', 'ḭ' => 'Ḭ', 'ḯ' => 'Ḯ', 'ḱ' => 'Ḱ', 'ḳ' => 'Ḳ', 'ḵ' => 'Ḵ', 'ḷ' => 'Ḷ', 'ḹ' => 'Ḹ', 'ḻ' => 'Ḻ', 'ḽ' => 'Ḽ', 'ḿ' => 'Ḿ', 'ṁ' => 'Ṁ', 'ṃ' => 'Ṃ', 'ṅ' => 'Ṅ', 'ṇ' => 'Ṇ', 'ṉ' => 'Ṉ', 'ṋ' => 'Ṋ', 'ṍ' => 'Ṍ', 'ṏ' => 'Ṏ', 'ṑ' => 'Ṑ', 'ṓ' => 'Ṓ', 'ṕ' => 'Ṕ', 'ṗ' => 'Ṗ', 'ṙ' => 'Ṙ', 'ṛ' => 'Ṛ', 'ṝ' => 'Ṝ', 'ṟ' => 'Ṟ', 'ṡ' => 'Ṡ', 'ṣ' => 'Ṣ', 'ṥ' => 'Ṥ', 'ṧ' => 'Ṧ', 'ṩ' => 'Ṩ', 'ṫ' => 'Ṫ', 'ṭ' => 'Ṭ', 'ṯ' => 'Ṯ', 'ṱ' => 'Ṱ', 'ṳ' => 'Ṳ', 'ṵ' => 'Ṵ', 'ṷ' => 'Ṷ', 'ṹ' => 'Ṹ', 'ṻ' => 'Ṻ', 'ṽ' => 'Ṽ', 'ṿ' => 'Ṿ', 'ẁ' => 'Ẁ', 'ẃ' => 'Ẃ', 'ẅ' => 'Ẅ', 'ẇ' => 'Ẇ', 'ẉ' => 'Ẉ', 'ẋ' => 'Ẋ', 'ẍ' => 'Ẍ', 'ẏ' => 'Ẏ', 'ẑ' => 'Ẑ', 'ẓ' => 'Ẓ', 'ẕ' => 'Ẕ', 'ẛ' => 'Ṡ', 'ạ' => 'Ạ', 'ả' => 'Ả', 'ấ' => 'Ấ', 'ầ' => 'Ầ', 'ẩ' => 'Ẩ', 'ẫ' => 'Ẫ', 'ậ' => 'Ậ', 'ắ' => 'Ắ', 'ằ' => 'Ằ', 'ẳ' => 'Ẳ', 'ẵ' => 'Ẵ', 'ặ' => 'Ặ', 'ẹ' => 'Ẹ', 'ẻ' => 'Ẻ', 'ẽ' => 'Ẽ', 'ế' => 'Ế', 'ề' => 'Ề', 'ể' => 'Ể', 'ễ' => 'Ễ', 'ệ' => 'Ệ', 'ỉ' => 'Ỉ', 'ị' => 'Ị', 'ọ' => 'Ọ', 'ỏ' => 'Ỏ', 'ố' => 'Ố', 'ồ' => 'Ồ', 'ổ' => 'Ổ', 'ỗ' => 'Ỗ', 'ộ' => 'Ộ', 'ớ' => 'Ớ', 'ờ' => 'Ờ', 'ở' => 'Ở', 'ỡ' => 'Ỡ', 'ợ' => 'Ợ', 'ụ' => 'Ụ', 'ủ' => 'Ủ', 'ứ' => 'Ứ', 'ừ' => 'Ừ', 'ử' => 'Ử', 'ữ' => 'Ữ', 'ự' => 'Ự', 'ỳ' => 'Ỳ', 'ỵ' => 'Ỵ', 'ỷ' => 'Ỷ', 'ỹ' => 'Ỹ', 'ỻ' => 'Ỻ', 'ỽ' => 'Ỽ', 'ỿ' => 'Ỿ', 'ἀ' => 'Ἀ', 'ἁ' => 'Ἁ', 'ἂ' => 'Ἂ', 'ἃ' => 'Ἃ', 'ἄ' => 'Ἄ', 'ἅ' => 'Ἅ', 'ἆ' => 'Ἆ', 'ἇ' => 'Ἇ', 'ἐ' => 'Ἐ', 'ἑ' => 'Ἑ', 'ἒ' => 'Ἒ', 'ἓ' => 'Ἓ', 'ἔ' => 'Ἔ', 'ἕ' => 'Ἕ', 'ἠ' => 'Ἠ', 'ἡ' => 'Ἡ', 'ἢ' => 'Ἢ', 'ἣ' => 'Ἣ', 'ἤ' => 'Ἤ', 'ἥ' => 'Ἥ', 'ἦ' => 'Ἦ', 'ἧ' => 'Ἧ', 'ἰ' => 'Ἰ', 'ἱ' => 'Ἱ', 'ἲ' => 'Ἲ', 'ἳ' => 'Ἳ', 'ἴ' => 'Ἴ', 'ἵ' => 'Ἵ', 'ἶ' => 'Ἶ', 'ἷ' => 'Ἷ', 'ὀ' => 'Ὀ', 'ὁ' => 'Ὁ', 'ὂ' => 'Ὂ', 'ὃ' => 'Ὃ', 'ὄ' => 'Ὄ', 'ὅ' => 'Ὅ', 'ὑ' => 'Ὑ', 'ὓ' => 'Ὓ', 'ὕ' => 'Ὕ', 'ὗ' => 'Ὗ', 'ὠ' => 'Ὠ', 'ὡ' => 'Ὡ', 'ὢ' => 'Ὢ', 'ὣ' => 'Ὣ', 'ὤ' => 'Ὤ', 'ὥ' => 'Ὥ', 'ὦ' => 'Ὦ', 'ὧ' => 'Ὧ', 'ὰ' => 'Ὰ', 'ά' => 'Ά', 'ὲ' => 'Ὲ', 'έ' => 'Έ', 'ὴ' => 'Ὴ', 'ή' => 'Ή', 'ὶ' => 'Ὶ', 'ί' => 'Ί', 'ὸ' => 'Ὸ', 'ό' => 'Ό', 'ὺ' => 'Ὺ', 'ύ' => 'Ύ', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', 'ᾀ' => 'ᾈ', 'ᾁ' => 'ᾉ', 'ᾂ' => 'ᾊ', 'ᾃ' => 'ᾋ', 'ᾄ' => 'ᾌ', 'ᾅ' => 'ᾍ', 'ᾆ' => 'ᾎ', 'ᾇ' => 'ᾏ', 'ᾐ' => 'ᾘ', 'ᾑ' => 'ᾙ', 'ᾒ' => 'ᾚ', 'ᾓ' => 'ᾛ', 'ᾔ' => 'ᾜ', 'ᾕ' => 'ᾝ', 'ᾖ' => 'ᾞ', 'ᾗ' => 'ᾟ', 'ᾠ' => 'ᾨ', 'ᾡ' => 'ᾩ', 'ᾢ' => 'ᾪ', 'ᾣ' => 'ᾫ', 'ᾤ' => 'ᾬ', 'ᾥ' => 'ᾭ', 'ᾦ' => 'ᾮ', 'ᾧ' => 'ᾯ', 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', 'ᾳ' => 'ᾼ', 'ι' => 'Ι', 'ῃ' => 'ῌ', 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ῥ' => 'Ῥ', 'ῳ' => 'ῼ', 'ⅎ' => 'Ⅎ', 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', 'ⅲ' => 'Ⅲ', 'ⅳ' => 'Ⅳ', 'ⅴ' => 'Ⅴ', 'ⅵ' => 'Ⅵ', 'ⅶ' => 'Ⅶ', 'ⅷ' => 'Ⅷ', 'ⅸ' => 'Ⅸ', 'ⅹ' => 'Ⅹ', 'ⅺ' => 'Ⅺ', 'ⅻ' => 'Ⅻ', 'ⅼ' => 'Ⅼ', 'ⅽ' => 'Ⅽ', 'ⅾ' => 'Ⅾ', 'ⅿ' => 'Ⅿ', 'ↄ' => 'Ↄ', 'ⓐ' => 'Ⓐ', 'ⓑ' => 'Ⓑ', 'ⓒ' => 'Ⓒ', 'ⓓ' => 'Ⓓ', 'ⓔ' => 'Ⓔ', 'ⓕ' => 'Ⓕ', 'ⓖ' => 'Ⓖ', 'ⓗ' => 'Ⓗ', 'ⓘ' => 'Ⓘ', 'ⓙ' => 'Ⓙ', 'ⓚ' => 'Ⓚ', 'ⓛ' => 'Ⓛ', 'ⓜ' => 'Ⓜ', 'ⓝ' => 'Ⓝ', 'ⓞ' => 'Ⓞ', 'ⓟ' => 'Ⓟ', 'ⓠ' => 'Ⓠ', 'ⓡ' => 'Ⓡ', 'ⓢ' => 'Ⓢ', 'ⓣ' => 'Ⓣ', 'ⓤ' => 'Ⓤ', 'ⓥ' => 'Ⓥ', 'ⓦ' => 'Ⓦ', 'ⓧ' => 'Ⓧ', 'ⓨ' => 'Ⓨ', 'ⓩ' => 'Ⓩ', 'ⰰ' => 'Ⰰ', 'ⰱ' => 'Ⰱ', 'ⰲ' => 'Ⰲ', 'ⰳ' => 'Ⰳ', 'ⰴ' => 'Ⰴ', 'ⰵ' => 'Ⰵ', 'ⰶ' => 'Ⰶ', 'ⰷ' => 'Ⰷ', 'ⰸ' => 'Ⰸ', 'ⰹ' => 'Ⰹ', 'ⰺ' => 'Ⰺ', 'ⰻ' => 'Ⰻ', 'ⰼ' => 'Ⰼ', 'ⰽ' => 'Ⰽ', 'ⰾ' => 'Ⰾ', 'ⰿ' => 'Ⰿ', 'ⱀ' => 'Ⱀ', 'ⱁ' => 'Ⱁ', 'ⱂ' => 'Ⱂ', 'ⱃ' => 'Ⱃ', 'ⱄ' => 'Ⱄ', 'ⱅ' => 'Ⱅ', 'ⱆ' => 'Ⱆ', 'ⱇ' => 'Ⱇ', 'ⱈ' => 'Ⱈ', 'ⱉ' => 'Ⱉ', 'ⱊ' => 'Ⱊ', 'ⱋ' => 'Ⱋ', 'ⱌ' => 'Ⱌ', 'ⱍ' => 'Ⱍ', 'ⱎ' => 'Ⱎ', 'ⱏ' => 'Ⱏ', 'ⱐ' => 'Ⱐ', 'ⱑ' => 'Ⱑ', 'ⱒ' => 'Ⱒ', 'ⱓ' => 'Ⱓ', 'ⱔ' => 'Ⱔ', 'ⱕ' => 'Ⱕ', 'ⱖ' => 'Ⱖ', 'ⱗ' => 'Ⱗ', 'ⱘ' => 'Ⱘ', 'ⱙ' => 'Ⱙ', 'ⱚ' => 'Ⱚ', 'ⱛ' => 'Ⱛ', 'ⱜ' => 'Ⱜ', 'ⱝ' => 'Ⱝ', 'ⱞ' => 'Ⱞ', 'ⱡ' => 'Ⱡ', 'ⱥ' => 'Ⱥ', 'ⱦ' => 'Ⱦ', 'ⱨ' => 'Ⱨ', 'ⱪ' => 'Ⱪ', 'ⱬ' => 'Ⱬ', 'ⱳ' => 'Ⱳ', 'ⱶ' => 'Ⱶ', 'ⲁ' => 'Ⲁ', 'ⲃ' => 'Ⲃ', 'ⲅ' => 'Ⲅ', 'ⲇ' => 'Ⲇ', 'ⲉ' => 'Ⲉ', 'ⲋ' => 'Ⲋ', 'ⲍ' => 'Ⲍ', 'ⲏ' => 'Ⲏ', 'ⲑ' => 'Ⲑ', 'ⲓ' => 'Ⲓ', 'ⲕ' => 'Ⲕ', 'ⲗ' => 'Ⲗ', 'ⲙ' => 'Ⲙ', 'ⲛ' => 'Ⲛ', 'ⲝ' => 'Ⲝ', 'ⲟ' => 'Ⲟ', 'ⲡ' => 'Ⲡ', 'ⲣ' => 'Ⲣ', 'ⲥ' => 'Ⲥ', 'ⲧ' => 'Ⲧ', 'ⲩ' => 'Ⲩ', 'ⲫ' => 'Ⲫ', 'ⲭ' => 'Ⲭ', 'ⲯ' => 'Ⲯ', 'ⲱ' => 'Ⲱ', 'ⲳ' => 'Ⲳ', 'ⲵ' => 'Ⲵ', 'ⲷ' => 'Ⲷ', 'ⲹ' => 'Ⲹ', 'ⲻ' => 'Ⲻ', 'ⲽ' => 'Ⲽ', 'ⲿ' => 'Ⲿ', 'ⳁ' => 'Ⳁ', 'ⳃ' => 'Ⳃ', 'ⳅ' => 'Ⳅ', 'ⳇ' => 'Ⳇ', 'ⳉ' => 'Ⳉ', 'ⳋ' => 'Ⳋ', 'ⳍ' => 'Ⳍ', 'ⳏ' => 'Ⳏ', 'ⳑ' => 'Ⳑ', 'ⳓ' => 'Ⳓ', 'ⳕ' => 'Ⳕ', 'ⳗ' => 'Ⳗ', 'ⳙ' => 'Ⳙ', 'ⳛ' => 'Ⳛ', 'ⳝ' => 'Ⳝ', 'ⳟ' => 'Ⳟ', 'ⳡ' => 'Ⳡ', 'ⳣ' => 'Ⳣ', 'ⳬ' => 'Ⳬ', 'ⳮ' => 'Ⳮ', 'ⳳ' => 'Ⳳ', 'ⴀ' => 'Ⴀ', 'ⴁ' => 'Ⴁ', 'ⴂ' => 'Ⴂ', 'ⴃ' => 'Ⴃ', 'ⴄ' => 'Ⴄ', 'ⴅ' => 'Ⴅ', 'ⴆ' => 'Ⴆ', 'ⴇ' => 'Ⴇ', 'ⴈ' => 'Ⴈ', 'ⴉ' => 'Ⴉ', 'ⴊ' => 'Ⴊ', 'ⴋ' => 'Ⴋ', 'ⴌ' => 'Ⴌ', 'ⴍ' => 'Ⴍ', 'ⴎ' => 'Ⴎ', 'ⴏ' => 'Ⴏ', 'ⴐ' => 'Ⴐ', 'ⴑ' => 'Ⴑ', 'ⴒ' => 'Ⴒ', 'ⴓ' => 'Ⴓ', 'ⴔ' => 'Ⴔ', 'ⴕ' => 'Ⴕ', 'ⴖ' => 'Ⴖ', 'ⴗ' => 'Ⴗ', 'ⴘ' => 'Ⴘ', 'ⴙ' => 'Ⴙ', 'ⴚ' => 'Ⴚ', 'ⴛ' => 'Ⴛ', 'ⴜ' => 'Ⴜ', 'ⴝ' => 'Ⴝ', 'ⴞ' => 'Ⴞ', 'ⴟ' => 'Ⴟ', 'ⴠ' => 'Ⴠ', 'ⴡ' => 'Ⴡ', 'ⴢ' => 'Ⴢ', 'ⴣ' => 'Ⴣ', 'ⴤ' => 'Ⴤ', 'ⴥ' => 'Ⴥ', 'ⴧ' => 'Ⴧ', 'ⴭ' => 'Ⴭ', 'ꙁ' => 'Ꙁ', 'ꙃ' => 'Ꙃ', 'ꙅ' => 'Ꙅ', 'ꙇ' => 'Ꙇ', 'ꙉ' => 'Ꙉ', 'ꙋ' => 'Ꙋ', 'ꙍ' => 'Ꙍ', 'ꙏ' => 'Ꙏ', 'ꙑ' => 'Ꙑ', 'ꙓ' => 'Ꙓ', 'ꙕ' => 'Ꙕ', 'ꙗ' => 'Ꙗ', 'ꙙ' => 'Ꙙ', 'ꙛ' => 'Ꙛ', 'ꙝ' => 'Ꙝ', 'ꙟ' => 'Ꙟ', 'ꙡ' => 'Ꙡ', 'ꙣ' => 'Ꙣ', 'ꙥ' => 'Ꙥ', 'ꙧ' => 'Ꙧ', 'ꙩ' => 'Ꙩ', 'ꙫ' => 'Ꙫ', 'ꙭ' => 'Ꙭ', 'ꚁ' => 'Ꚁ', 'ꚃ' => 'Ꚃ', 'ꚅ' => 'Ꚅ', 'ꚇ' => 'Ꚇ', 'ꚉ' => 'Ꚉ', 'ꚋ' => 'Ꚋ', 'ꚍ' => 'Ꚍ', 'ꚏ' => 'Ꚏ', 'ꚑ' => 'Ꚑ', 'ꚓ' => 'Ꚓ', 'ꚕ' => 'Ꚕ', 'ꚗ' => 'Ꚗ', 'ꚙ' => 'Ꚙ', 'ꚛ' => 'Ꚛ', 'ꜣ' => 'Ꜣ', 'ꜥ' => 'Ꜥ', 'ꜧ' => 'Ꜧ', 'ꜩ' => 'Ꜩ', 'ꜫ' => 'Ꜫ', 'ꜭ' => 'Ꜭ', 'ꜯ' => 'Ꜯ', 'ꜳ' => 'Ꜳ', 'ꜵ' => 'Ꜵ', 'ꜷ' => 'Ꜷ', 'ꜹ' => 'Ꜹ', 'ꜻ' => 'Ꜻ', 'ꜽ' => 'Ꜽ', 'ꜿ' => 'Ꜿ', 'ꝁ' => 'Ꝁ', 'ꝃ' => 'Ꝃ', 'ꝅ' => 'Ꝅ', 'ꝇ' => 'Ꝇ', 'ꝉ' => 'Ꝉ', 'ꝋ' => 'Ꝋ', 'ꝍ' => 'Ꝍ', 'ꝏ' => 'Ꝏ', 'ꝑ' => 'Ꝑ', 'ꝓ' => 'Ꝓ', 'ꝕ' => 'Ꝕ', 'ꝗ' => 'Ꝗ', 'ꝙ' => 'Ꝙ', 'ꝛ' => 'Ꝛ', 'ꝝ' => 'Ꝝ', 'ꝟ' => 'Ꝟ', 'ꝡ' => 'Ꝡ', 'ꝣ' => 'Ꝣ', 'ꝥ' => 'Ꝥ', 'ꝧ' => 'Ꝧ', 'ꝩ' => 'Ꝩ', 'ꝫ' => 'Ꝫ', 'ꝭ' => 'Ꝭ', 'ꝯ' => 'Ꝯ', 'ꝺ' => 'Ꝺ', 'ꝼ' => 'Ꝼ', 'ꝿ' => 'Ꝿ', 'ꞁ' => 'Ꞁ', 'ꞃ' => 'Ꞃ', 'ꞅ' => 'Ꞅ', 'ꞇ' => 'Ꞇ', 'ꞌ' => 'Ꞌ', 'ꞑ' => 'Ꞑ', 'ꞓ' => 'Ꞓ', 'ꞗ' => 'Ꞗ', 'ꞙ' => 'Ꞙ', 'ꞛ' => 'Ꞛ', 'ꞝ' => 'Ꞝ', 'ꞟ' => 'Ꞟ', 'ꞡ' => 'Ꞡ', 'ꞣ' => 'Ꞣ', 'ꞥ' => 'Ꞥ', 'ꞧ' => 'Ꞧ', 'ꞩ' => 'Ꞩ', 'a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', '𐐨' => '𐐀', '𐐩' => '𐐁', '𐐪' => '𐐂', '𐐫' => '𐐃', '𐐬' => '𐐄', '𐐭' => '𐐅', '𐐮' => '𐐆', '𐐯' => '𐐇', '𐐰' => '𐐈', '𐐱' => '𐐉', '𐐲' => '𐐊', '𐐳' => '𐐋', '𐐴' => '𐐌', '𐐵' => '𐐍', '𐐶' => '𐐎', '𐐷' => '𐐏', '𐐸' => '𐐐', '𐐹' => '𐐑', '𐐺' => '𐐒', '𐐻' => '𐐓', '𐐼' => '𐐔', '𐐽' => '𐐕', '𐐾' => '𐐖', '𐐿' => '𐐗', '𐑀' => '𐐘', '𐑁' => '𐐙', '𐑂' => '𐐚', '𐑃' => '𐐛', '𐑄' => '𐐜', '𐑅' => '𐐝', '𐑆' => '𐐞', '𐑇' => '𐐟', '𐑈' => '𐐠', '𐑉' => '𐐡', '𐑊' => '𐐢', '𐑋' => '𐐣', '𐑌' => '𐐤', '𐑍' => '𐐥', '𐑎' => '𐐦', '𐑏' => '𐐧', '𑣀' => '𑢠', '𑣁' => '𑢡', '𑣂' => '𑢢', '𑣃' => '𑢣', '𑣄' => '𑢤', '𑣅' => '𑢥', '𑣆' => '𑢦', '𑣇' => '𑢧', '𑣈' => '𑢨', '𑣉' => '𑢩', '𑣊' => '𑢪', '𑣋' => '𑢫', '𑣌' => '𑢬', '𑣍' => '𑢭', '𑣎' => '𑢮', '𑣏' => '𑢯', '𑣐' => '𑢰', '𑣑' => '𑢱', '𑣒' => '𑢲', '𑣓' => '𑢳', '𑣔' => '𑢴', '𑣕' => '𑢵', '𑣖' => '𑢶', '𑣗' => '𑢷', '𑣘' => '𑢸', '𑣙' => '𑢹', '𑣚' => '𑢺', '𑣛' => '𑢻', '𑣜' => '𑢼', '𑣝' => '𑢽', '𑣞' => '𑢾', '𑣟' => '𑢿', ); $result =& $data; unset($data); return $result; 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); $i = 0; $len = strlen($s); while ($i < $len) { $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; $uchr = substr($s, $i, $ulen); $i += $ulen; if (isset($map[$uchr])) { $uchr = $map[$uchr]; $nlen = strlen($uchr); if ($nlen == $ulen) { $nlen = $i; do { $s[--$nlen] = $uchr[--$ulen]; } while ($ulen); } else { $s = substr_replace($s, $uchr, $i - $ulen, $ulen); $len += $nlen - $ulen; $i += $nlen - $ulen; } } } } if (null === $encoding) { return $s; } return iconv('UTF-8', $encoding.'//IGNORE', $s); } public static function mb_internal_encoding($encoding = null) { if (null === $encoding) { return self::$internalEncoding; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { self::$internalEncoding = $encoding; return true; } return false; } public static function mb_language($lang = null) { if (null === $lang) { return self::$language; } switch ($lang = strtolower($lang)) { case 'uni': case 'neutral': self::$language = $lang; return true; } return false; } public static function mb_list_encodings() { return array('UTF-8'); } public static function mb_encoding_aliases($encoding) { switch (strtoupper($encoding)) { case 'UTF8': case 'UTF-8': return array('utf8'); } return false; } public static function mb_check_encoding($var = null, $encoding = null) { if (null === $encoding) { if (null === $var) { return false; } $encoding = self::$internalEncoding; } return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); } public static function mb_detect_encoding($str, $encodingList = null, $strict = false) { if (null === $encodingList) { $encodingList = self::$encodingList; } else { if (!is_array($encodingList)) { $encodingList = array_map('trim', explode(',', $encodingList)); } $encodingList = array_map('strtoupper', $encodingList); } foreach ($encodingList as $enc) { switch ($enc) { case 'ASCII': if (!preg_match('/[\x80-\xFF]/', $str)) { return $enc; } break; case 'UTF8': case 'UTF-8': if (preg_match('//u', $str)) { return 'UTF-8'; } break; default: if (0 === strncmp($enc, 'ISO-8859-', 9)) { return $enc; } } } return false; } public static function mb_detect_order($encodingList = null) { if (null === $encodingList) { return self::$encodingList; } if (!is_array($encodingList)) { $encodingList = array_map('trim', explode(',', $encodingList)); } $encodingList = array_map('strtoupper', $encodingList); foreach ($encodingList as $enc) { switch ($enc) { default: if (strncmp($enc, 'ISO-8859-', 9)) { return false; } case 'ASCII': case 'UTF8': case 'UTF-8': } } self::$encodingList = $encodingList; return true; } public static function mb_strlen($s, $encoding = null) { switch ($encoding = self::getEncoding($encoding)) { case 'ASCII': case 'CP850': return strlen($s); } return @iconv_strlen($s, $encoding); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('' === $needle .= '') { trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); return false; } return iconv_strpos($haystack, $needle, $offset, $encoding); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ($offset != (int) $offset) { $offset = 0; } elseif ($offset = (int) $offset) { if ($offset < 0) { $haystack = self::mb_substr($haystack, 0, $offset, $encoding); $offset = 0; } else { $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); } } $pos = iconv_strrpos($haystack, $needle, $encoding); return false !== $pos ? $offset + $pos : false; } public static function mb_strtolower($s, $encoding = null) { return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); } public static function mb_strtoupper($s, $encoding = null) { return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); } public static function mb_substitute_character($c = null) { if (0 === strcasecmp($c, 'none')) { return true; } return null !== $c ? false : 'none'; } public static function mb_substr($s, $start, $length = null, $encoding = null) { $encoding = self::getEncoding($encoding); if ($start < 0) { $start = iconv_strlen($s, $encoding) + $start; if ($start < 0) { $start = 0; } } if (null === $length) { $length = 2147483647; } elseif ($length < 0) { $length = iconv_strlen($s, $encoding) + $length - $start; if ($length < 0) { return ''; } } return iconv_substr($s, $start, $length, $encoding).''; } public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); return self::mb_strpos($haystack, $needle, $offset, $encoding); } public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) { $pos = self::mb_stripos($haystack, $needle, 0, $encoding); return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) { $encoding = self::getEncoding($encoding); $needle = self::mb_substr($needle, 0, 1, $encoding); $pos = iconv_strrpos($haystack, $needle, $encoding); return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) { $needle = self::mb_substr($needle, 0, 1, $encoding); $pos = self::mb_strripos($haystack, $needle, $encoding); return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); return self::mb_strrpos($haystack, $needle, $offset, $encoding); } public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) { $pos = strpos($haystack, $needle); if (false === $pos) { return false; } if ($part) { return substr($haystack, 0, $pos); } return substr($haystack, $pos); } public static function mb_get_info($type = 'all') { $info = array( 'internal_encoding' => self::$internalEncoding, 'http_output' => 'pass', 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', 'func_overload' => 0, 'func_overload_list' => 'no overload', 'mail_charset' => 'UTF-8', 'mail_header_encoding' => 'BASE64', 'mail_body_encoding' => 'BASE64', 'illegal_chars' => 0, 'encoding_translation' => 'Off', 'language' => self::$language, 'detect_order' => self::$encodingList, 'substitute_character' => 'none', 'strict_detection' => 'Off', ); if ('all' === $type) { return $info; } if (isset($info[$type])) { return $info[$type]; } return false; } public static function mb_http_input($type = '') { return false; } public static function mb_http_output($encoding = null) { return null !== $encoding ? 'pass' === $encoding : 'pass'; } public static function mb_strwidth($s, $encoding = null) { $encoding = self::getEncoding($encoding); if ('UTF-8' !== $encoding) { $s = iconv($encoding, 'UTF-8//IGNORE', $s); } $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); return ($wide << 1) + iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) { return substr_count($haystack, $needle); } public static function mb_output_handler($contents, $status) { return $contents; } public static function mb_chr($code, $encoding = null) { if (0x80 > $code %= 0x200000) { $s = chr($code); } elseif (0x800 > $code) { $s = chr(0xC0 | $code >> 6).chr(0x80 | $code & 0x3F); } elseif (0x10000 > $code) { $s = chr(0xE0 | $code >> 12).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F); } else { $s = chr(0xF0 | $code >> 18).chr(0x80 | $code >> 12 & 0x3F).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F); } if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { $s = mb_convert_encoding($s, $encoding, 'UTF-8'); } return $s; } public static function mb_ord($s, $encoding = null) { if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { $s = mb_convert_encoding($s, 'UTF-8', $encoding); } $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; if (0xF0 <= $code) { return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; } if (0xE0 <= $code) { return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; } if (0xC0 <= $code) { return (($code - 0xC0) << 6) + $s[2] - 0x80; } return $code; } private static function getSubpart($pos, $part, $haystack, $encoding) { if (false === $pos) { return false; } if ($part) { return self::mb_substr($haystack, 0, $pos, $encoding); } return self::mb_substr($haystack, $pos, null, $encoding); } private static function html_encoding_callback($m) { $i = 1; $entities = ''; $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); while (isset($m[$i])) { if (0x80 > $m[$i]) { $entities .= chr($m[$i++]); continue; } if (0xF0 <= $m[$i]) { $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; } elseif (0xE0 <= $m[$i]) { $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; } else { $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; } $entities .= '&#'.$c.';'; } return $entities; } private static function title_case_lower($s) { return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8'); } private static function title_case_upper($s) { return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8'); } private static function getData($file) { if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { return require $file; } return false; } private static function getEncoding($encoding) { if (null === $encoding) { return self::$internalEncoding; } $encoding = strtoupper($encoding); if ('8BIT' === $encoding || 'BINARY' === $encoding) { return 'CP850'; } if ('UTF8' === $encoding) { return 'UTF-8'; } return $encoding; } } debug = $debug; $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); } public static function register($debug = true, $charset = null, $fileLinkFormat = null) { $handler = new static($debug, $charset, $fileLinkFormat); $prev = set_exception_handler(array($handler, 'handle')); if (is_array($prev) && $prev[0] instanceof ErrorHandler) { restore_exception_handler(); $prev[0]->setExceptionHandler(array($handler, 'handle')); } return $handler; } public function setHandler(callable $handler = null) { $old = $this->handler; $this->handler = $handler; return $old; } public function setFileLinkFormat($fileLinkFormat) { $old = $this->fileLinkFormat; $this->fileLinkFormat = $fileLinkFormat; return $old; } public function handle(\Exception $exception) { if (null === $this->handler || $exception instanceof OutOfMemoryException) { $this->sendPhpResponse($exception); return; } $caughtLength = $this->caughtLength = 0; ob_start(function ($buffer) { $this->caughtBuffer = $buffer; return ''; }); $this->sendPhpResponse($exception); while (null === $this->caughtBuffer && ob_end_flush()) { } if (isset($this->caughtBuffer[0])) { ob_start(function ($buffer) { if ($this->caughtLength) { $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); if (isset($cleanBuffer[0])) { $buffer = $cleanBuffer; } } return $buffer; }); echo $this->caughtBuffer; $caughtLength = ob_get_length(); } $this->caughtBuffer = null; try { call_user_func($this->handler, $exception); $this->caughtLength = $caughtLength; } catch (\Exception $e) { if (!$caughtLength) { throw $exception; } } } public function sendPhpResponse($exception) { if (!$exception instanceof FlattenException) { $exception = FlattenException::create($exception); } if (!headers_sent()) { header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); foreach ($exception->getHeaders() as $name => $value) { header($name.': '.$value, false); } header('Content-Type: text/html; charset='.$this->charset); } echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); } public function getHtml($exception) { if (!$exception instanceof FlattenException) { $exception = FlattenException::create($exception); } return $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); } public function getContent(FlattenException $exception) { switch ($exception->getStatusCode()) { case 404: $title = 'Sorry, the page you are looking for could not be found.'; break; default: $title = 'Whoops, looks like something went wrong.'; } $content = ''; if ($this->debug) { try { $count = count($exception->getAllPrevious()); $total = $count + 1; foreach ($exception->toArray() as $position => $e) { $ind = $count - $position + 1; $class = $this->formatClass($e['class']); $message = nl2br($this->escapeHtml($e['message'])); $content .= sprintf(<<<'EOF'

    %d/%d %s%s: %s

      EOF , $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message); foreach ($e['trace'] as $trace) { $content .= '
    1. '; if ($trace['function']) { $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); } if (isset($trace['file']) && isset($trace['line'])) { $content .= $this->formatPath($trace['file'], $trace['line']); } $content .= "
    2. \n"; } $content .= "
    \n
    \n"; } } catch (\Exception $e) { if ($this->debug) { $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage())); } else { $title = 'Whoops, looks like something went wrong.'; } } } return <<

    $title

    $content EOF; } public function getStylesheet(FlattenException $exception) { return <<<'EOF' .sf-reset { font: 11px Verdana, Arial, sans-serif; color: #333 } .sf-reset .clear { clear:both; height:0; font-size:0; line-height:0; } .sf-reset .clear_fix:after { display:block; height:0; clear:both; visibility:hidden; } .sf-reset .clear_fix { display:inline-block; } .sf-reset * html .clear_fix { height:1%; } .sf-reset .clear_fix { display:block; } .sf-reset, .sf-reset .block { margin: auto } .sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; } .sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px } .sf-reset strong { font-weight:bold; } .sf-reset a { color:#6c6159; cursor: default; } .sf-reset a img { border:none; } .sf-reset a:hover { text-decoration:underline; } .sf-reset em { font-style:italic; } .sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif } .sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; } .sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; } .sf-reset .exception_message { margin-left: 3em; display: block; } .sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; } .sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px; border-bottom-right-radius: 16px; border-bottom-left-radius: 16px; border-bottom:1px solid #ccc; border-right:1px solid #ccc; border-left:1px solid #ccc; word-wrap: break-word; } .sf-reset .block_exception { background-color:#ddd; color: #333; padding:20px; border-top-left-radius: 16px; border-top-right-radius: 16px; border-top:1px solid #ccc; border-right:1px solid #ccc; border-left:1px solid #ccc; overflow: hidden; word-wrap: break-word; } .sf-reset a { background:none; color:#868686; text-decoration:none; } .sf-reset a:hover { background:none; color:#313131; text-decoration:underline; } .sf-reset ol { padding: 10px 0; } .sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px; border-radius: 10px; border: 1px solid #ccc; } EOF; } private function decorate($content, $css) { return << $content EOF; } private function formatClass($class) { $parts = explode('\\', $class); return sprintf('%s', $class, array_pop($parts)); } private function formatPath($path, $line) { $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); $fmt = $this->fileLinkFormat; if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) { return sprintf(' in %s line %d', $this->escapeHtml($link), $file, $line); } return sprintf(' in %s line %d', $this->escapeHtml($path), $file, $line); } private function formatArgs(array $args) { $result = array(); foreach ($args as $key => $item) { if ('object' === $item[0]) { $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); } elseif ('array' === $item[0]) { $formattedValue = sprintf('array(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); } elseif ('null' === $item[0]) { $formattedValue = 'null'; } elseif ('boolean' === $item[0]) { $formattedValue = ''.strtolower(var_export($item[1], true)).''; } elseif ('resource' === $item[0]) { $formattedValue = 'resource'; } else { $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true))); } $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); } return implode(', ', $result); } private function escapeHtml($str) { return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); } } throwAt(0, true); } DebugClassLoader::enable(); } } logs[] = array($level, $message, $context); } public function cleanLogs() { $logs = $this->logs; $this->logs = array(); return $logs; } } array('/', array())); public function __construct(callable $classLoader) { $this->classLoader = $classLoader; $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile'); if (!isset(self::$caseCheck)) { $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR); $i = strrpos($file, DIRECTORY_SEPARATOR); $dir = substr($file, 0, 1 + $i); $file = substr($file, 1 + $i); $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); $test = realpath($dir.$test); if (false === $test || false === $i) { self::$caseCheck = 0; } elseif (substr($test, -strlen($file)) === $file) { self::$caseCheck = 1; } elseif (false !== stripos(PHP_OS, 'darwin')) { self::$caseCheck = 2; } else { self::$caseCheck = 0; } } } public function getClassLoader() { return $this->classLoader; } public static function enable() { class_exists('Symfony\Component\Debug\ErrorHandler'); class_exists('Psr\Log\LogLevel'); if (!is_array($functions = spl_autoload_functions())) { return; } foreach ($functions as $function) { spl_autoload_unregister($function); } foreach ($functions as $function) { if (!is_array($function) || !$function[0] instanceof self) { $function = array(new static($function), 'loadClass'); } spl_autoload_register($function); } } public static function disable() { if (!is_array($functions = spl_autoload_functions())) { return; } foreach ($functions as $function) { spl_autoload_unregister($function); } foreach ($functions as $function) { if (is_array($function) && $function[0] instanceof self) { $function = $function[0]->getClassLoader(); } spl_autoload_register($function); } } public function loadClass($class) { ErrorHandler::stackErrors(); try { if ($this->isFinder) { if ($file = $this->classLoader[0]->findFile($class)) { require_once $file; } } else { call_user_func($this->classLoader, $class); $file = false; } } finally { ErrorHandler::unstackErrors(); } $exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); if ($class && '\\' === $class[0]) { $class = substr($class, 1); } if ($exists) { $refl = new \ReflectionClass($class); $name = $refl->getName(); if ($name !== $class && 0 === strcasecmp($name, $class)) { throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name)); } if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { @trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); } else { if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) { $len = 0; $ns = ''; } else { switch ($ns = substr($name, 0, $len)) { case 'Symfony\Bridge\\': case 'Symfony\Bundle\\': case 'Symfony\Component\\': $ns = 'Symfony\\'; $len = strlen($ns); break; } } $parent = get_parent_class($class); if (!$parent || strncmp($ns, $parent, $len)) { if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { @trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); } $parentInterfaces = array(); $deprecatedInterfaces = array(); if ($parent) { foreach (class_implements($parent) as $interface) { $parentInterfaces[$interface] = 1; } } foreach ($refl->getInterfaceNames() as $interface) { if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { $deprecatedInterfaces[] = $interface; } foreach (class_implements($interface) as $interface) { $parentInterfaces[$interface] = 1; } } foreach ($deprecatedInterfaces as $interface) { if (!isset($parentInterfaces[$interface])) { @trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); } } } } } if ($file) { if (!$exists) { if (false !== strpos($class, '/')) { throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); } throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); } if (self::$caseCheck) { $real = explode('\\', $class.strrchr($file, '.')); $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file)); $i = count($tail) - 1; $j = count($real) - 1; while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { --$i; --$j; } array_splice($tail, 0, $i + 1); } if (self::$caseCheck && $tail) { $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail); $tailLen = strlen($tail); $real = $refl->getFileName(); if (2 === self::$caseCheck) { $i = 1 + strrpos($real, '/'); $file = substr($real, $i); $real = substr($real, 0, $i); if (isset(self::$darwinCache[$real])) { $kDir = $real; } else { $kDir = strtolower($real); if (isset(self::$darwinCache[$kDir])) { $real = self::$darwinCache[$kDir][0]; } else { $dir = getcwd(); chdir($real); $real = getcwd().'/'; chdir($dir); $dir = $real; $k = $kDir; $i = strlen($dir) - 1; while (!isset(self::$darwinCache[$k])) { self::$darwinCache[$k] = array($dir, array()); self::$darwinCache[$dir] = &self::$darwinCache[$k]; while ('/' !== $dir[--$i]) { } $k = substr($k, 0, ++$i); $dir = substr($dir, 0, $i--); } } } $dirFiles = self::$darwinCache[$kDir][1]; if (isset($dirFiles[$file])) { $kFile = $file; } else { $kFile = strtolower($file); if (!isset($dirFiles[$kFile])) { foreach (scandir($real, 2) as $f) { if ('.' !== $f[0]) { $dirFiles[$f] = $f; if ($f === $file) { $kFile = $k = $file; } elseif ($f !== $k = strtolower($f)) { $dirFiles[$k] = $f; } } } self::$darwinCache[$kDir][1] = $dirFiles; } } $real .= $dirFiles[$kFile]; } if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) ) { throw new \RuntimeException(sprintf('Case mismatch between class and real file names: %s vs %s in %s', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1))); } } return true; } } } $messageLen) { return; } if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { return; } foreach (array('class', 'interface', 'trait') as $typeName) { $prefix = ucfirst($typeName).' \''; $prefixLen = strlen($prefix); if (0 !== strpos($error['message'], $prefix)) { continue; } $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); $tail = ' for another namespace?'; } else { $className = $fullyQualifiedClassName; $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); $tail = '?'; } if ($candidates = $this->getClassCandidates($className)) { $tail = array_pop($candidates).'"?'; if ($candidates) { $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; } else { $tail = ' for "'.$tail; } } $message .= "\nDid you forget a \"use\" statement".$tail; return new ClassNotFoundException($message, $exception); } } private function getClassCandidates($class) { if (!is_array($functions = spl_autoload_functions())) { return array(); } $classes = array(); foreach ($functions as $function) { if (!is_array($function)) { continue; } if ($function[0] instanceof DebugClassLoader) { $function = $function[0]->getClassLoader(); if (!is_array($function)) { continue; } } if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) { foreach ($function[0]->getPrefixes() as $prefix => $paths) { foreach ($paths as $path) { $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); } } } if ($function[0] instanceof ComposerClassLoader) { foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { foreach ($paths as $path) { $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); } } } } return array_unique($classes); } private function findClassInPath($path, $class, $prefix) { if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { return array(); } $classes = array(); $filename = $class.'.php'; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { $classes[] = $class; } } return $classes; } private function convertFileToClass($path, $file, $prefix) { $candidates = array( $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file), $prefix.$namespacedClass, $prefix.'\\'.$namespacedClass, str_replace('\\', '_', $namespacedClass), str_replace('\\', '_', $prefix.$namespacedClass), str_replace('\\', '_', $prefix.'\\'.$namespacedClass), ); if ($prefix) { $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); } foreach ($candidates as $candidate) { if ($this->classExists($candidate)) { return $candidate; } } require_once $file; foreach ($candidates as $candidate) { if ($this->classExists($candidate)) { return $candidate; } } } private function classExists($class) { return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); } } $messageLen) { return; } if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { return; } $prefix = 'Call to undefined function '; $prefixLen = strlen($prefix); if (0 !== strpos($error['message'], $prefix)) { return; } $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); } else { $functionName = $fullyQualifiedFunctionName; $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); } $candidates = array(); foreach (get_defined_functions() as $type => $definedFunctionNames) { foreach ($definedFunctionNames as $definedFunctionName) { if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); } else { $definedFunctionNameBasename = $definedFunctionName; } if ($definedFunctionNameBasename === $functionName) { $candidates[] = '\\'.$definedFunctionName; } } } if ($candidates) { sort($candidates); $last = array_pop($candidates).'"?'; if ($candidates) { $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; } else { $candidates = '"'.$last; } $message .= "\nDid you mean to call ".$candidates; } return new UndefinedFunctionException($message, $exception); } } setMessage($exception->getMessage()); $e->setCode($exception->getCode()); if ($exception instanceof HttpExceptionInterface) { $statusCode = $exception->getStatusCode(); $headers = array_merge($headers, $exception->getHeaders()); } if (null === $statusCode) { $statusCode = 500; } $e->setStatusCode($statusCode); $e->setHeaders($headers); $e->setTraceFromException($exception); $e->setClass(get_class($exception)); $e->setFile($exception->getFile()); $e->setLine($exception->getLine()); $previous = $exception->getPrevious(); if ($previous instanceof \Exception) { $e->setPrevious(static::create($previous)); } elseif ($previous instanceof \Throwable) { $e->setPrevious(static::create(new FatalThrowableError($previous))); } return $e; } public function toArray() { $exceptions = array(); foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { $exceptions[] = array( 'message' => $exception->getMessage(), 'class' => $exception->getClass(), 'trace' => $exception->getTrace(), ); } return $exceptions; } public function getStatusCode() { return $this->statusCode; } public function setStatusCode($code) { $this->statusCode = $code; } public function getHeaders() { return $this->headers; } public function setHeaders(array $headers) { $this->headers = $headers; } public function getClass() { return $this->class; } public function setClass($class) { $this->class = $class; } public function getFile() { return $this->file; } public function setFile($file) { $this->file = $file; } public function getLine() { return $this->line; } public function setLine($line) { $this->line = $line; } public function getMessage() { return $this->message; } public function setMessage($message) { $this->message = $message; } public function getCode() { return $this->code; } public function setCode($code) { $this->code = $code; } public function getPrevious() { return $this->previous; } public function setPrevious(FlattenException $previous) { $this->previous = $previous; } public function getAllPrevious() { $exceptions = array(); $e = $this; while ($e = $e->getPrevious()) { $exceptions[] = $e; } return $exceptions; } public function getTrace() { return $this->trace; } public function setTraceFromException(\Exception $exception) { $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); } public function setTrace($trace, $file, $line) { $this->trace = array(); $this->trace[] = array( 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => $file, 'line' => $line, 'args' => array(), ); foreach ($trace as $entry) { $class = ''; $namespace = ''; if (isset($entry['class'])) { $parts = explode('\\', $entry['class']); $class = array_pop($parts); $namespace = implode('\\', $parts); } $this->trace[] = array( 'namespace' => $namespace, 'short_class' => $class, 'class' => isset($entry['class']) ? $entry['class'] : '', 'type' => isset($entry['type']) ? $entry['type'] : '', 'function' => isset($entry['function']) ? $entry['function'] : null, 'file' => isset($entry['file']) ? $entry['file'] : null, 'line' => isset($entry['line']) ? $entry['line'] : null, 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), ); } } private function flattenArgs($args, $level = 0, &$count = 0) { $result = array(); foreach ($args as $key => $value) { if (++$count > 1e4) { return array('array', '*SKIPPED over 10000 entries*'); } if ($value instanceof \__PHP_Incomplete_Class) { $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); } elseif (is_object($value)) { $result[$key] = array('object', get_class($value)); } elseif (is_array($value)) { if ($level > 10) { $result[$key] = array('array', '*DEEP NESTED ARRAY*'); } else { $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count)); } } elseif (null === $value) { $result[$key] = array('null', null); } elseif (is_bool($value)) { $result[$key] = array('boolean', $value); } elseif (is_int($value)) { $result[$key] = array('integer', $value); } elseif (is_float($value)) { $result[$key] = array('float', $value); } elseif (is_resource($value)) { $result[$key] = array('resource', get_resource_type($value)); } else { $result[$key] = array('string', (string) $value); } } return $result; } private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) { $array = new \ArrayObject($value); return $array['__PHP_Incomplete_Class_Name']; } } getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), $previous->getPrevious() ); $this->setTrace($previous->getTrace()); } } context = $context; } public function getContext() { return $this->context; } } severity = $severity; $this->file = $file; $this->line = $line; } public function getSeverity() { return $this->severity; } public function getFile() { return $this->file; } public function getLine() { return $this->line; } public function JsonSerialize() { return array( 'severity' => $this->severity, 'file' => $this->file, 'line' => $this->line, ); } } getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), $previous->getPrevious() ); $this->setTrace($previous->getTrace()); } } setTrace($trace); } elseif (null !== $traceOffset) { if (function_exists('xdebug_get_function_stack')) { $trace = xdebug_get_function_stack(); if (0 < $traceOffset) { array_splice($trace, -$traceOffset); } foreach ($trace as &$frame) { if (!isset($frame['type'])) { if (isset($frame['class'])) { $frame['type'] = '::'; } } elseif ('dynamic' === $frame['type']) { $frame['type'] = '->'; } elseif ('static' === $frame['type']) { $frame['type'] = '::'; } if (!$traceArgs) { unset($frame['params'], $frame['args']); } elseif (isset($frame['params']) && !isset($frame['args'])) { $frame['args'] = $frame['params']; unset($frame['params']); } } unset($frame); $trace = array_reverse($trace); } elseif (function_exists('symfony_debug_backtrace')) { $trace = symfony_debug_backtrace(); if (0 < $traceOffset) { array_splice($trace, 0, $traceOffset); } } else { $trace = array(); } $this->setTrace($trace); } } protected function setTrace($trace) { $traceReflector = new \ReflectionProperty('Exception', 'trace'); $traceReflector->setAccessible(true); $traceReflector->setValue($this, $trace); } } getMessage(); $severity = E_PARSE; } elseif ($e instanceof \TypeError) { $message = 'Type error: '.$e->getMessage(); $severity = E_RECOVERABLE_ERROR; } else { $message = $e->getMessage(); $severity = E_ERROR; } \ErrorException::__construct( $message, $e->getCode(), $severity, $e->getFile(), $e->getLine() ); $this->setTrace($e->getTrace()); } } getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), $previous->getPrevious() ); $this->setTrace($previous->getTrace()); } } 'Deprecated', E_USER_DEPRECATED => 'User Deprecated', E_NOTICE => 'Notice', E_USER_NOTICE => 'User Notice', E_STRICT => 'Runtime Notice', E_WARNING => 'Warning', E_USER_WARNING => 'User Warning', E_COMPILE_WARNING => 'Compile Warning', E_CORE_WARNING => 'Core Warning', E_USER_ERROR => 'User Error', E_RECOVERABLE_ERROR => 'Catchable Fatal Error', E_COMPILE_ERROR => 'Compile Error', E_PARSE => 'Parse Error', E_ERROR => 'Error', E_CORE_ERROR => 'Core Error', ); private $loggers = array( E_DEPRECATED => array(null, LogLevel::INFO), E_USER_DEPRECATED => array(null, LogLevel::INFO), E_NOTICE => array(null, LogLevel::WARNING), E_USER_NOTICE => array(null, LogLevel::WARNING), E_STRICT => array(null, LogLevel::WARNING), E_WARNING => array(null, LogLevel::WARNING), E_USER_WARNING => array(null, LogLevel::WARNING), E_COMPILE_WARNING => array(null, LogLevel::WARNING), E_CORE_WARNING => array(null, LogLevel::WARNING), E_USER_ERROR => array(null, LogLevel::CRITICAL), E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), E_PARSE => array(null, LogLevel::CRITICAL), E_ERROR => array(null, LogLevel::CRITICAL), E_CORE_ERROR => array(null, LogLevel::CRITICAL), ); private $thrownErrors = 0x1FFF; private $scopedErrors = 0x1FFF; private $tracedErrors = 0x77FB; private $screamedErrors = 0x55; private $loggedErrors = 0; private $traceReflector; private $isRecursive = 0; private $isRoot = false; private $exceptionHandler; private $bootstrappingLogger; private static $reservedMemory; private static $stackedErrors = array(); private static $stackedErrorLevels = array(); private static $toStringException = null; private static $exitCode = 0; public static function register(self $handler = null, $replace = true) { if (null === self::$reservedMemory) { self::$reservedMemory = str_repeat('x', 10240); register_shutdown_function(__CLASS__.'::handleFatalError'); } if ($handlerIsNew = null === $handler) { $handler = new static(); } if (null === $prev = set_error_handler(array($handler, 'handleError'))) { restore_error_handler(); set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors); $handler->isRoot = true; } if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) { $handler = $prev[0]; $replace = false; } if ($replace || !$prev) { $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException'))); } else { restore_error_handler(); } $handler->throwAt(E_ALL & $handler->thrownErrors, true); return $handler; } public function __construct(BufferingLogger $bootstrappingLogger = null) { if ($bootstrappingLogger) { $this->bootstrappingLogger = $bootstrappingLogger; $this->setDefaultLogger($bootstrappingLogger); } $this->traceReflector = new \ReflectionProperty('Exception', 'trace'); $this->traceReflector->setAccessible(true); } public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false) { $loggers = array(); if (is_array($levels)) { foreach ($levels as $type => $logLevel) { if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { $loggers[$type] = array($logger, $logLevel); } } } else { if (null === $levels) { $levels = E_ALL; } foreach ($this->loggers as $type => $log) { if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { $log[0] = $logger; $loggers[$type] = $log; } } } $this->setLoggers($loggers); } public function setLoggers(array $loggers) { $prevLogged = $this->loggedErrors; $prev = $this->loggers; $flush = array(); foreach ($loggers as $type => $log) { if (!isset($prev[$type])) { throw new \InvalidArgumentException('Unknown error type: '.$type); } if (!is_array($log)) { $log = array($log); } elseif (!array_key_exists(0, $log)) { throw new \InvalidArgumentException('No logger provided'); } if (null === $log[0]) { $this->loggedErrors &= ~$type; } elseif ($log[0] instanceof LoggerInterface) { $this->loggedErrors |= $type; } else { throw new \InvalidArgumentException('Invalid logger provided'); } $this->loggers[$type] = $log + $prev[$type]; if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { $flush[$type] = $type; } } $this->reRegister($prevLogged | $this->thrownErrors); if ($flush) { foreach ($this->bootstrappingLogger->cleanLogs() as $log) { $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; if (!isset($flush[$type])) { $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); } elseif ($this->loggers[$type][0]) { $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); } } } return $prev; } public function setExceptionHandler(callable $handler = null) { $prev = $this->exceptionHandler; $this->exceptionHandler = $handler; return $prev; } public function throwAt($levels, $replace = false) { $prev = $this->thrownErrors; $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; if (!$replace) { $this->thrownErrors |= $prev; } $this->reRegister($prev | $this->loggedErrors); return $prev; } public function scopeAt($levels, $replace = false) { $prev = $this->scopedErrors; $this->scopedErrors = (int) $levels; if (!$replace) { $this->scopedErrors |= $prev; } return $prev; } public function traceAt($levels, $replace = false) { $prev = $this->tracedErrors; $this->tracedErrors = (int) $levels; if (!$replace) { $this->tracedErrors |= $prev; } return $prev; } public function screamAt($levels, $replace = false) { $prev = $this->screamedErrors; $this->screamedErrors = (int) $levels; if (!$replace) { $this->screamedErrors |= $prev; } return $prev; } private function reRegister($prev) { if ($prev !== $this->thrownErrors | $this->loggedErrors) { $handler = set_error_handler('var_dump'); $handler = is_array($handler) ? $handler[0] : null; restore_error_handler(); if ($handler === $this) { restore_error_handler(); if ($this->isRoot) { set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors); } else { set_error_handler(array($this, 'handleError')); } } } } public function handleError($type, $message, $file, $line) { $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; $log = $this->loggedErrors & $type; $throw = $this->thrownErrors & $type & $level; $type &= $level | $this->screamedErrors; if (!$type || (!$log && !$throw)) { return $type && $log; } $scope = $this->scopedErrors & $type; if (4 < $numArgs = func_num_args()) { $context = $scope ? (func_get_arg(4) ?: array()) : array(); $backtrace = 5 < $numArgs ? func_get_arg(5) : null; } else { $context = array(); $backtrace = null; } if (isset($context['GLOBALS']) && $scope) { $e = $context; unset($e['GLOBALS'], $context); $context = $e; } if (null !== $backtrace && $type & E_ERROR) { $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace')); return true; } $logMessage = $this->levels[$type].': '.$message; if (null !== self::$toStringException) { $errorAsException = self::$toStringException; self::$toStringException = null; } elseif (!$throw && !($type & $level)) { $errorAsException = new SilencedErrorContext($type, $file, $line); } else { if ($scope) { $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context); } else { $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); } if ($throw || $this->tracedErrors & $type) { $backtrace = $backtrace ?: $errorAsException->getTrace(); $lightTrace = $backtrace; for ($i = 0; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { $lightTrace = array_slice($lightTrace, 1 + $i); break; } } if (!($throw || $this->scopedErrors & $type)) { for ($i = 0; isset($lightTrace[$i]); ++$i) { unset($lightTrace[$i]['args']); } } $this->traceReflector->setValue($errorAsException, $lightTrace); } else { $this->traceReflector->setValue($errorAsException, array()); } } if ($throw) { if (E_USER_ERROR & $type) { for ($i = 1; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) && '__toString' === $backtrace[$i]['function'] && '->' === $backtrace[$i]['type'] && !isset($backtrace[$i - 1]['class']) && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) ) { foreach ($context as $e) { if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) { if (1 === $i) { $errorAsException = $e; break; } self::$toStringException = $e; return true; } } if (1 < $i) { $this->handleException($errorAsException); return false; } } } } throw $errorAsException; } if ($this->isRecursive) { $log = 0; } elseif (self::$stackedErrorLevels) { self::$stackedErrors[] = array( $this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $logMessage, array('exception' => $errorAsException), ); } else { try { $this->isRecursive = true; $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; $this->loggers[$type][0]->log($level, $logMessage, array('exception' => $errorAsException)); } finally { $this->isRecursive = false; } } return $type && $log; } public function handleException($exception, array $error = null) { if (null === $error) { self::$exitCode = 255; } if (!$exception instanceof \Exception) { $exception = new FatalThrowableError($exception); } $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { if ($exception instanceof FatalErrorException) { if ($exception instanceof FatalThrowableError) { $error = array( 'type' => $type, 'message' => $message = $exception->getMessage(), 'file' => $exception->getFile(), 'line' => $exception->getLine(), ); } else { $message = 'Fatal '.$exception->getMessage(); } } elseif ($exception instanceof \ErrorException) { $message = 'Uncaught '.$exception->getMessage(); if ($exception instanceof ContextErrorException) { $e['context'] = $exception->getContext(); } } else { $message = 'Uncaught Exception: '.$exception->getMessage(); } } if ($this->loggedErrors & $type) { try { $this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception)); } catch (\Exception $handlerException) { } catch (\Throwable $handlerException) { } } if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { foreach ($this->getFatalErrorHandlers() as $handler) { if ($e = $handler->handleError($error, $exception)) { $exception = $e; break; } } } if (empty($this->exceptionHandler)) { throw $exception; } try { call_user_func($this->exceptionHandler, $exception); } catch (\Exception $handlerException) { } catch (\Throwable $handlerException) { } if (isset($handlerException)) { $this->exceptionHandler = null; $this->handleException($handlerException); } } public static function handleFatalError(array $error = null) { if (null === self::$reservedMemory) { return; } self::$reservedMemory = null; $handler = set_error_handler('var_dump'); $handler = is_array($handler) ? $handler[0] : null; restore_error_handler(); if (!$handler instanceof self) { return; } if ($exit = null === $error) { $error = error_get_last(); } try { while (self::$stackedErrorLevels) { static::unstackErrors(); } } catch (\Exception $exception) { } catch (\Throwable $exception) { } if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { $handler->throwAt(0, true); $trace = isset($error['backtrace']) ? $error['backtrace'] : null; if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); } else { $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); } } try { if (isset($exception)) { self::$exitCode = 255; $handler->handleException($exception, $error); } } catch (FatalErrorException $e) { } if ($exit && self::$exitCode) { $exitCode = self::$exitCode; register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); } } public static function stackErrors() { self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); } public static function unstackErrors() { $level = array_pop(self::$stackedErrorLevels); if (null !== $level) { $errorReportingLevel = error_reporting($level); if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { error_reporting($errorReportingLevel); } } if (empty(self::$stackedErrorLevels)) { $errors = self::$stackedErrors; self::$stackedErrors = array(); foreach ($errors as $error) { $error[0]->log($error[1], $error[2], $error[3]); } } } protected function getFatalErrorHandlers() { return array( new UndefinedFunctionFatalErrorHandler(), new UndefinedMethodFatalErrorHandler(), new ClassNotFoundFatalErrorHandler(), ); } } input = $input; $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($output); } public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false) { $messages = is_array($messages) ? array_values($messages) : array($messages); $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, true)); $this->newLine(); } public function title($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), )); $this->newLine(); } public function section($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), )); $this->newLine(); } public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } public function text($message) { $this->autoPrependText(); $messages = is_array($message) ? array_values($message) : array($message); foreach ($messages as $message) { $this->writeln(sprintf(' %s', $message)); } } public function comment($message) { $messages = is_array($message) ? array_values($message) : array($message); $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, null, null, ' // ')); $this->newLine(); } public function success($message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } public function warning($message) { $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); } public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } public function table(array $headers, array $rows) { $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('%s'); $table = new Table($this); $table->setHeaders($headers); $table->setRows($rows); $table->setStyle($style); $table->render(); $this->newLine(); } public function ask($question, $default = null, $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } public function askHidden($question, $validator = null) { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } public function confirm($question, $default = true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } public function choice($question, array $choices, $default = null) { if (null !== $default) { $values = array_flip($choices); $default = $values[$default]; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); } public function progressStart($max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } public function progressAdvance($step = 1) { $this->getProgressBar()->advance($step); } public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } public function createProgressBar($max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' !== DIRECTORY_SEPARATOR) { $progressBar->setEmptyBarCharacter('░'); $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); } return $progressBar; } public function askQuestion(Question $question) { if ($this->input->isInteractive()) { $this->autoPrependBlock(); } if (!$this->questionHelper) { $this->questionHelper = new SymfonyQuestionHelper(); } $answer = $this->questionHelper->ask($this->input, $this, $question); if ($this->input->isInteractive()) { $this->newLine(); $this->bufferedOutput->write("\n"); } return $answer; } public function writeln($messages, $type = self::OUTPUT_NORMAL) { parent::writeln($messages, $type); $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); } public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { parent::write($messages, $newline, $type); $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); } public function newLine($count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } private function getProgressBar() { if (!$this->progressBar) { throw new RuntimeException('The ProgressBar is not started.'); } return $this->progressBar; } private function autoPrependBlock() { $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { return $this->newLine(); } $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText() { $fetched = $this->bufferedOutput->fetch(); if ("\n" !== substr($fetched, -1)) { $this->newLine(); } } private function reduceBuffer($messages) { return array_map(function ($value) { return substr($value, -4); }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); } private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false) { $indentLength = 0; $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); $lines = array(); if (null !== $type) { $type = sprintf('[%s] ', $type); $indentLength = strlen($type); $lineIndentation = str_repeat(' ', $indentLength); } foreach ($messages as $key => $message) { if ($escape) { $message = OutputFormatter::escape($message); } $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true))); if (count($messages) > 1 && $key < count($messages) - 1) { $lines[] = ''; } } $firstLineIndex = 0; if ($padding && $this->isDecorated()) { $firstLineIndex = 1; array_unshift($lines, ''); $lines[] = ''; } foreach ($lines as $i => &$line) { if (null !== $type) { $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; } $line = $prefix.$line; $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); if ($style) { $line = sprintf('<%s>%s', $style, $line); } } return $lines; } } output = $output; } public function newLine($count = 1) { $this->output->write(str_repeat(PHP_EOL, $count)); } public function createProgressBar($max = 0) { return new ProgressBar($this->output, $max); } public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } public function writeln($messages, $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } public function setVerbosity($level) { $this->output->setVerbosity($level); } public function getVerbosity() { return $this->output->getVerbosity(); } public function setDecorated($decorated) { $this->output->setDecorated($decorated); } public function isDecorated() { return $this->output->isDecorated(); } public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } public function getFormatter() { return $this->output->getFormatter(); } public function isQuiet() { return $this->output->isQuiet(); } public function isVerbose() { return $this->output->isVerbose(); } public function isVeryVerbose() { return $this->output->isVeryVerbose(); } public function isDebug() { return $this->output->isDebug(); } } input = $input; } } 1, 'colspan' => 1, ); public function __construct($value = '', array $options = array()) { if (is_numeric($value) && !is_string($value)) { $value = (string) $value; } $this->value = $value; if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); } $this->options = array_merge($this->options, $options); } public function __toString() { return $this->value; } public function getColspan() { return (int) $this->options['colspan']; } public function getRowspan() { return (int) $this->options['rowspan']; } } $helper) { $this->set($helper, is_int($alias) ? null : $alias); } } public function set(HelperInterface $helper, $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } public function has($name) { return isset($this->helpers[$name]); } public function get($name) { if (!$this->has($name)) { throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); } return $this->helpers[$name]; } public function setCommand(Command $command = null) { $this->command = $command; } public function getCommand() { return $this->command; } public function getIterator() { return new \ArrayIterator($this->helpers); } } output = $output; if (!self::$styles) { self::$styles = self::initStyles(); } $this->setStyle('default'); } public static function setStyleDefinition($name, TableStyle $style) { if (!self::$styles) { self::$styles = self::initStyles(); } self::$styles[$name] = $style; } public static function getStyleDefinition($name) { if (!self::$styles) { self::$styles = self::initStyles(); } if (isset(self::$styles[$name])) { return self::$styles[$name]; } throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } public function setStyle($name) { $this->style = $this->resolveStyle($name); return $this; } public function getStyle() { return $this->style; } public function setColumnStyle($columnIndex, $name) { $columnIndex = intval($columnIndex); $this->columnStyles[$columnIndex] = $this->resolveStyle($name); return $this; } public function getColumnStyle($columnIndex) { if (isset($this->columnStyles[$columnIndex])) { return $this->columnStyles[$columnIndex]; } return $this->getStyle(); } public function setColumnWidth($columnIndex, $width) { $this->columnWidths[intval($columnIndex)] = intval($width); return $this; } public function setColumnWidths(array $widths) { $this->columnWidths = array(); foreach ($widths as $index => $width) { $this->setColumnWidth($index, $width); } return $this; } public function setHeaders(array $headers) { $headers = array_values($headers); if (!empty($headers) && !is_array($headers[0])) { $headers = array($headers); } $this->headers = $headers; return $this; } public function setRows(array $rows) { $this->rows = array(); return $this->addRows($rows); } public function addRows(array $rows) { foreach ($rows as $row) { $this->addRow($row); } return $this; } public function addRow($row) { if ($row instanceof TableSeparator) { $this->rows[] = $row; return $this; } if (!is_array($row)) { throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); } $this->rows[] = array_values($row); return $this; } public function setRow($column, array $row) { $this->rows[$column] = $row; return $this; } public function render() { $this->calculateNumberOfColumns(); $rows = $this->buildTableRows($this->rows); $headers = $this->buildTableRows($this->headers); $this->calculateColumnsWidth(array_merge($headers, $rows)); $this->renderRowSeparator(); if (!empty($headers)) { foreach ($headers as $header) { $this->renderRow($header, $this->style->getCellHeaderFormat()); $this->renderRowSeparator(); } } foreach ($rows as $row) { if ($row instanceof TableSeparator) { $this->renderRowSeparator(); } else { $this->renderRow($row, $this->style->getCellRowFormat()); } } if (!empty($rows)) { $this->renderRowSeparator(); } $this->cleanup(); } private function renderRowSeparator() { if (0 === $count = $this->numberOfColumns) { return; } if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { return; } $markup = $this->style->getCrossingChar(); for ($column = 0; $column < $count; ++$column) { $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar(); } $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); } private function renderColumnSeparator() { return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); } private function renderRow(array $row, $cellFormat) { if (empty($row)) { return; } $rowContent = $this->renderColumnSeparator(); foreach ($this->getRowColumns($row) as $column) { $rowContent .= $this->renderCell($row, $column, $cellFormat); $rowContent .= $this->renderColumnSeparator(); } $this->output->writeln($rowContent); } private function renderCell(array $row, $column, $cellFormat) { $cell = isset($row[$column]) ? $row[$column] : ''; $width = $this->effectiveColumnWidths[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; } } if (false !== $encoding = mb_detect_encoding($cell, null, true)) { $width += strlen($cell) - mb_strwidth($cell, $encoding); } $style = $this->getColumnStyle($column); if ($cell instanceof TableSeparator) { return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width)); } $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); $content = sprintf($style->getCellRowContentFormat(), $cell); return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); } private function calculateNumberOfColumns() { if (null !== $this->numberOfColumns) { return; } $columns = array(0); foreach (array_merge($this->headers, $this->rows) as $row) { if ($row instanceof TableSeparator) { continue; } $columns[] = $this->getNumberOfColumns($row); } $this->numberOfColumns = max($columns); } private function buildTableRows($rows) { $unmergedRows = array(); for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) { $rows = $this->fillNextRows($rows, $rowKey); foreach ($rows[$rowKey] as $column => $cell) { if (!strstr($cell, "\n")) { continue; } $lines = explode("\n", str_replace("\n", "\n", $cell)); foreach ($lines as $lineKey => $line) { if ($cell instanceof TableCell) { $line = new TableCell($line, array('colspan' => $cell->getColspan())); } if (0 === $lineKey) { $rows[$rowKey][$column] = $line; } else { $unmergedRows[$rowKey][$lineKey][$column] = $line; } } } } $tableRows = array(); foreach ($rows as $rowKey => $row) { $tableRows[] = $this->fillCells($row); if (isset($unmergedRows[$rowKey])) { $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); } } return $tableRows; } private function fillNextRows($rows, $line) { $unmergedRows = array(); foreach ($rows[$line] as $column => $cell) { if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; $lines = array($cell); if (strstr($cell, "\n")) { $lines = explode("\n", str_replace("\n", "\n", $cell)); $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); unset($lines[0]); } $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows); foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); if ($nbLines === $unmergedRowKey - $line) { break; } } } } foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); } } else { $row = $this->copyRow($rows, $unmergedRowKey - 1); foreach ($unmergedRow as $column => $cell) { if (!empty($cell)) { $row[$column] = $unmergedRow[$column]; } } array_splice($rows, $unmergedRowKey, 0, array($row)); } } return $rows; } private function fillCells($row) { $newRow = array(); foreach ($row as $column => $cell) { $newRow[] = $cell; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { $newRow[] = ''; } } } return $newRow ?: $row; } private function copyRow($rows, $line) { $row = $rows[$line]; foreach ($row as $cellKey => $cellValue) { $row[$cellKey] = ''; if ($cellValue instanceof TableCell) { $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); } } return $row; } private function getNumberOfColumns(array $row) { $columns = count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; } return $columns; } private function getRowColumns($row) { $columns = range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { if ($cell instanceof TableCell && $cell->getColspan() > 1) { $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); } } return $columns; } private function calculateColumnsWidth($rows) { for ($column = 0; $column < $this->numberOfColumns; ++$column) { $lengths = array(); foreach ($rows as $row) { if ($row instanceof TableSeparator) { continue; } foreach ($row as $i => $cell) { if ($cell instanceof TableCell) { $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); $textLength = Helper::strlen($textContent); if ($textLength > 0) { $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); foreach ($contentColumns as $position => $content) { $row[$i + $position] = $content; } } } } $lengths[] = $this->getCellWidth($row, $column); } $this->effectiveColumnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; } } private function getColumnSeparatorWidth() { return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); } private function getCellWidth(array $row, $column) { $cellWidth = 0; if (isset($row[$column])) { $cell = $row[$column]; $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); } $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0; return max($cellWidth, $columnWidth); } private function cleanup() { $this->effectiveColumnWidths = array(); $this->numberOfColumns = null; } private static function initStyles() { $borderless = new TableStyle(); $borderless ->setHorizontalBorderChar('=') ->setVerticalBorderChar(' ') ->setCrossingChar(' ') ; $compact = new TableStyle(); $compact ->setHorizontalBorderChar('') ->setVerticalBorderChar(' ') ->setCrossingChar('') ->setCellRowContentFormat('%s') ; $styleGuide = new TableStyle(); $styleGuide ->setHorizontalBorderChar('-') ->setVerticalBorderChar(' ') ->setCrossingChar(' ') ->setCellHeaderFormat('%s') ; return array( 'default' => new TableStyle(), 'borderless' => $borderless, 'compact' => $compact, 'symfony-style-guide' => $styleGuide, ); } private function resolveStyle($name) { if ($name instanceof TableStyle) { return $name; } if (isset(self::$styles[$name])) { return self::$styles[$name]; } throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } } '; private $format; private $internalFormat; private $redrawFreq = 1; private $output; private $step = 0; private $max; private $startTime; private $stepWidth; private $percent = 0.0; private $formatLineCount; private $messages = array(); private $overwrite = true; private $terminal; private $firstRun = true; private static $formatters; private static $formats; public function __construct(OutputInterface $output, $max = 0) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->output = $output; $this->setMaxSteps($max); $this->terminal = new Terminal(); if (!$this->output->isDecorated()) { $this->overwrite = false; $this->setRedrawFrequency($max / 10); } $this->startTime = time(); } public static function setPlaceholderFormatterDefinition($name, callable $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition($name) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; } public static function setFormatDefinition($name, $format) { if (!self::$formats) { self::$formats = self::initFormats(); } self::$formats[$name] = $format; } public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return isset(self::$formats[$name]) ? self::$formats[$name] : null; } public function setMessage($message, $name = 'message') { $this->messages[$name] = $message; } public function getMessage($name = 'message') { return $this->messages[$name]; } public function getStartTime() { return $this->startTime; } public function getMaxSteps() { return $this->max; } public function getProgress() { return $this->step; } private function getStepWidth() { return $this->stepWidth; } public function getProgressPercent() { return $this->percent; } public function setBarWidth($size) { $this->barWidth = max(1, (int) $size); } public function getBarWidth() { return $this->barWidth; } public function setBarCharacter($char) { $this->barChar = $char; } public function getBarCharacter() { if (null === $this->barChar) { return $this->max ? '=' : $this->emptyBarChar; } return $this->barChar; } public function setEmptyBarCharacter($char) { $this->emptyBarChar = $char; } public function getEmptyBarCharacter() { return $this->emptyBarChar; } public function setProgressCharacter($char) { $this->progressChar = $char; } public function getProgressCharacter() { return $this->progressChar; } public function setFormat($format) { $this->format = null; $this->internalFormat = $format; } public function setRedrawFrequency($freq) { $this->redrawFreq = max((int) $freq, 1); } public function start($max = null) { $this->startTime = time(); $this->step = 0; $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); } $this->display(); } public function advance($step = 1) { $this->setProgress($this->step + $step); } public function setOverwrite($overwrite) { $this->overwrite = (bool) $overwrite; } public function setProgress($step) { $step = (int) $step; if ($this->max && $step > $this->max) { $this->max = $step; } elseif ($step < 0) { $step = 0; } $prevPeriod = (int) ($this->step / $this->redrawFreq); $currPeriod = (int) ($step / $this->redrawFreq); $this->step = $step; $this->percent = $this->max ? (float) $this->step / $this->max : 0; if ($prevPeriod !== $currPeriod || $this->max === $step) { $this->display(); } } public function finish() { if (!$this->max) { $this->max = $this->step; } if ($this->step === $this->max && !$this->overwrite) { return; } $this->setProgress($this->max); } public function display() { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite($this->buildLine()); } public function clear() { if (!$this->overwrite) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite(''); } private function setRealFormat($format) { if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { $this->format = self::getFormatDefinition($format.'_nomax'); } elseif (null !== self::getFormatDefinition($format)) { $this->format = self::getFormatDefinition($format); } else { $this->format = $format; } $this->formatLineCount = substr_count($this->format, "\n"); } private function setMaxSteps($max) { $this->max = max(0, (int) $max); $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; } private function overwrite($message) { if ($this->overwrite) { if (!$this->firstRun) { $this->output->write("\x0D"); $this->output->write("\x1B[2K"); if ($this->formatLineCount > 0) { $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount)); } } } elseif ($this->step > 0) { $this->output->writeln(''); } $this->firstRun = false; $this->output->write($message); } private function determineBestFormat() { switch ($this->output->getVerbosity()) { case OutputInterface::VERBOSITY_VERBOSE: return $this->max ? 'verbose' : 'verbose_nomax'; case OutputInterface::VERBOSITY_VERY_VERBOSE: return $this->max ? 'very_verbose' : 'very_verbose_nomax'; case OutputInterface::VERBOSITY_DEBUG: return $this->max ? 'debug' : 'debug_nomax'; default: return $this->max ? 'normal' : 'normal_nomax'; } } private static function initPlaceholderFormatters() { return array( 'bar' => function (ProgressBar $bar, OutputInterface $output) { $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); $display = str_repeat($bar->getBarCharacter(), $completeBars); if ($completeBars < $bar->getBarWidth()) { $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); } return $display; }, 'elapsed' => function (ProgressBar $bar) { return Helper::formatTime(time() - $bar->getStartTime()); }, 'remaining' => function (ProgressBar $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } if (!$bar->getProgress()) { $remaining = 0; } else { $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); } return Helper::formatTime($remaining); }, 'estimated' => function (ProgressBar $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } if (!$bar->getProgress()) { $estimated = 0; } else { $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); } return Helper::formatTime($estimated); }, 'memory' => function (ProgressBar $bar) { return Helper::formatMemory(memory_get_usage(true)); }, 'current' => function (ProgressBar $bar) { return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); }, 'max' => function (ProgressBar $bar) { return $bar->getMaxSteps(); }, 'percent' => function (ProgressBar $bar) { return floor($bar->getProgressPercent() * 100); }, ); } private static function initFormats() { return array( 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', 'normal_nomax' => ' %current% [%bar%]', 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', ); } private function buildLine() { $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; $callback = function ($matches) { if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { $text = call_user_func($formatter, $this, $this->output); } elseif (isset($this->messages[$matches[1]])) { $text = $this->messages[$matches[1]]; } else { return $matches[0]; } if (isset($matches[2])) { $text = sprintf('%'.$matches[2], $text); } return $text; }; $line = preg_replace_callback($regex, $callback, $this->format); $linesLength = array_map(function ($subLine) { return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r")); }, explode("\n", $line)); $linesWidth = max($linesLength); $terminalWidth = $this->terminal->getWidth(); if ($linesWidth <= $terminalWidth) { return $line; } $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); return preg_replace_callback($regex, $callback, $this->format); } } %s'; private $cellRowFormat = '%s'; private $cellRowContentFormat = ' %s '; private $borderFormat = '%s'; private $padType = STR_PAD_RIGHT; public function setPaddingChar($paddingChar) { if (!$paddingChar) { throw new LogicException('The padding char must not be empty'); } $this->paddingChar = $paddingChar; return $this; } public function getPaddingChar() { return $this->paddingChar; } public function setHorizontalBorderChar($horizontalBorderChar) { $this->horizontalBorderChar = $horizontalBorderChar; return $this; } public function getHorizontalBorderChar() { return $this->horizontalBorderChar; } public function setVerticalBorderChar($verticalBorderChar) { $this->verticalBorderChar = $verticalBorderChar; return $this; } public function getVerticalBorderChar() { return $this->verticalBorderChar; } public function setCrossingChar($crossingChar) { $this->crossingChar = $crossingChar; return $this; } public function getCrossingChar() { return $this->crossingChar; } public function setCellHeaderFormat($cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } public function getCellHeaderFormat() { return $this->cellHeaderFormat; } public function setCellRowFormat($cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } public function getCellRowFormat() { return $this->cellRowFormat; } public function setCellRowContentFormat($cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } public function getCellRowContentFormat() { return $this->cellRowContentFormat; } public function setBorderFormat($borderFormat) { $this->borderFormat = $borderFormat; return $this; } public function getBorderFormat() { return $this->borderFormat; } public function setPadType($padType) { if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) { throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); } $this->padType = $padType; return $this; } public function getPadType() { return $this->padType; } } getValidator(); $question->setValidator(function ($value) use ($validator) { if (null !== $validator) { $value = $validator($value); } else { if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) { throw new LogicException('A value is required.'); } } return $value; }); return parent::ask($input, $output, $question); } protected function writePrompt(OutputInterface $output, Question $question) { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); $default = $question->getDefault(); switch (true) { case null === $default: $text = sprintf(' %s:', $text); break; case $question instanceof ConfirmationQuestion: $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); break; case $question instanceof ChoiceQuestion && $question->isMultiselect(): $choices = $question->getChoices(); $default = explode(',', $default); foreach ($default as $key => $value) { $default[$key] = $choices[trim($value)]; } $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); break; case $question instanceof ChoiceQuestion: $choices = $question->getChoices(); $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default])); break; default: $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); } $output->writeln($text); if ($question instanceof ChoiceQuestion) { $width = max(array_map('strlen', array_keys($question->getChoices()))); foreach ($question->getChoices() as $key => $value) { $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); } } $output->write(' > '); } protected function writeError(OutputInterface $output, \Exception $error) { if ($output instanceof SymfonyStyle) { $output->newLine(); $output->error($error->getMessage()); return; } parent::writeError($output, $error); } } getErrorOutput(); } if (!$input->isInteractive()) { return $question->getDefault(); } if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { $this->inputStream = $stream; } if (!$question->getValidator()) { return $this->doAsk($output, $question); } $interviewer = function () use ($output, $question) { return $this->doAsk($output, $question); }; return $this->validateAttempts($interviewer, $output, $question); } public function setInputStream($stream) { @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); if (!is_resource($stream)) { throw new InvalidArgumentException('Input stream must be a valid resource.'); } $this->inputStream = $stream; } public function getInputStream() { if (0 === func_num_args() || func_get_arg(0)) { @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); } return $this->inputStream; } public function getName() { return 'question'; } private function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterValues(); if (null === $autocomplete || !$this->hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { try { $ret = trim($this->getHiddenResponse($output, $inputStream)); } catch (\RuntimeException $e) { if (!$question->isHiddenFallback()) { throw $e; } } } if (false === $ret) { $ret = fgets($inputStream, 4096); if (false === $ret) { throw new RuntimeException('Aborted'); } $ret = trim($ret); } } else { $ret = trim($this->autocomplete($output, $question, $inputStream)); } $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); if ($normalizer = $question->getNormalizer()) { return $normalizer($ret); } return $ret; } protected function writePrompt(OutputInterface $output, Question $question) { $message = $question->getQuestion(); if ($question instanceof ChoiceQuestion) { $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices()))); $messages = (array) $question->getQuestion(); foreach ($question->getChoices() as $key => $value) { $width = $maxWidth - $this->strlen($key); $messages[] = ' ['.$key.str_repeat(' ', $width).'] '.$value; } $output->writeln($messages); $message = $question->getPrompt(); } $output->write($message); } protected function writeError(OutputInterface $output, \Exception $error) { if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); } else { $message = ''.$error->getMessage().''; } $output->writeln($message); } private function autocomplete(OutputInterface $output, Question $question, $inputStream) { $autocomplete = $question->getAutocompleterValues(); $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); $sttyMode = shell_exec('stty -g'); shell_exec('stty -icanon -echo'); $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); while (!feof($inputStream)) { $c = fread($inputStream, 1); if ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; $output->write("\033[1D"); } if ($i === 0) { $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); } else { $numMatches = 0; } $ret = substr($ret, 0, $i); } elseif ("\033" === $c) { $c .= fread($inputStream, 2); if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { if ('A' === $c[2] && -1 === $ofs) { $ofs = 0; } if (0 === $numMatches) { continue; } $ofs += ('A' === $c[2]) ? -1 : 1; $ofs = ($numMatches + $ofs) % $numMatches; } } elseif (ord($c) < 32) { if ("\t" === $c || "\n" === $c) { if ($numMatches > 0 && -1 !== $ofs) { $ret = $matches[$ofs]; $output->write(substr($ret, $i)); $i = strlen($ret); } if ("\n" === $c) { $output->write($c); break; } $numMatches = 0; } continue; } else { $output->write($c); $ret .= $c; ++$i; $numMatches = 0; $ofs = 0; foreach ($autocomplete as $value) { if (0 === strpos($value, $ret) && $i !== strlen($value)) { $matches[$numMatches++] = $value; } } } $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { $output->write("\0337"); $output->write(''.substr($matches[$ofs], $i).''); $output->write("\0338"); } } shell_exec(sprintf('stty %s', $sttyMode)); return $ret; } private function getHiddenResponse(OutputInterface $output, $inputStream) { if ('\\' === DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; } $value = rtrim(shell_exec($exe)); $output->writeln(''); if (isset($tmpExe)) { unlink($tmpExe); } return $value; } if ($this->hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); $value = fgets($inputStream, 4096); shell_exec(sprintf('stty %s', $sttyMode)); if (false === $value) { throw new RuntimeException('Aborted'); } $value = trim($value); $output->writeln(''); return $value; } if (false !== $shell = $this->getShell()) { $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); $value = rtrim(shell_exec($command)); $output->writeln(''); return $value; } throw new RuntimeException('Unable to hide the response.'); } private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) { $error = null; $attempts = $question->getMaxAttempts(); while (null === $attempts || $attempts--) { if (null !== $error) { $this->writeError($output, $error); } try { return call_user_func($question->getValidator(), $interviewer()); } catch (RuntimeException $e) { throw $e; } catch (\Exception $error) { } } throw $error; } private function getShell() { if (null !== self::$shell) { return self::$shell; } self::$shell = false; if (file_exists('/usr/bin/env')) { $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { self::$shell = $sh; break; } } } return self::$shell; } private function hasSttyAvailable() { if (null !== self::$stty) { return self::$stty; } exec('stty 2>&1', $output, $exitcode); return self::$stty = $exitcode === 0; } } started[$id] = array('border' => ++$this->count % count($this->colors)); return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') { $message = ''; if ($error) { if (isset($this->started[$id]['out'])) { $message .= "\n"; unset($this->started[$id]['out']); } if (!isset($this->started[$id]['err'])) { $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); } else { if (isset($this->started[$id]['err'])) { $message .= "\n"; unset($this->started[$id]['err']); } if (!isset($this->started[$id]['out'])) { $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); $this->started[$id]['out'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); } return $message; } public function stop($id, $message, $successful, $prefix = 'RES') { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; if ($successful) { return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); unset($this->started[$id]['out'], $this->started[$id]['err']); return $message; } private function getBorder($id) { return sprintf(' ', $this->colors[$this->started[$id]['border']]); } public function getName() { return 'debug_formatter'; } } register('txt', new TextDescriptor()) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) ; } public function describe(OutputInterface $output, $object, array $options = array()) { $options = array_merge(array( 'raw_text' => false, 'format' => 'txt', ), $options); if (!isset($this->descriptors[$options['format']])) { throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); } $descriptor = $this->descriptors[$options['format']]; $descriptor->describe($output, $object, $options); } public function register($format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } public function getName() { return 'descriptor'; } } output = $output; if (null === $format) { $format = $this->determineBestFormat(); } if (null === $indicatorValues) { $indicatorValues = array('-', '\\', '|', '/'); } $indicatorValues = array_values($indicatorValues); if (2 > count($indicatorValues)) { throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); } $this->format = self::getFormatDefinition($format); $this->indicatorChangeInterval = $indicatorChangeInterval; $this->indicatorValues = $indicatorValues; $this->startTime = time(); } public function setMessage($message) { $this->message = $message; $this->display(); } public function start($message) { if ($this->started) { throw new LogicException('Progress indicator already started.'); } $this->message = $message; $this->started = true; $this->startTime = time(); $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; $this->indicatorCurrent = 0; $this->display(); } public function advance() { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } if (!$this->output->isDecorated()) { return; } $currentTime = $this->getCurrentTimeInMilliseconds(); if ($currentTime < $this->indicatorUpdateTime) { return; } $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; ++$this->indicatorCurrent; $this->display(); } public function finish($message) { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } $this->message = $message; $this->display(); $this->output->writeln(''); $this->started = false; } public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return isset(self::$formats[$name]) ? self::$formats[$name] : null; } public static function setPlaceholderFormatterDefinition($name, $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition($name) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; } private function display() { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } $self = $this; $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) { if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { return call_user_func($formatter, $self); } return $matches[0]; }, $this->format)); } private function determineBestFormat() { switch ($this->output->getVerbosity()) { case OutputInterface::VERBOSITY_VERBOSE: return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; case OutputInterface::VERBOSITY_VERY_VERBOSE: case OutputInterface::VERBOSITY_DEBUG: return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; default: return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; } } private function overwrite($message) { if ($this->output->isDecorated()) { $this->output->write("\x0D\x1B[2K"); $this->output->write($message); } else { $this->output->writeln($message); } } private function getCurrentTimeInMilliseconds() { return round(microtime(true) * 1000); } private static function initPlaceholderFormatters() { return array( 'indicator' => function (ProgressIndicator $indicator) { return $indicator->indicatorValues[$indicator->indicatorCurrent % count($indicator->indicatorValues)]; }, 'message' => function (ProgressIndicator $indicator) { return $indicator->message; }, 'elapsed' => function (ProgressIndicator $indicator) { return Helper::formatTime(time() - $indicator->startTime); }, 'memory' => function () { return Helper::formatMemory(memory_get_usage(true)); }, ); } private static function initFormats() { return array( 'normal' => ' %indicator% %message%', 'normal_no_ansi' => ' %message%', 'verbose' => ' %indicator% %message% (%elapsed:6s%)', 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', ); } } helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public static function strlen($string) { if (false === $encoding = mb_detect_encoding($string, null, true)) { return strlen($string); } return mb_strwidth($string, $encoding); } public static function formatTime($secs) { static $timeFormats = array( array(0, '< 1 sec'), array(1, '1 sec'), array(2, 'secs', 1), array(60, '1 min'), array(120, 'mins', 60), array(3600, '1 hr'), array(7200, 'hrs', 3600), array(86400, '1 day'), array(172800, 'days', 86400), ); foreach ($timeFormats as $index => $format) { if ($secs >= $format[0]) { if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) || $index == count($timeFormats) - 1 ) { if (2 == count($format)) { return $format[1]; } return floor($secs / $format[2]).' '.$format[1]; } } } } public static function formatMemory($memory) { if ($memory >= 1024 * 1024 * 1024) { return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); } if ($memory >= 1024 * 1024) { return sprintf('%.1f MiB', $memory / 1024 / 1024); } if ($memory >= 1024) { return sprintf('%d KiB', $memory / 1024); } return sprintf('%d B', $memory); } public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) { return self::strlen(self::removeDecoration($formatter, $string)); } public static function removeDecoration(OutputFormatterInterface $formatter, $string) { $isDecorated = $formatter->isDecorated(); $formatter->setDecorated(false); $string = $formatter->format($string); $string = preg_replace("/\033\[[^m]*m/", '', $string); $formatter->setDecorated($isDecorated); return $string; } } [%s] %s', $style, $section, $style, $message); } public function formatBlock($messages, $style, $large = false) { if (!is_array($messages)) { $messages = array($messages); } $len = 0; $lines = array(); foreach ($messages as $message) { $message = OutputFormatter::escape($message); $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); $len = max($this->strlen($message) + ($large ? 4 : 2), $len); } $messages = $large ? array(str_repeat(' ', $len)) : array(); for ($i = 0; isset($lines[$i]); ++$i) { $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); } if ($large) { $messages[] = str_repeat(' ', $len); } for ($i = 0; isset($messages[$i]); ++$i) { $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); } return implode("\n", $messages); } public function truncate($message, $length, $suffix = '...') { $computedLength = $length - $this->strlen($suffix); if ($computedLength > $this->strlen($message)) { return $message; } if (false === $encoding = mb_detect_encoding($message, null, true)) { return substr($message, 0, $length).$suffix; } return mb_substr($message, 0, $length, $encoding).$suffix; } public function getName() { return 'formatter'; } } getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); if (is_array($cmd)) { $process = ProcessBuilder::create($cmd)->getProcess(); } elseif ($cmd instanceof Process) { $process = $cmd; } else { $process = new Process($cmd); } if ($verbosity <= $output->getVerbosity()) { $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); } if ($output->isDebug()) { $callback = $this->wrapCallback($output, $process, $callback); } $process->run($callback); if ($verbosity <= $output->getVerbosity()) { $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); } if (!$process->isSuccessful() && null !== $error) { $output->writeln(sprintf('%s', $this->escapeString($error))); } return $process; } public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null) { $process = $this->run($output, $cmd, $error, $callback); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process; } public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); return function ($type, $buffer) use ($output, $process, $callback, $formatter) { $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); if (null !== $callback) { call_user_func($callback, $type, $buffer); } }; } private function escapeString($str) { return str_replace('<', '\\<', $str); } public function getName() { return 'process'; } } appendChild($definitionXML = $dom->createElement('definition')); $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); foreach ($definition->getArguments() as $argument) { $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); } $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); } return $dom; } public function getCommandDocument(Command $command) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $command->getSynopsis(); $command->mergeApplicationDefinition(false); $commandXML->setAttribute('id', $command->getName()); $commandXML->setAttribute('name', $command->getName()); $commandXML->appendChild($usagesXML = $dom->createElement('usages')); foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); $commandXML->appendChild($helpXML = $dom->createElement('help')); $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); return $dom; } public function getApplicationDocument(Application $application, $namespace = null) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); if ($application->getName() !== 'UNKNOWN') { $rootXml->setAttribute('name', $application->getName()); if ($application->getVersion() !== 'UNKNOWN') { $rootXml->setAttribute('version', $application->getVersion()); } } $rootXml->appendChild($commandsXML = $dom->createElement('commands')); $description = new ApplicationDescription($application, $namespace); if ($namespace) { $commandsXML->setAttribute('namespace', $namespace); } foreach ($description->getCommands() as $command) { $this->appendDocument($commandsXML, $this->getCommandDocument($command)); } if (!$namespace) { $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); foreach ($description->getNamespaces() as $namespaceDescription) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); foreach ($namespaceDescription['commands'] as $name) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($name)); } } } return $dom; } protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->writeDocument($this->getInputArgumentDocument($argument)); } protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeDocument($this->getInputOptionDocument($option)); } protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } protected function describeCommand(Command $command, array $options = array()) { $this->writeDocument($this->getCommandDocument($command)); } protected function describeApplication(Application $application, array $options = array()) { $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); } private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } private function getInputArgumentDocument(InputArgument $argument) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('argument')); $objectXML->setAttribute('name', $argument->getName()); $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } return $dom; } private function getInputOptionDocument(InputOption $option) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--'.$option->getName()); $pos = strpos($option->getShortcut(), '|'); if (false !== $pos) { $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut()))); } else { $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); } $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); if ($option->acceptValue()) { $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); if (!empty($defaults)) { foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } } return $dom; } } getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); } else { $default = ''; } $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName()); $spacingWidth = $totalWidth - strlen($argument->getName()); $this->writeText(sprintf(' %s %s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), $default ), $options); } protected function describeInputOption(InputOption $option, array $options = array()) { if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); } else { $default = ''; } $value = ''; if ($option->acceptValue()) { $value = '='.strtoupper($option->getName()); if ($option->isValueOptional()) { $value = '['.$value.']'; } } $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option)); $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value) ); $spacingWidth = $totalWidth - Helper::strlen($synopsis); $this->writeText(sprintf(' %s %s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : '' ), $options); } protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); foreach ($definition->getArguments() as $argument) { $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); } if ($definition->getArguments()) { $this->writeText('Arguments:', $options); $this->writeText("\n"); foreach ($definition->getArguments() as $argument) { $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth))); $this->writeText("\n"); } } if ($definition->getArguments() && $definition->getOptions()) { $this->writeText("\n"); } if ($definition->getOptions()) { $laterOptions = array(); $this->writeText('Options:', $options); foreach ($definition->getOptions() as $option) { if (strlen($option->getShortcut()) > 1) { $laterOptions[] = $option; continue; } $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); } foreach ($laterOptions as $option) { $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); } } } protected function describeCommand(Command $command, array $options = array()) { $command->getSynopsis(true); $command->getSynopsis(false); $command->mergeApplicationDefinition(false); $this->writeText('Usage:', $options); foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { $this->writeText("\n"); $this->writeText(' '.$usage, $options); } $this->writeText("\n"); $definition = $command->getNativeDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); $this->writeText("\n"); } if ($help = $command->getProcessedHelp()) { $this->writeText("\n"); $this->writeText('Help:', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); if (isset($options['raw_text']) && $options['raw_text']) { $width = $this->getColumnWidth($description->getCommands()); foreach ($description->getCommands() as $command) { $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); $this->writeText("\n"); } } else { if ('' != $help = $application->getHelp()) { $this->writeText("$help\n\n", $options); } $this->writeText("Usage:\n", $options); $this->writeText(" command [options] [arguments]\n\n", $options); $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); $this->writeText("\n"); $this->writeText("\n"); $width = $this->getColumnWidth($description->getCommands()); if ($describedNamespace) { $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); } else { $this->writeText('Available commands:', $options); } $commands = $description->getCommands(); foreach ($description->getNamespaces() as $namespace) { if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->writeText("\n"); $this->writeText(' '.$namespace['id'].'', $options); } foreach ($namespace['commands'] as $name) { if (isset($commands[$name])) { $this->writeText("\n"); $spacingWidth = $width - Helper::strlen($name); $command = $commands[$name]; $commandAliases = $this->getCommandAliasesText($command); $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); } } } $this->writeText("\n"); } } private function writeText($content, array $options = array()) { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true ); } private function getCommandAliasesText($command) { $text = ''; $aliases = $command->getAliases(); if ($aliases) { $text = '['.implode('|', $aliases).'] '; } return $text; } private function formatDefaultValue($default) { if (is_string($default)) { $default = OutputFormatter::escape($default); } elseif (is_array($default)) { foreach ($default as $key => $value) { if (is_string($value)) { $default[$key] = OutputFormatter::escape($value); } } } return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } private function getColumnWidth(array $commands) { $widths = array(); foreach ($commands as $command) { $widths[] = Helper::strlen($command->getName()); foreach ($command->getAliases() as $alias) { $widths[] = Helper::strlen($alias); } } return max($widths) + 2; } private function calculateTotalWidthForOptions($options) { $totalWidth = 0; foreach ($options as $option) { $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); if ($option->acceptValue()) { $valueLength = 1 + Helper::strlen($option->getName()); $valueLength += $option->isValueOptional() ? 2 : 0; $nameLength += $valueLength; } $totalWidth = max($totalWidth, $nameLength); } return $totalWidth; } } output = $output; switch (true) { case $object instanceof InputArgument: $this->describeInputArgument($object, $options); break; case $object instanceof InputOption: $this->describeInputOption($object, $options); break; case $object instanceof InputDefinition: $this->describeInputDefinition($object, $options); break; case $object instanceof Command: $this->describeCommand($object, $options); break; case $object instanceof Application: $this->describeApplication($object, $options); break; default: throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); } } protected function write($content, $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); abstract protected function describeInputOption(InputOption $option, array $options = array()); abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); abstract protected function describeCommand(Command $command, array $options = array()); abstract protected function describeApplication(Application $application, array $options = array()); } write( '**'.$argument->getName().':**'."\n\n" .'* Name: '.($argument->getName() ?: '')."\n" .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $argument->getDescription() ?: '')."\n" .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' ); } protected function describeInputOption(InputOption $option, array $options = array()) { $this->write( '**'.$option->getName().':**'."\n\n" .'* Name: `--'.$option->getName().'`'."\n" .'* Shortcut: '.($option->getShortcut() ? '`-'.implode('|-', explode('|', $option->getShortcut())).'`' : '')."\n" .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $option->getDescription() ?: '')."\n" .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' ); } protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { if ($showArguments = count($definition->getArguments()) > 0) { $this->write('### Arguments:'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); $this->write($this->describeInputArgument($argument)); } } if (count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options:'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); $this->write($this->describeInputOption($option)); } } } protected function describeCommand(Command $command, array $options = array()) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); $this->write( $command->getName()."\n" .str_repeat('-', Helper::strlen($command->getName()))."\n\n" .'* Description: '.($command->getDescription() ?: '')."\n" .'* Usage:'."\n\n" .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) { return $carry.' * `'.$usage.'`'."\n"; }) ); if ($help = $command->getProcessedHelp()) { $this->write("\n"); $this->write($help); } if ($command->getNativeDefinition()) { $this->write("\n\n"); $this->describeInputDefinition($command->getNativeDefinition()); } } protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); $this->write($application->getName()."\n".str_repeat('=', Helper::strlen($application->getName()))); foreach ($description->getNamespaces() as $namespace) { if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->write("\n\n"); $this->write('**'.$namespace['id'].':**'); } $this->write("\n\n"); $this->write(implode("\n", array_map(function ($commandName) { return '* '.$commandName; }, $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); $this->write($this->describeCommand($command)); } } } application = $application; $this->namespace = $namespace; } public function getNamespaces() { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } public function getCommands() { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } public function getCommand($name) { if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name)); } return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; } private function inspectApplication() { $this->commands = array(); $this->namespaces = array(); $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); foreach ($this->sortCommands($all) as $namespace => $commands) { $names = array(); foreach ($commands as $name => $command) { if (!$command->getName() || $command->isHidden()) { continue; } if ($command->getName() === $name) { $this->commands[$name] = $command; } else { $this->aliases[$name] = $command; } $names[] = $name; } $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); } } private function sortCommands(array $commands) { $namespacedCommands = array(); $globalCommands = array(); foreach ($commands as $name => $command) { $key = $this->application->extractNamespace($name, 1); if (!$key) { $globalCommands['_global'][$name] = $command; } else { $namespacedCommands[$key][$name] = $command; } } ksort($namespacedCommands); $namespacedCommands = array_merge($globalCommands, $namespacedCommands); foreach ($namespacedCommands as &$commandsSet) { ksort($commandsSet); } unset($commandsSet); return $namespacedCommands; } } writeData($this->getInputArgumentData($argument), $options); } protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeData($this->getInputOptionData($option), $options); } protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeData($this->getInputDefinitionData($definition), $options); } protected function describeCommand(Command $command, array $options = array()) { $this->writeData($this->getCommandData($command), $options); } protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); $commands = array(); foreach ($description->getCommands() as $command) { $commands[] = $this->getCommandData($command); } $data = $describedNamespace ? array('commands' => $commands, 'namespace' => $describedNamespace) : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces())); $this->writeData($data, $options); } private function writeData(array $data, array $options) { $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); } private function getInputArgumentData(InputArgument $argument) { return array( 'name' => $argument->getName(), 'is_required' => $argument->isRequired(), 'is_array' => $argument->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), 'default' => $argument->getDefault(), ); } private function getInputOptionData(InputOption $option) { return array( 'name' => '--'.$option->getName(), 'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), 'default' => $option->getDefault(), ); } private function getInputDefinitionData(InputDefinition $definition) { $inputArguments = array(); foreach ($definition->getArguments() as $name => $argument) { $inputArguments[$name] = $this->getInputArgumentData($argument); } $inputOptions = array(); foreach ($definition->getOptions() as $name => $option) { $inputOptions[$name] = $this->getInputOptionData($option); } return array('arguments' => $inputArguments, 'options' => $inputOptions); } private function getCommandData(Command $command) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); return array( 'name' => $command->getName(), 'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()), 'description' => $command->getDescription(), 'help' => $command->getProcessedHelp(), 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), ); } } name = $name; $this->version = $version; $this->terminal = new Terminal(); $this->defaultCommand = 'list'; $this->helperSet = $this->getDefaultHelperSet(); $this->definition = $this->getDefaultInputDefinition(); foreach ($this->getDefaultCommands() as $command) { $this->add($command); } } public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } public function run(InputInterface $input = null, OutputInterface $output = null) { putenv('LINES='.$this->terminal->getHeight()); putenv('COLUMNS='.$this->terminal->getWidth()); if (null === $input) { $input = new ArgvInput(); } if (null === $output) { $output = new ConsoleOutput(); } $this->configureIO($input, $output); try { $e = null; $exitCode = $this->doRun($input, $output); } catch (\Exception $x) { $e = $x; } catch (\Throwable $x) { $e = new FatalThrowableError($x); } if (null !== $e) { if (!$this->catchExceptions) { throw $x; } if ($output instanceof ConsoleOutputInterface) { $this->renderException($e, $output->getErrorOutput()); } else { $this->renderException($e, $output); } $exitCode = $e->getCode(); if (is_numeric($exitCode)) { $exitCode = (int) $exitCode; if (0 === $exitCode) { $exitCode = 1; } } else { $exitCode = 1; } } if ($this->autoExit) { if ($exitCode > 255) { $exitCode = 255; } exit($exitCode); } return $exitCode; } public function doRun(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--version', '-V'), true)) { $output->writeln($this->getLongVersion()); return 0; } $name = $this->getCommandName($input); if (true === $input->hasParameterOption(array('--help', '-h'), true)) { if (!$name) { $name = 'help'; $input = new ArrayInput(array('command_name' => $this->defaultCommand)); } else { $this->wantHelps = true; } } if (!$name) { $name = $this->defaultCommand; $input = new ArrayInput(array('command' => $this->defaultCommand)); } $this->runningCommand = null; $command = $this->find($name); $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } public function getDefinition() { if ($this->singleCommand) { $inputDefinition = $this->definition; $inputDefinition->setArguments(); return $inputDefinition; } return $this->definition; } public function getHelp() { return $this->getLongVersion(); } public function areExceptionsCaught() { return $this->catchExceptions; } public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; } public function isAutoExitEnabled() { return $this->autoExit; } public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getVersion() { return $this->version; } public function setVersion($version) { $this->version = $version; } public function getLongVersion() { if ('UNKNOWN' !== $this->getName()) { if ('UNKNOWN' !== $this->getVersion()) { return sprintf('%s %s', $this->getName(), $this->getVersion()); } return $this->getName(); } return 'Console Tool'; } public function register($name) { return $this->add(new Command($name)); } public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } public function add(Command $command) { $command->setApplication($this); if (!$command->isEnabled()) { $command->setApplication(null); return; } if (null === $command->getDefinition()) { throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); } $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->commands[$alias] = $command; } return $command; } public function get($name) { if (!isset($this->commands[$name])) { throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); } $command = $this->commands[$name]; if ($this->wantHelps) { $this->wantHelps = false; $helpCommand = $this->get('help'); $helpCommand->setCommand($command); return $helpCommand; } return $command; } public function has($name) { return isset($this->commands[$name]); } public function getNamespaces() { $namespaces = array(); foreach ($this->all() as $command) { $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); foreach ($command->getAliases() as $alias) { $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); } } return array_values(array_unique(array_filter($namespaces))); } public function findNamespace($namespace) { $allNamespaces = $this->getNamespaces(); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); if (empty($namespaces)) { $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { if (1 == count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new CommandNotFoundException($message, $alternatives); } $exact = in_array($namespace, $namespaces, true); if (count($namespaces) > 1 && !$exact) { throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); } return $exact ? $namespace : reset($namespaces); } public function find($name) { $allCommands = array_keys($this->commands); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); $commands = preg_grep('{^'.$expr.'}', $allCommands); if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { $this->findNamespace(substr($name, 0, $pos)); } $message = sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternatives($name, $allCommands)) { if (1 == count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new CommandNotFoundException($message, $alternatives); } if (count($commands) > 1) { $commandList = $this->commands; $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { $commandName = $commandList[$nameOrAlias]->getName(); return $commandName === $nameOrAlias || !in_array($commandName, $commands); }); } $exact = in_array($name, $commands, true); if (count($commands) > 1 && !$exact) { $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands)); } return $this->get($exact ? $name : reset($commands)); } public function all($namespace = null) { if (null === $namespace) { return $this->commands; } $commands = array(); foreach ($this->commands as $name => $command) { if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { $commands[$name] = $command; } } return $commands; } public static function getAbbreviations($names) { $abbrevs = array(); foreach ($names as $name) { for ($len = strlen($name); $len > 0; --$len) { $abbrev = substr($name, 0, $len); $abbrevs[$abbrev][] = $name; } } return $abbrevs; } public function renderException(\Exception $e, OutputInterface $output) { $output->writeln('', OutputInterface::VERBOSITY_QUIET); do { $title = sprintf( ' [%s%s] ', get_class($e), $output->isVerbose() && 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '' ); $len = $this->stringWidth($title); $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX; if (defined('HHVM_VERSION') && $width > 1 << 31) { $width = 1 << 31; } $lines = array(); foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { foreach ($this->splitStringByWidth($line, $width - 4) as $line) { $lineLength = $this->stringWidth($line) + 4; $lines[] = array($line, $lineLength); $len = max($lineLength, $len); } } $messages = array(); $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); foreach ($lines as $line) { $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); } $messages[] = $emptyLine; $messages[] = ''; $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); $trace = $e->getTrace(); array_unshift($trace, array( 'function' => '', 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', 'args' => array(), )); for ($i = 0, $count = count($trace); $i < $count; ++$i) { $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; $function = $trace[$i]['function']; $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET); } $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } while ($e = $e->getPrevious()); if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET); $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } protected function getTerminalWidth() { @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); return $this->terminal->getWidth(); } protected function getTerminalHeight() { @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); return $this->terminal->getHeight(); } public function getTerminalDimensions() { @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); return array($this->terminal->getWidth(), $this->terminal->getHeight()); } public function setTerminalDimensions($width, $height) { @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED); putenv('COLUMNS='.$width); putenv('LINES='.$height); return $this; } protected function configureIO(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--ansi'), true)) { $output->setDecorated(true); } elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) { $output->setDecorated(false); } if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) { $input->setInteractive(false); } elseif (function_exists('posix_isatty')) { $inputStream = null; if ($input instanceof StreamableInputInterface) { $inputStream = $input->getStream(); } if (!$inputStream && $this->getHelperSet()->has('question')) { $inputStream = $this->getHelperSet()->get('question')->getInputStream(false); } if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { $input->setInteractive(false); } } if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); $input->setInteractive(false); } else { if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === 3) { $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === 2) { $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); } } } protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { $helper->setInput($input); } } if (null === $this->dispatcher) { return $command->run($input, $output); } try { $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); } catch (ExceptionInterface $e) { } $event = new ConsoleCommandEvent($command, $input, $output); $e = null; try { $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); if ($event->commandShouldRun()) { $exitCode = $command->run($input, $output); } else { $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; } } catch (\Exception $e) { } catch (\Throwable $e) { } if (null !== $e) { $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); if ($x !== $event->getException()) { $e = $event->getException(); } $exitCode = $e->getCode(); } $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); if (null !== $e) { throw $e; } return $event->getExitCode(); } protected function getCommandName(InputInterface $input) { return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); } protected function getDefaultInputDefinition() { return new InputDefinition(array( new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), )); } protected function getDefaultCommands() { return array(new HelpCommand(), new ListCommand()); } protected function getDefaultHelperSet() { return new HelperSet(array( new FormatterHelper(), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper(), )); } private function getAbbreviationSuggestions($abbrevs) { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } public function extractNamespace($name, $limit = null) { $parts = explode(':', $name); array_pop($parts); return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); } private function findAlternatives($name, $collection) { $threshold = 1e3; $alternatives = array(); $collectionParts = array(); foreach ($collection as $item) { $collectionParts[$item] = explode(':', $item); } foreach (explode(':', $name) as $i => $subname) { foreach ($collectionParts as $collectionName => $parts) { $exists = isset($alternatives[$collectionName]); if (!isset($parts[$i]) && $exists) { $alternatives[$collectionName] += $threshold; continue; } elseif (!isset($parts[$i])) { continue; } $lev = levenshtein($subname, $parts[$i]); if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; } elseif ($exists) { $alternatives[$collectionName] += $threshold; } } } foreach ($collection as $item) { $lev = levenshtein($name, $item); if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; } } $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); asort($alternatives); return array_keys($alternatives); } public function setDefaultCommand($commandName, $isSingleCommand = false) { $this->defaultCommand = $commandName; if ($isSingleCommand) { $this->find($commandName); $this->singleCommand = true; } return $this; } private function stringWidth($string) { if (false === $encoding = mb_detect_encoding($string, null, true)) { return strlen($string); } return mb_strwidth($string, $encoding); } private function splitStringByWidth($string, $width) { if (false === $encoding = mb_detect_encoding($string, null, true)) { return str_split($string, $width); } $utf8String = mb_convert_encoding($string, 'utf8', $encoding); $lines = array(); $line = ''; foreach (preg_split('//u', $utf8String) as $char) { if (mb_strwidth($line.$char, 'utf8') <= $width) { $line .= $char; continue; } $lines[] = str_pad($line, $width); $line = $char; } if ('' !== $line) { $lines[] = count($lines) ? str_pad($line, $width) : $line; } mb_convert_variables($encoding, 'utf8', $lines); return $lines; } private function extractAllNamespaces($name) { $parts = explode(':', $name, -1); $namespaces = array(); foreach ($parts as $part) { if (count($namespaces)) { $namespaces[] = end($namespaces).':'.$part; } else { $namespaces[] = $part; } } return $namespaces; } } '; private $errorMessage = 'Value "%s" is invalid'; public function __construct($question, array $choices, $default = null) { parent::__construct($question, $default); $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } public function getChoices() { return $this->choices; } public function setMultiselect($multiselect) { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } public function isMultiselect() { return $this->multiselect; } public function getPrompt() { return $this->prompt; } public function setPrompt($prompt) { $this->prompt = $prompt; return $this; } public function setErrorMessage($errorMessage) { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); return $this; } private function getDefaultValidator() { $choices = $this->choices; $errorMessage = $this->errorMessage; $multiselect = $this->multiselect; $isAssoc = $this->isAssoc($choices); return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { $selectedChoices = str_replace(' ', '', $selected); if ($multiselect) { if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $selected)); } $selectedChoices = explode(',', $selectedChoices); } else { $selectedChoices = array($selected); } $multiselectChoices = array(); foreach ($selectedChoices as $value) { $results = array(); foreach ($choices as $key => $choice) { if ($choice === $value) { $results[] = $key; } } if (count($results) > 1) { throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); } $result = array_search($value, $choices); if (!$isAssoc) { if (false !== $result) { $result = $choices[$result]; } elseif (isset($choices[$value])) { $result = $choices[$value]; } } elseif (false === $result && isset($choices[$value])) { $result = $value; } if (false === $result) { throw new InvalidArgumentException(sprintf($errorMessage, $value)); } $multiselectChoices[] = (string) $result; } if ($multiselect) { return $multiselectChoices; } return current($multiselectChoices); }; } } question = $question; $this->default = $default; } public function getQuestion() { return $this->question; } public function getDefault() { return $this->default; } public function isHidden() { return $this->hidden; } public function setHidden($hidden) { if ($this->autocompleterValues) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = (bool) $hidden; return $this; } public function isHiddenFallback() { return $this->hiddenFallback; } public function setHiddenFallback($fallback) { $this->hiddenFallback = (bool) $fallback; return $this; } public function getAutocompleterValues() { return $this->autocompleterValues; } public function setAutocompleterValues($values) { if (is_array($values)) { $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); } if (null !== $values && !is_array($values)) { if (!$values instanceof \Traversable || !$values instanceof \Countable) { throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); } } if ($this->hidden) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->autocompleterValues = $values; return $this; } public function setValidator(callable $validator = null) { $this->validator = $validator; return $this; } public function getValidator() { return $this->validator; } public function setMaxAttempts($attempts) { if (null !== $attempts && $attempts < 1) { throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); } $this->attempts = $attempts; return $this; } public function getMaxAttempts() { return $this->attempts; } public function setNormalizer(callable $normalizer) { $this->normalizer = $normalizer; return $this; } public function getNormalizer() { return $this->normalizer; } protected function isAssoc($array) { return (bool) count(array_filter(array_keys($array), 'is_string')); } } trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } private function getDefaultNormalizer() { $default = $this->getDefault(); $regex = $this->trueAnswerRegex; return function ($answer) use ($default, $regex) { if (is_bool($answer)) { return $answer; } $answerIsTrue = (bool) preg_match($regex, $answer); if (false === $default) { return $answer && $answerIsTrue; } return !$answer || $answerIsTrue; }; } } command = $command; } public function execute(array $input, array $options = array()) { if (!isset($input['command']) && (null !== $application = $this->command->getApplication()) && $application->getDefinition()->hasArgument('command') ) { $input = array_merge(array('command' => $this->command->getName()), $input); } $this->input = new ArrayInput($input); if ($this->inputs) { $this->input->setStream(self::createStream($this->inputs)); } if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false); if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } return $this->statusCode = $this->command->run($this->input, $this->output); } public function getDisplay($normalize = false) { rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } public function getStatusCode() { return $this->statusCode; } public function setInputs(array $inputs) { $this->inputs = $inputs; return $this; } private static function createStream(array $inputs) { $stream = fopen('php://memory', 'r+', false); fwrite($stream, implode(PHP_EOL, $inputs)); rewind($stream); return $stream; } } application = $application; } public function run(array $input, $options = array()) { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->captureStreamsIndependently = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; if (!$this->captureStreamsIndependently) { $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } } else { $this->output = new ConsoleOutput( isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL, isset($options['decorated']) ? $options['decorated'] : null ); $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); $errorOutput->setFormatter($this->output->getFormatter()); $errorOutput->setVerbosity($this->output->getVerbosity()); $errorOutput->setDecorated($this->output->isDecorated()); $reflectedOutput = new \ReflectionObject($this->output); $strErrProperty = $reflectedOutput->getProperty('stderr'); $strErrProperty->setAccessible(true); $strErrProperty->setValue($this->output, $errorOutput); $reflectedParent = $reflectedOutput->getParentClass(); $streamProperty = $reflectedParent->getProperty('stream'); $streamProperty->setAccessible(true); $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); } return $this->statusCode = $this->application->run($this->input, $this->output); } public function getDisplay($normalize = false) { rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } public function getErrorOutput($normalize = false) { if (!$this->captureStreamsIndependently) { throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); } rewind($this->output->getErrorOutput()->getStream()); $display = stream_get_contents($this->output->getErrorOutput()->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } public function getStatusCode() { return $this->statusCode; } } array('set' => 30, 'unset' => 39), 'red' => array('set' => 31, 'unset' => 39), 'green' => array('set' => 32, 'unset' => 39), 'yellow' => array('set' => 33, 'unset' => 39), 'blue' => array('set' => 34, 'unset' => 39), 'magenta' => array('set' => 35, 'unset' => 39), 'cyan' => array('set' => 36, 'unset' => 39), 'white' => array('set' => 37, 'unset' => 39), 'default' => array('set' => 39, 'unset' => 39), ); private static $availableBackgroundColors = array( 'black' => array('set' => 40, 'unset' => 49), 'red' => array('set' => 41, 'unset' => 49), 'green' => array('set' => 42, 'unset' => 49), 'yellow' => array('set' => 43, 'unset' => 49), 'blue' => array('set' => 44, 'unset' => 49), 'magenta' => array('set' => 45, 'unset' => 49), 'cyan' => array('set' => 46, 'unset' => 49), 'white' => array('set' => 47, 'unset' => 49), 'default' => array('set' => 49, 'unset' => 49), ); private static $availableOptions = array( 'bold' => array('set' => 1, 'unset' => 22), 'underscore' => array('set' => 4, 'unset' => 24), 'blink' => array('set' => 5, 'unset' => 25), 'reverse' => array('set' => 7, 'unset' => 27), 'conceal' => array('set' => 8, 'unset' => 28), ); private $foreground; private $background; private $options = array(); public function __construct($foreground = null, $background = null, array $options = array()) { if (null !== $foreground) { $this->setForeground($foreground); } if (null !== $background) { $this->setBackground($background); } if (count($options)) { $this->setOptions($options); } } public function setForeground($color = null) { if (null === $color) { $this->foreground = null; return; } if (!isset(static::$availableForegroundColors[$color])) { throw new InvalidArgumentException(sprintf( 'Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)) )); } $this->foreground = static::$availableForegroundColors[$color]; } public function setBackground($color = null) { if (null === $color) { $this->background = null; return; } if (!isset(static::$availableBackgroundColors[$color])) { throw new InvalidArgumentException(sprintf( 'Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)) )); } $this->background = static::$availableBackgroundColors[$color]; } public function setOption($option) { if (!isset(static::$availableOptions[$option])) { throw new InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) )); } if (!in_array(static::$availableOptions[$option], $this->options)) { $this->options[] = static::$availableOptions[$option]; } } public function unsetOption($option) { if (!isset(static::$availableOptions[$option])) { throw new InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) )); } $pos = array_search(static::$availableOptions[$option], $this->options); if (false !== $pos) { unset($this->options[$pos]); } } public function setOptions(array $options) { $this->options = array(); foreach ($options as $option) { $this->setOption($option); } } public function apply($text) { $setCodes = array(); $unsetCodes = array(); if (null !== $this->foreground) { $setCodes[] = $this->foreground['set']; $unsetCodes[] = $this->foreground['unset']; } if (null !== $this->background) { $setCodes[] = $this->background['set']; $unsetCodes[] = $this->background['unset']; } if (count($this->options)) { foreach ($this->options as $option) { $setCodes[] = $option['set']; $unsetCodes[] = $option['unset']; } } if (0 === count($setCodes)) { return $text; } return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); } } emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); $this->reset(); } public function reset() { $this->styles = array(); } public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } public function pop(OutputFormatterStyleInterface $style = null) { if (empty($this->styles)) { return $this->emptyStyle; } if (null === $style) { return array_pop($this->styles); } foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { if ($style->apply('') === $stackedStyle->apply('')) { $this->styles = array_slice($this->styles, 0, $index); return $stackedStyle; } } throw new InvalidArgumentException('Incorrectly nested style tag found.'); } public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[count($this->styles) - 1]; } public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } public function getEmptyStyle() { return $this->emptyStyle; } } decorated = (bool) $decorated; $this->setStyle('error', new OutputFormatterStyle('white', 'red')); $this->setStyle('info', new OutputFormatterStyle('green')); $this->setStyle('comment', new OutputFormatterStyle('yellow')); $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); foreach ($styles as $name => $style) { $this->setStyle($name, $style); } $this->styleStack = new OutputFormatterStyleStack(); } public function setDecorated($decorated) { $this->decorated = (bool) $decorated; } public function isDecorated() { return $this->decorated; } public function setStyle($name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } public function hasStyle($name) { return isset($this->styles[strtolower($name)]); } public function getStyle($name) { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(sprintf('Undefined style: %s', $name)); } return $this->styles[strtolower($name)]; } public function format($message) { $message = (string) $message; $offset = 0; $output = ''; $tagRegex = '[a-z][a-z0-9,_=;-]*+'; preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; if (0 != $pos && '\\' == $message[$pos - 1]) { continue; } $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); $offset = $pos + strlen($text); if ($open = '/' != $text[1]) { $tag = $matches[1][$i][0]; } else { $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; } if (!$open && !$tag) { $this->styleStack->pop(); } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { $output .= $this->applyCurrentStyle($text); } elseif ($open) { $this->styleStack->push($style); } else { $this->styleStack->pop($style); } } $output .= $this->applyCurrentStyle(substr($message, $offset)); if (false !== strpos($output, '<<')) { return strtr($output, array('\\<' => '<', '<<' => '\\')); } return str_replace('\\<', '<', $output); } public function getStyleStack() { return $this->styleStack; } private function createStyleFromString($string) { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) { return false; } $style = new OutputFormatterStyle(); foreach ($matches as $match) { array_shift($match); if ('fg' == $match[0]) { $style->setForeground($match[1]); } elseif ('bg' == $match[0]) { $style->setBackground($match[1]); } elseif ('options' === $match[0]) { preg_match_all('([^,;]+)', $match[1], $options); $options = array_shift($options); foreach ($options as $option) { try { $style->setOption($option); } catch (\InvalidArgumentException $e) { @trigger_error(sprintf('Unknown style options are deprecated since version 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED); return false; } } } else { return false; } } return $style; } private function applyCurrentStyle($text) { return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; } } verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; $this->formatter = $formatter ?: new OutputFormatter(); $this->formatter->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } public function getFormatter() { return $this->formatter; } public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); } public function isDecorated() { return $this->formatter->isDecorated(); } public function setVerbosity($level) { $this->verbosity = (int) $level; } public function getVerbosity() { return $this->verbosity; } public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } public function writeln($messages, $options = self::OUTPUT_NORMAL) { $this->write($messages, true, $options); } public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) { $messages = (array) $messages; $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; $type = $types & $options ?: self::OUTPUT_NORMAL; $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; if ($verbosity > $this->getVerbosity()) { return; } foreach ($messages as $message) { switch ($type) { case OutputInterface::OUTPUT_NORMAL: $message = $this->formatter->format($message); break; case OutputInterface::OUTPUT_RAW: break; case OutputInterface::OUTPUT_PLAIN: $message = strip_tags($this->formatter->format($message)); break; } $this->doWrite($message, $newline); } } abstract protected function doWrite($message, $newline); } openOutputStream(), $verbosity, $decorated, $formatter); $actualDecorated = $this->isDecorated(); $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); if (null === $decorated) { $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); } } public function setDecorated($decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } public function setVerbosity($level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } public function getErrorOutput() { return $this->stderr; } public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } protected function hasStdoutSupport() { return false === $this->isRunningOS400(); } protected function hasStderrSupport() { return false === $this->isRunningOS400(); } private function isRunningOS400() { $checks = array( function_exists('php_uname') ? php_uname('s') : '', getenv('OSTYPE'), PHP_OS, ); return false !== stripos(implode(';', $checks), 'OS400'); } private function openOutputStream() { if (!$this->hasStdoutSupport()) { return fopen('php://output', 'w'); } return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); } private function openErrorStream() { return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); } } buffer; $this->buffer = ''; return $content; } protected function doWrite($message, $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= PHP_EOL; } } } stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } public function getStream() { return $this->stream; } protected function doWrite($message, $newline) { if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) { throw new RuntimeException('Unable to write output.'); } fflush($this->stream); } protected function hasColorSupport() { if (DIRECTORY_SEPARATOR === '\\') { return '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); } return function_exists('posix_isatty') && @posix_isatty($this->stream); } } array('pipe', 'w'), 2 => array('pipe', 'w'), ); $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); if (is_resource($process)) { $info = stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { return array((int) $matches[2], (int) $matches[1]); } } } private static function getSttyColumns() { if (!function_exists('proc_open')) { return; } $descriptorspec = array( 1 => array('pipe', 'w'), 2 => array('pipe', 'w'), ); $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); if (is_resource($process)) { $info = stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); return $info; } } } alternatives = $alternatives; } public function getAlternatives() { return $this->alternatives; } } command = $command; $this->input = $input; $this->output = $output; } public function getCommand() { return $this->command; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } } setExitCode($exitCode); } public function setExitCode($exitCode) { $this->exitCode = (int) $exitCode; } public function getExitCode() { return $this->exitCode; } } commandShouldRun = false; } public function enableCommand() { return $this->commandShouldRun = true; } public function commandShouldRun() { return $this->commandShouldRun; } } setException($exception); $this->exitCode = (int) $exitCode; } public function getException() { return $this->exception; } public function setException(\Exception $exception) { $this->exception = $exception; } public function getExitCode() { return $this->exitCode; } } setName('list') ->setDefinition($this->createDefinition()) ->setDescription('Lists commands') ->setHelp(<<<'EOF' The %command.name% command lists all commands: php %command.full_name% You can also display the commands for a specific namespace: php %command.full_name% test You can also output the information in other formats by using the --format option: php %command.full_name% --format=xml It's also possible to get raw list of commands (useful for embedding command runner): php %command.full_name% --raw EOF ) ; } public function getNativeDefinition() { return $this->createDefinition(); } protected function execute(InputInterface $input, OutputInterface $output) { $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), )); } private function createDefinition() { return new InputDefinition(array( new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), )); } } lockHandler) { throw new LogicException('A lock is already in place.'); } $this->lockHandler = new LockHandler($name ?: $this->getName()); if (!$this->lockHandler->lock($blocking)) { $this->lockHandler = null; return false; } return true; } private function release() { if ($this->lockHandler) { $this->lockHandler->release(); $this->lockHandler = null; } } } ignoreValidationErrors(); $this ->setName('help') ->setDefinition(array( new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), )) ->setDescription('Displays help for a command') ->setHelp(<<<'EOF' The %command.name% command displays help for a given command: php %command.full_name% list You can also output the help in other formats by using the --format option: php %command.full_name% --format=xml list To display the list of available commands, please use the list command. EOF ) ; } public function setCommand(Command $command) { $this->command = $command; } protected function execute(InputInterface $input, OutputInterface $output) { if (null === $this->command) { $this->command = $this->getApplication()->find($input->getArgument('command_name')); } $helper = new DescriptorHelper(); $helper->describe($output, $this->command, array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), )); $this->command = null; } } definition = new InputDefinition(); if (null !== $name) { $this->setName($name); } $this->configure(); if (!$this->name) { throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); } } public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } public function setApplication(Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public function getApplication() { return $this->application; } public function isEnabled() { return true; } protected function configure() { } protected function execute(InputInterface $input, OutputInterface $output) { throw new LogicException('You must override the execute() method in the concrete command class.'); } protected function interact(InputInterface $input, OutputInterface $output) { } protected function initialize(InputInterface $input, OutputInterface $output) { } public function run(InputInterface $input, OutputInterface $output) { $this->getSynopsis(true); $this->getSynopsis(false); $this->mergeApplicationDefinition(); try { $input->bind($this->definition); } catch (ExceptionInterface $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if (null !== $this->processTitle) { if (function_exists('cli_set_process_title')) { if (false === @cli_set_process_title($this->processTitle)) { if ('Darwin' === PHP_OS) { $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); } else { $error = error_get_last(); trigger_error($error['message'], E_USER_WARNING); } } } elseif (function_exists('setproctitle')) { setproctitle($this->processTitle); } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { $output->writeln('Install the proctitle PECL to be able to change the process title.'); } } if ($input->isInteractive()) { $this->interact($input, $output); } if ($input->hasArgument('command') && null === $input->getArgument('command')) { $input->setArgument('command', $this->getName()); } $input->validate(); if ($this->code) { $statusCode = call_user_func($this->code, $input, $output); } else { $statusCode = $this->execute($input, $output); } return is_numeric($statusCode) ? (int) $statusCode : 0; } public function setCode(callable $code) { if ($code instanceof \Closure) { $r = new \ReflectionFunction($code); if (null === $r->getClosureThis()) { if (PHP_VERSION_ID < 70000) { $code = @\Closure::bind($code, $this); } else { $code = \Closure::bind($code, $this); } } } $this->code = $code; return $this; } public function mergeApplicationDefinition($mergeArgs = true) { if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { return; } $this->definition->addOptions($this->application->getDefinition()->getOptions()); if ($mergeArgs) { $currentArguments = $this->definition->getArguments(); $this->definition->setArguments($this->application->getDefinition()->getArguments()); $this->definition->addArguments($currentArguments); } $this->applicationDefinitionMerged = true; if ($mergeArgs) { $this->applicationDefinitionMergedWithArgs = true; } } public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->applicationDefinitionMerged = false; return $this; } public function getDefinition() { return $this->definition; } public function getNativeDefinition() { return $this->getDefinition(); } public function addArgument($name, $mode = null, $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); return $this; } public function setName($name) { $this->validateName($name); $this->name = $name; return $this; } public function setProcessTitle($title) { $this->processTitle = $title; return $this; } public function getName() { return $this->name; } public function setHidden($hidden) { $this->hidden = (bool) $hidden; return $this; } public function isHidden() { return $this->hidden; } public function setDescription($description) { $this->description = $description; return $this; } public function getDescription() { return $this->description; } public function setHelp($help) { $this->help = $help; return $this; } public function getHelp() { return $this->help; } public function getProcessedHelp() { $name = $this->name; $placeholders = array( '%command.name%', '%command.full_name%', ); $replacements = array( $name, $_SERVER['PHP_SELF'].' '.$name, ); return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); } public function setAliases($aliases) { if (!is_array($aliases) && !$aliases instanceof \Traversable) { throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); } foreach ($aliases as $alias) { $this->validateName($alias); } $this->aliases = $aliases; return $this; } public function getAliases() { return $this->aliases; } public function getSynopsis($short = false) { $key = $short ? 'short' : 'long'; if (!isset($this->synopsis[$key])) { $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); } return $this->synopsis[$key]; } public function addUsage($usage) { if (0 !== strpos($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } public function getUsages() { return $this->usages; } public function getHelper($name) { if (null === $this->helperSet) { throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); } return $this->helperSet->get($name); } private function validateName($name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, ); private $formatLevelMap = array( LogLevel::EMERGENCY => self::ERROR, LogLevel::ALERT => self::ERROR, LogLevel::CRITICAL => self::ERROR, LogLevel::ERROR => self::ERROR, LogLevel::WARNING => self::INFO, LogLevel::NOTICE => self::INFO, LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::INFO, ); private $errored = false; public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) { $this->output = $output; $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } public function log($level, $message, array $context = array()) { if (!isset($this->verbosityLevelMap[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); } $output = $this->output; if ($this->formatLevelMap[$level] === self::ERROR) { if ($this->output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->errored = true; } if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); } } public function hasErrored() { return $this->errored; } private function interpolate($message, array $context) { $replace = array(); foreach ($context as $key => $val) { if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { $replace[sprintf('{%s}', $key)] = $val; } } return strtr($message, $replace); } } 7 || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } $this->name = $name; $this->mode = $mode; $this->description = $description; $this->setDefault($default); } public function getName() { return $this->name; } public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } public function setDefault($default = null) { if (self::REQUIRED === $this->mode && null !== $default) { throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } elseif (!is_array($default)) { throw new LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } } setTokens($this->tokenize($input)); } private function tokenize($input) { $tokens = array(); $length = strlen($input); $cursor = 0; while ($cursor < $length) { if (preg_match('/\s+/A', $input, $match, null, $cursor)) { } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes($match[1]); } else { throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); } $cursor += strlen($match[0]); } return $tokens; } } definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } public function bind(InputDefinition $definition) { $this->arguments = array(); $this->options = array(); $this->definition = $definition; $this->parse(); } abstract protected function parse(); public function validate() { $definition = $this->definition; $givenArguments = $this->arguments; $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); }); if (count($missingArguments) > 0) { throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); } } public function isInteractive() { return $this->interactive; } public function setInteractive($interactive) { $this->interactive = (bool) $interactive; } public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } public function getArgument($name) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); } public function setArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } public function hasArgument($name) { return $this->definition->hasArgument($name); } public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } public function getOption($name) { if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } public function setOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } public function hasOption($name) { return $this->definition->hasOption($name); } public function escapeToken($token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } public function setStream($stream) { $this->stream = $stream; } public function getStream() { return $this->stream; } } parameters = $parameters; parent::__construct($definition); } public function getFirstArgument() { foreach ($this->parameters as $key => $value) { if ($key && '-' === $key[0]) { continue; } return $value; } } public function hasParameterOption($values, $onlyParams = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!is_int($k)) { $v = $k; } if ($onlyParams && $v === '--') { return false; } if (in_array($v, $values)) { return true; } } return false; } public function getParameterOption($values, $default = false, $onlyParams = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if ($onlyParams && ($k === '--' || (is_int($k) && $v === '--'))) { return false; } if (is_int($k)) { if (in_array($v, $values)) { return true; } } elseif (in_array($k, $values)) { return $v; } } return $default; } public function __toString() { $params = array(); foreach ($this->parameters as $param => $val) { if ($param && '-' === $param[0]) { $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); } else { $params[] = $this->escapeToken($val); } } return implode(' ', $params); } protected function parse() { foreach ($this->parameters as $key => $value) { if ($key === '--') { return; } if (0 === strpos($key, '--')) { $this->addLongOption(substr($key, 2), $value); } elseif ('-' === $key[0]) { $this->addShortOption(substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } private function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } private function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isValueRequired()) { throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); } $value = $option->isValueOptional() ? $option->getDefault() : true; } $this->options[$name] = $value; } private function addArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } } setDefinition($definition); } public function setDefinition(array $definition) { $arguments = array(); $options = array(); foreach ($definition as $item) { if ($item instanceof InputOption) { $options[] = $item; } else { $arguments[] = $item; } } $this->setArguments($arguments); $this->setOptions($options); } public function setArguments($arguments = array()) { $this->arguments = array(); $this->requiredCount = 0; $this->hasOptional = false; $this->hasAnArrayArgument = false; $this->addArguments($arguments); } public function addArguments($arguments = array()) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); } if ($this->hasAnArrayArgument) { throw new LogicException('Cannot add an argument after an array argument.'); } if ($argument->isRequired() && $this->hasOptional) { throw new LogicException('Cannot add a required argument after an optional one.'); } if ($argument->isArray()) { $this->hasAnArrayArgument = true; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->hasOptional = true; } $this->arguments[$argument->getName()] = $argument; } public function getArgument($name) { if (!$this->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return $arguments[$name]; } public function hasArgument($name) { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } public function getArguments() { return $this->arguments; } public function getArgumentCount() { return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); } public function getArgumentRequiredCount() { return $this->requiredCount; } public function getArgumentDefaults() { $values = array(); foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } public function setOptions($options = array()) { $this->options = array(); $this->shortcuts = array(); $this->addOptions($options); } public function addOptions($options = array()) { foreach ($options as $option) { $this->addOption($option); } } public function addOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); } } } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { $this->shortcuts[$shortcut] = $option->getName(); } } } public function getOption($name) { if (!$this->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } public function hasOption($name) { return isset($this->options[$name]); } public function getOptions() { return $this->options; } public function hasShortcut($name) { return isset($this->shortcuts[$name]); } public function getOptionForShortcut($shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } public function getOptionDefaults() { $values = array(); foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } private function shortcutToName($shortcut) { if (!isset($this->shortcuts[$shortcut])) { throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } public function getSynopsis($short = false) { $elements = array(); if ($short && $this->getOptions()) { $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { $value = ''; if ($option->acceptValue()) { $value = sprintf( ' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '' ); } $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); } } if (count($elements) && $this->getArguments()) { $elements[] = '[--]'; } foreach ($this->getArguments() as $argument) { $element = '<'.$argument->getName().'>'; if (!$argument->isRequired()) { $element = '['.$element.']'; } elseif ($argument->isArray()) { $element = $element.' ('.$element.')'; } if ($argument->isArray()) { $element .= '...'; } $elements[] = $element; } return implode(' ', $elements); } } tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } protected function parse() { $parseOptions = true; $this->parsed = $this->tokens; while (null !== $token = array_shift($this->parsed)) { if ($parseOptions && '' == $token) { $this->parseArgument($token); } elseif ($parseOptions && '--' == $token) { $parseOptions = false; } elseif ($parseOptions && 0 === strpos($token, '--')) { $this->parseLongOption($token); } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { $this->parseShortOption($token); } else { $this->parseArgument($token); } } } private function parseShortOption($token) { $name = substr($token, 1); if (strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } private function parseShortOptionSet($name) { $len = strlen($name); for ($i = 0; $i < $len; ++$i) { if (!$this->definition->hasShortcut($name[$i])) { throw new RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); } $option = $this->definition->getOptionForShortcut($name[$i]); if ($option->acceptValue()) { $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); break; } else { $this->addLongOption($option->getName(), null); } } } private function parseLongOption($token) { $name = substr($token, 2); if (false !== $pos = strpos($name, '=')) { if (0 === strlen($value = substr($name, $pos + 1))) { array_unshift($this->parsed, null); } $this->addLongOption(substr($name, 0, $pos), $value); } else { $this->addLongOption($name, null); } } private function parseArgument($token) { $c = count($this->arguments); if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token; } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; } else { $all = $this->definition->getArguments(); if (count($all)) { throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); } throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); } } private function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } private function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (!isset($value[0])) { $value = null; } if (null !== $value && !$option->acceptValue()) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } if (null === $value && $option->acceptValue() && count($this->parsed)) { $next = array_shift($this->parsed); if (isset($next[0]) && '-' !== $next[0]) { $value = $next; } elseif (empty($next)) { $value = null; } else { array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isValueRequired()) { throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isArray()) { $value = $option->isValueOptional() ? $option->getDefault() : true; } } if ($option->isArray()) { $this->options[$name][] = $value; } else { $this->options[$name] = $value; } } public function getFirstArgument() { foreach ($this->tokens as $token) { if ($token && '-' === $token[0]) { continue; } return $token; } } public function hasParameterOption($values, $onlyParams = false) { $values = (array) $values; foreach ($this->tokens as $token) { if ($onlyParams && $token === '--') { return false; } foreach ($values as $value) { if ($token === $value || 0 === strpos($token, $value.'=')) { return true; } } } return false; } public function getParameterOption($values, $default = false, $onlyParams = false) { $values = (array) $values; $tokens = $this->tokens; while (0 < count($tokens)) { $token = array_shift($tokens); if ($onlyParams && $token === '--') { return false; } foreach ($values as $value) { if ($token === $value || 0 === strpos($token, $value.'=')) { if (false !== $pos = strpos($token, '=')) { return substr($token, $pos + 1); } return array_shift($tokens); } } } return $default; } public function __toString() { $tokens = array_map(function ($token) { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1].$this->escapeToken($match[2]); } if ($token && $token[0] !== '-') { return $this->escapeToken($token); } return $token; }, $this->tokens); return implode(' ', $tokens); } } 15 || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } $this->setDefault($default); } public function getShortcut() { return $this->shortcut; } public function getName() { return $this->name; } public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } public function setDefault($default = null) { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } elseif (!is_array($default)) { throw new LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptValue() ? $default : false; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } public function equals(InputOption $option) { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional() ; } } ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; } public static function create() { return new static(); } public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; return $this; } public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } public function depth($level) { $this->depths[] = new Comparator\NumberComparator($level); return $this; } public function date($date) { $this->dates[] = new Comparator\DateComparator($date); return $this; } public function name($pattern) { $this->names[] = $pattern; return $this; } public function notName($pattern) { $this->notNames[] = $pattern; return $this; } public function contains($pattern) { $this->contains[] = $pattern; return $this; } public function notContains($pattern) { $this->notContains[] = $pattern; return $this; } public function path($pattern) { $this->paths[] = $pattern; return $this; } public function notPath($pattern) { $this->notPaths[] = $pattern; return $this; } public function size($size) { $this->sizes[] = new Comparator\NumberComparator($size); return $this; } public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); return $this; } public function ignoreDotFiles($ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; } else { $this->ignore &= ~static::IGNORE_DOT_FILES; } return $this; } public function ignoreVCS($ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_FILES; } return $this; } public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; } self::$vcsPatterns = array_unique(self::$vcsPatterns); } public function sort(\Closure $closure) { $this->sort = $closure; return $this; } public function sortByName() { $this->sort = Iterator\SortableIterator::SORT_BY_NAME; return $this; } public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } public function filter(\Closure $closure) { $this->filters[] = $closure; return $this; } public function followLinks() { $this->followLinks = true; return $this; } public function ignoreUnreadableDirs($ignore = true) { $this->ignoreUnreadableDirs = (bool) $ignore; return $this; } public function in($dirs) { $resolvedDirs = array(); foreach ((array) $dirs as $dir) { if (is_dir($dir)) { $resolvedDirs[] = $dir; } elseif ($glob = glob($dir, (defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) { $resolvedDirs = array_merge($resolvedDirs, $glob); } else { throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); } } $this->dirs = array_merge($this->dirs, $resolvedDirs); return $this; } public function getIterator() { if (0 === count($this->dirs) && 0 === count($this->iterators)) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); } if (1 === count($this->dirs) && 0 === count($this->iterators)) { return $this->searchInDirectory($this->dirs[0]); } $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { $iterator->append($this->searchInDirectory($dir)); } foreach ($this->iterators as $it) { $iterator->append($it); } return $iterator; } public function append($iterator) { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); } elseif ($iterator instanceof \Iterator) { $this->iterators[] = $iterator; } elseif ($iterator instanceof \Traversable || is_array($iterator)) { $it = new \ArrayIterator(); foreach ($iterator as $file) { $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); } $this->iterators[] = $it; } else { throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); } return $this; } public function count() { return iterator_count($this->getIterator()); } private function searchInDirectory($dir) { if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $this->exclude = array_merge($this->exclude, self::$vcsPatterns); } if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { $this->notPaths[] = '#(^|/)\..+(/|$)#'; } $minDepth = 0; $maxDepth = PHP_INT_MAX; foreach ($this->depths as $comparator) { switch ($comparator->getOperator()) { case '>': $minDepth = $comparator->getTarget() + 1; break; case '>=': $minDepth = $comparator->getTarget(); break; case '<': $maxDepth = $comparator->getTarget() - 1; break; case '<=': $maxDepth = $comparator->getTarget(); break; default: $minDepth = $maxDepth = $comparator->getTarget(); } } $flags = \RecursiveDirectoryIterator::SKIP_DOTS; if ($this->followLinks) { $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; } $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); } $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) { $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); } if ($this->mode) { $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); } if ($this->names || $this->notNames) { $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); } if ($this->contains || $this->notContains) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->sizes) { $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); } if ($this->dates) { $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); } if ($this->filters) { $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } if ($this->paths || $this->notPaths) { $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); } if ($this->sort) { $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); $iterator = $iteratorAggregate->getIterator(); } return $iterator; } } minDepth = $minDepth; $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); parent::__construct($iterator); } public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } } current()->getRelativePathname(); if ('\\' === DIRECTORY_SEPARATOR) { $filename = str_replace('\\', '/', $filename); } return $this->isAccepted($filename); } protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } filters = $filters; parent::__construct($iterator); } public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { if (false === call_user_func($filter, $fileinfo)) { return false; } } return true; } } ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->rootPath = $path; if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { $this->directorySeparator = DIRECTORY_SEPARATOR; } } public function current() { if (null === $subPathname = $this->subPath) { $subPathname = $this->subPath = (string) $this->getSubPath(); } if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; } $subPathname .= $this->getFilename(); return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname); } public function getChildren() { try { $children = parent::getChildren(); if ($children instanceof self) { $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } return $children; } catch (\UnexpectedValueException $e) { if ($this->ignoreUnreadableDirs) { return new \RecursiveArrayIterator(array()); } else { throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } } public function rewind() { if (false === $this->isRewindable()) { return; } if (PHP_VERSION_ID < 50523 || PHP_VERSION_ID >= 50600 && PHP_VERSION_ID < 50607) { parent::next(); } parent::rewind(); } public function isRewindable() { if (null !== $this->rewindable) { return $this->rewindable; } if ('' === $this->getPath()) { return $this->rewindable = false; } if (false !== $stream = @opendir($this->getPath())) { $infos = stream_get_meta_data($stream); closedir($stream); if ($infos['seekable']) { return $this->rewindable = true; } } return $this->rewindable = false; } } matchRegexps[] = $this->toRegex($pattern); } foreach ($noMatchPatterns as $pattern) { $this->noMatchRegexps[] = $this->toRegex($pattern); } parent::__construct($iterator); } protected function isAccepted($string) { foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $string)) { return false; } } if ($this->matchRegexps) { foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $string)) { return true; } } return false; } return true; } protected function isRegex($str) { if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { $start = substr($m[1], 0, 1); $end = substr($m[1], -1); if ($start === $end) { return !preg_match('/[*?[:alnum:] \\\\]/', $start); } foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) { if ($start === $delimiters[0] && $end === $delimiters[1]) { return true; } } } return false; } abstract protected function toRegex($str); } iterator = $iterator; if (self::SORT_BY_NAME === $sort) { $this->sort = function ($a, $b) { return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = function ($a, $b) { if ($a->isDir() && $b->isFile()) { return -1; } elseif ($a->isFile() && $b->isDir()) { return 1; } return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { $this->sort = function ($a, $b) { return $a->getATime() - $b->getATime(); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { $this->sort = function ($a, $b) { return $a->getCTime() - $b->getCTime(); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { $this->sort = function ($a, $b) { return $a->getMTime() - $b->getMTime(); }; } elseif (is_callable($sort)) { $this->sort = $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } public function getIterator() { $array = iterator_to_array($this->iterator, true); uasort($array, $this->sort); return new \ArrayIterator($array); } } comparators = $comparators; parent::__construct($iterator); } public function accept() { $fileinfo = $this->current(); if (!$fileinfo->isFile()) { return true; } $filesize = $fileinfo->getSize(); foreach ($this->comparators as $compare) { if (!$compare->test($filesize)) { return false; } } return true; } } matchRegexps && !$this->noMatchRegexps) { return true; } $fileinfo = $this->current(); if ($fileinfo->isDir() || !$fileinfo->isReadable()) { return false; } $content = $fileinfo->getContents(); if (!$content) { return false; } return $this->isAccepted($content); } protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } comparators = $comparators; parent::__construct($iterator); } public function accept() { $fileinfo = $this->current(); if (!file_exists($fileinfo->getPathname())) { return false; } $filedate = $fileinfo->getMTime(); foreach ($this->comparators as $compare) { if (!$compare->test($filedate)) { return false; } } return true; } } isAccepted($this->current()->getFilename()); } protected function toRegex($str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } } iterator = $iterator; $this->isRecursive = $iterator instanceof \RecursiveIterator; $patterns = array(); foreach ($directories as $directory) { $directory = rtrim($directory, '/'); if (!$this->isRecursive || false !== strpos($directory, '/')) { $patterns[] = preg_quote($directory, '#'); } else { $this->excludedDirs[$directory] = true; } } if ($patterns) { $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; } parent::__construct($iterator); } public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { return false; } if ($this->excludedPattern) { $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); $path = str_replace('\\', '/', $path); return !preg_match($this->excludedPattern, $path); } return true; } public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } public function getChildren() { $children = new self($this->iterator->getChildren(), array()); $children->excludedDirs = $this->excludedDirs; $children->excludedPattern = $this->excludedPattern; return $children; } } mode = $mode; parent::__construct($iterator); } public function accept() { $fileinfo = $this->current(); if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { return false; } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { return false; } return true; } } 50607 || (PHP_VERSION_ID > 50523 && PHP_VERSION_ID < 50600)) { parent::rewind(); return; } $iterator = $this; while ($iterator instanceof \OuterIterator) { $innerIterator = $iterator->getInnerIterator(); if ($innerIterator instanceof RecursiveDirectoryIterator) { if ($innerIterator->isRewindable()) { $innerIterator->next(); $innerIterator->rewind(); } } elseif ($innerIterator instanceof \FilesystemIterator) { $innerIterator->next(); $innerIterator->rewind(); } $iterator = $innerIterator; } parent::rewind(); } } relativePath = $relativePath; $this->relativePathname = $relativePathname; } public function getRelativePath() { return $this->relativePath; } public function getRelativePathname() { return $this->relativePathname; } public function getContents() { $level = error_reporting(0); $content = file_get_contents($this->getPathname()); error_reporting($level); if (false === $content) { $error = error_get_last(); throw new \RuntimeException($error['message']); } return $content; } } ]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); } $target = $matches[2]; if (!is_numeric($target)) { throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); } if (isset($matches[3])) { switch (strtolower($matches[3])) { case 'k': $target *= 1000; break; case 'ki': $target *= 1024; break; case 'm': $target *= 1000000; break; case 'mi': $target *= 1024 * 1024; break; case 'g': $target *= 1000000000; break; case 'gi': $target *= 1024 * 1024 * 1024; break; } } $this->setTarget($target); $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); } } ]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); } try { $date = new \DateTime($matches[2]); $target = $date->format('U'); } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } $operator = isset($matches[1]) ? $matches[1] : '=='; if ('since' === $operator || 'after' === $operator) { $operator = '>'; } if ('until' === $operator || 'before' === $operator) { $operator = '<'; } $this->setOperator($operator); $this->setTarget($target); } } target; } public function setTarget($target) { $this->target = $target; } public function getOperator() { return $this->operator; } public function setOperator($operator) { if (!$operator) { $operator = '=='; } if (!in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } $this->operator = $operator; } public function test($test) { switch ($this->operator) { case '>': return $test > $this->target; case '>=': return $test >= $this->target; case '<': return $test < $this->target; case '<=': return $test <= $this->target; case '!=': return $test != $this->target; } return $test == $this->target; } } propagationStopped; } public function stopPropagation() { $this->propagationStopped = true; } } dispatcherService = $dispatcherService; $this->listenerTag = $listenerTag; $this->subscriberTag = $subscriberTag; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { return; } $definition = $container->findDefinition($this->dispatcherService); foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) { $def = $container->getDefinition($id); if (!$def->isPublic()) { throw new InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id)); } if ($def->isAbstract()) { throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id)); } foreach ($events as $event) { $priority = isset($event['priority']) ? $event['priority'] : 0; if (!isset($event['event'])) { throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); } if (!isset($event['method'])) { $event['method'] = 'on'.preg_replace_callback(array( '/(?<=\b)[a-z]/i', '/[^a-z0-9]/i', ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); } $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); } } foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { $def = $container->getDefinition($id); if (!$def->isPublic()) { throw new InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id)); } if ($def->isAbstract()) { throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id)); } $class = $container->getParameterBag()->resolveValue($def->getClass()); $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; if (!is_subclass_of($class, $interface)) { if (!class_exists($class, false)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); } $definition->addMethodCall('addSubscriberService', array($id, $class)); } } } dispatcher = $dispatcher; $this->stopwatch = $stopwatch; $this->logger = $logger; $this->called = array(); $this->wrappedListeners = array(); } public function addListener($eventName, $listener, $priority = 0) { $this->dispatcher->addListener($eventName, $listener, $priority); } public function addSubscriber(EventSubscriberInterface $subscriber) { $this->dispatcher->addSubscriber($subscriber); } public function removeListener($eventName, $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { if ($wrappedListener->getWrappedListener() === $listener) { $listener = $wrappedListener; unset($this->wrappedListeners[$eventName][$index]); break; } } } return $this->dispatcher->removeListener($eventName, $listener); } public function removeSubscriber(EventSubscriberInterface $subscriber) { return $this->dispatcher->removeSubscriber($subscriber); } public function getListeners($eventName = null) { return $this->dispatcher->getListeners($eventName); } public function getListenerPriority($eventName, $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { if ($wrappedListener->getWrappedListener() === $listener) { return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); } } } return $this->dispatcher->getListenerPriority($eventName, $listener); } public function hasListeners($eventName = null) { return $this->dispatcher->hasListeners($eventName); } public function dispatch($eventName, Event $event = null) { if (null === $event) { $event = new Event(); } if (null !== $this->logger && $event->isPropagationStopped()) { $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); } $this->preProcess($eventName); $this->preDispatch($eventName, $event); $e = $this->stopwatch->start($eventName, 'section'); $this->dispatcher->dispatch($eventName, $event); if ($e->isStarted()) { $e->stop(); } $this->postDispatch($eventName, $event); $this->postProcess($eventName); return $event; } public function getCalledListeners() { $called = array(); foreach ($this->called as $eventName => $listeners) { foreach ($listeners as $listener) { $called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); } } return $called; } public function getNotCalledListeners() { try { $allListeners = $this->getListeners(); } catch (\Exception $e) { if (null !== $this->logger) { $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); } return array(); } $notCalled = array(); foreach ($allListeners as $eventName => $listeners) { foreach ($listeners as $listener) { $called = false; if (isset($this->called[$eventName])) { foreach ($this->called[$eventName] as $l) { if ($l->getWrappedListener() === $listener) { $called = true; break; } } } if (!$called) { if (!$listener instanceof WrappedListener) { $listener = new WrappedListener($listener, null, $this->stopwatch, $this); } $notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); } } } uasort($notCalled, array($this, 'sortListenersByPriority')); return $notCalled; } public function __call($method, $arguments) { return call_user_func_array(array($this->dispatcher, $method), $arguments); } protected function preDispatch($eventName, Event $event) { } protected function postDispatch($eventName, Event $event) { } private function preProcess($eventName) { foreach ($this->dispatcher->getListeners($eventName) as $listener) { $priority = $this->getListenerPriority($eventName, $listener); $wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this); $this->wrappedListeners[$eventName][] = $wrappedListener; $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $wrappedListener, $priority); } } private function postProcess($eventName) { unset($this->wrappedListeners[$eventName]); $skipped = false; foreach ($this->dispatcher->getListeners($eventName) as $listener) { if (!$listener instanceof WrappedListener) { continue; } $priority = $this->getListenerPriority($eventName, $listener); $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); if (null !== $this->logger) { $context = array('event' => $eventName, 'listener' => $listener->getPretty()); } if ($listener->wasCalled()) { if (null !== $this->logger) { $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); } if (!isset($this->called[$eventName])) { $this->called[$eventName] = new \SplObjectStorage(); } $this->called[$eventName]->attach($listener); } if (null !== $this->logger && $skipped) { $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); } if ($listener->stoppedPropagation()) { if (null !== $this->logger) { $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); } $skipped = true; } } } private function sortListenersByPriority($a, $b) { if (is_int($a['priority']) && !is_int($b['priority'])) { return 1; } if (!is_int($a['priority']) && is_int($b['priority'])) { return -1; } if ($a['priority'] === $b['priority']) { return 0; } if ($a['priority'] > $b['priority']) { return -1; } return 1; } } listener = $listener; $this->name = $name; $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = false; $this->stoppedPropagation = false; if (is_array($listener)) { $this->name = is_object($listener[0]) ? get_class($listener[0]) : $listener[0]; $this->pretty = $this->name.'::'.$listener[1]; } elseif ($listener instanceof \Closure) { $this->pretty = $this->name = 'closure'; } elseif (is_string($listener)) { $this->pretty = $this->name = $listener; } else { $this->name = get_class($listener); $this->pretty = $this->name.'::__invoke'; } if (null !== $name) { $this->name = $name; } if (null === self::$cloner) { self::$cloner = class_exists(ClassStub::class) ? new VarCloner() : false; } } public function getWrappedListener() { return $this->listener; } public function wasCalled() { return $this->called; } public function stoppedPropagation() { return $this->stoppedPropagation; } public function getPretty() { return $this->pretty; } public function getInfo($eventName) { if (null === $this->data) { $this->data = false !== self::$cloner ? self::$cloner->cloneVar(array(new ClassStub($this->pretty.'()', $this->listener)))->seek(0) : $this->pretty; } return array( 'event' => $eventName, 'priority' => null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null, 'pretty' => $this->pretty, 'data' => $this->data, ); } public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) { $this->called = true; $e = $this->stopwatch->start($this->name, 'event_listener'); call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); if ($e->isStarted()) { $e->stop(); } if ($event->isPropagationStopped()) { $this->stoppedPropagation = true; } } } subject = $subject; $this->arguments = $arguments; } public function getSubject() { return $this->subject; } public function getArgument($key) { if ($this->hasArgument($key)) { return $this->arguments[$key]; } throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); } public function setArgument($key, $value) { $this->arguments[$key] = $value; return $this; } public function getArguments() { return $this->arguments; } public function setArguments(array $args = array()) { $this->arguments = $args; return $this; } public function hasArgument($key) { return array_key_exists($key, $this->arguments); } public function offsetGet($key) { return $this->getArgument($key); } public function offsetSet($key, $value) { $this->setArgument($key, $value); } public function offsetUnset($key) { if ($this->hasArgument($key)) { unset($this->arguments[$key]); } } public function offsetExists($key) { return $this->hasArgument($key); } public function getIterator() { return new \ArrayIterator($this->arguments); } } getListeners($eventName)) { $this->doDispatch($listeners, $eventName, $event); } return $event; } public function getListeners($eventName = null) { if (null !== $eventName) { if (!isset($this->listeners[$eventName])) { return array(); } if (!isset($this->sorted[$eventName])) { $this->sortListeners($eventName); } return $this->sorted[$eventName]; } foreach ($this->listeners as $eventName => $eventListeners) { if (!isset($this->sorted[$eventName])) { $this->sortListeners($eventName); } } return array_filter($this->sorted); } public function getListenerPriority($eventName, $listener) { if (!isset($this->listeners[$eventName])) { return; } foreach ($this->listeners[$eventName] as $priority => $listeners) { if (false !== in_array($listener, $listeners, true)) { return $priority; } } } public function hasListeners($eventName = null) { return (bool) $this->getListeners($eventName); } public function addListener($eventName, $listener, $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName]); } public function removeListener($eventName, $listener) { if (!isset($this->listeners[$eventName])) { return; } foreach ($this->listeners[$eventName] as $priority => $listeners) { if (false !== ($key = array_search($listener, $listeners, true))) { unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); } } } public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (is_string($params)) { $this->addListener($eventName, array($subscriber, $params)); } elseif (is_string($params[0])) { $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); } else { foreach ($params as $listener) { $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); } } } } public function removeSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (is_array($params) && is_array($params[0])) { foreach ($params as $listener) { $this->removeListener($eventName, array($subscriber, $listener[0])); } } else { $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0])); } } } protected function doDispatch($listeners, $eventName, Event $event) { foreach ($listeners as $listener) { if ($event->isPropagationStopped()) { break; } call_user_func($listener, $event, $eventName, $this); } } private function sortListeners($eventName) { krsort($this->listeners[$eventName]); $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); } } dispatcher = $dispatcher; } public function dispatch($eventName, Event $event = null) { return $this->dispatcher->dispatch($eventName, $event); } public function addListener($eventName, $listener, $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function addSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeListener($eventName, $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function getListeners($eventName = null) { return $this->dispatcher->getListeners($eventName); } public function getListenerPriority($eventName, $listener) { return $this->dispatcher->getListenerPriority($eventName, $listener); } public function hasListeners($eventName = null) { return $this->dispatcher->hasListeners($eventName); } } container = $container; } public function addListenerService($eventName, $callback, $priority = 0) { if (!is_array($callback) || 2 !== count($callback)) { throw new \InvalidArgumentException('Expected an array("service", "method") argument'); } $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); } public function removeListener($eventName, $listener) { $this->lazyLoad($eventName); if (isset($this->listenerIds[$eventName])) { foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) { $key = $serviceId.'.'.$method; if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { unset($this->listeners[$eventName][$key]); if (empty($this->listeners[$eventName])) { unset($this->listeners[$eventName]); } unset($this->listenerIds[$eventName][$i]); if (empty($this->listenerIds[$eventName])) { unset($this->listenerIds[$eventName]); } } } } parent::removeListener($eventName, $listener); } public function hasListeners($eventName = null) { if (null === $eventName) { return (bool) count($this->listenerIds) || (bool) count($this->listeners); } if (isset($this->listenerIds[$eventName])) { return true; } return parent::hasListeners($eventName); } public function getListeners($eventName = null) { if (null === $eventName) { foreach ($this->listenerIds as $serviceEventName => $args) { $this->lazyLoad($serviceEventName); } } else { $this->lazyLoad($eventName); } return parent::getListeners($eventName); } public function getListenerPriority($eventName, $listener) { $this->lazyLoad($eventName); return parent::getListenerPriority($eventName, $listener); } public function addSubscriberService($serviceId, $class) { foreach ($class::getSubscribedEvents() as $eventName => $params) { if (is_string($params)) { $this->listenerIds[$eventName][] = array($serviceId, $params, 0); } elseif (is_string($params[0])) { $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); } else { foreach ($params as $listener) { $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); } } } } public function getContainer() { return $this->container; } protected function lazyLoad($eventName) { if (isset($this->listenerIds[$eventName])) { foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) { $listener = $this->container->get($serviceId); $key = $serviceId.'.'.$method; if (!isset($this->listeners[$eventName][$key])) { $this->addListener($eventName, array($listener, $method), $priority); } elseif ($listener !== $this->listeners[$eventName][$key]) { parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); $this->addListener($eventName, array($listener, $method), $priority); } $this->listeners[$eventName][$key] = $listener; } } } } node->getAttribute('href'); } protected function setNode(\DOMElement $node) { if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { throw new \LogicException(sprintf('Unable to navigate from a "%s" tag.', $node->nodeName)); } $this->node = $node; } } setNode($node); $this->method = $method ? strtoupper($method) : null; $this->currentUri = $currentUri; } public function getNode() { return $this->node; } public function getMethod() { return $this->method; } public function getUri() { $uri = trim($this->getRawUri()); if (null !== parse_url($uri, PHP_URL_SCHEME)) { return $uri; } if (!$uri) { return $this->currentUri; } if ('#' === $uri[0]) { return $this->cleanupAnchor($this->currentUri).$uri; } $baseUri = $this->cleanupUri($this->currentUri); if ('?' === $uri[0]) { return $baseUri.$uri; } if (0 === strpos($uri, '//')) { return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri; } $baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri); if ('/' === $uri[0]) { return $baseUri.$uri; } $path = parse_url(substr($this->currentUri, strlen($baseUri)), PHP_URL_PATH); $path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path; } abstract protected function getRawUri(); protected function canonicalizePath($path) { if ('' === $path || '/' === $path) { return $path; } if ('.' === substr($path, -1)) { $path .= '/'; } $output = array(); foreach (explode('/', $path) as $segment) { if ('..' === $segment) { array_pop($output); } elseif ('.' !== $segment) { $output[] = $segment; } } return implode('/', $output); } abstract protected function setNode(\DOMElement $node); private function cleanupUri($uri) { return $this->cleanupQuery($this->cleanupAnchor($uri)); } private function cleanupQuery($uri) { if (false !== $pos = strpos($uri, '?')) { return substr($uri, 0, $pos); } return $uri; } private function cleanupAnchor($uri) { if (false !== $pos = strpos($uri, '#')) { return substr($uri, 0, $pos); } return $uri; } } baseHref = $baseHref; $this->initialize(); } public function getFormNode() { return $this->node; } public function setValues(array $values) { foreach ($values as $name => $value) { $this->fields->set($name, $value); } return $this; } public function getValues() { $values = array(); foreach ($this->fields->all() as $name => $field) { if ($field->isDisabled()) { continue; } if (!$field instanceof Field\FileFormField && $field->hasValue()) { $values[$name] = $field->getValue(); } } return $values; } public function getFiles() { if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { return array(); } $files = array(); foreach ($this->fields->all() as $name => $field) { if ($field->isDisabled()) { continue; } if ($field instanceof Field\FileFormField) { $files[$name] = $field->getValue(); } } return $files; } public function getPhpValues() { $values = array(); foreach ($this->getValues() as $name => $value) { $qs = http_build_query(array($name => $value), '', '&'); if (!empty($qs)) { parse_str($qs, $expandedValue); $varName = substr($name, 0, strlen(key($expandedValue))); $values = array_replace_recursive($values, array($varName => current($expandedValue))); } } return $values; } public function getPhpFiles() { $values = array(); foreach ($this->getFiles() as $name => $value) { $qs = http_build_query(array($name => $value), '', '&'); if (!empty($qs)) { parse_str($qs, $expandedValue); $varName = substr($name, 0, strlen(key($expandedValue))); $values = array_replace_recursive($values, array($varName => current($expandedValue))); } } return $values; } public function getUri() { $uri = parent::getUri(); if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { $query = parse_url($uri, PHP_URL_QUERY); $currentParameters = array(); if ($query) { parse_str($query, $currentParameters); } $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), null, '&'); $pos = strpos($uri, '?'); $base = false === $pos ? $uri : substr($uri, 0, $pos); $uri = rtrim($base.'?'.$queryString, '?'); } return $uri; } protected function getRawUri() { return $this->node->getAttribute('action'); } public function getMethod() { if (null !== $this->method) { return $this->method; } return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET'; } public function has($name) { return $this->fields->has($name); } public function remove($name) { $this->fields->remove($name); } public function get($name) { return $this->fields->get($name); } public function set(FormField $field) { $this->fields->add($field); } public function all() { return $this->fields->all(); } public function offsetExists($name) { return $this->has($name); } public function offsetGet($name) { return $this->fields->get($name); } public function offsetSet($name, $value) { $this->fields->set($name, $value); } public function offsetUnset($name) { $this->fields->remove($name); } public function disableValidation() { foreach ($this->fields->all() as $field) { if ($field instanceof Field\ChoiceFormField) { $field->disableValidation(); } } return $this; } protected function setNode(\DOMElement $node) { $this->button = $node; if ('button' === $node->nodeName || ('input' === $node->nodeName && in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image')))) { if ($node->hasAttribute('form')) { $formId = $node->getAttribute('form'); $form = $node->ownerDocument->getElementById($formId); if (null === $form) { throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); } $this->node = $form; return; } do { if (null === $node = $node->parentNode) { throw new \LogicException('The selected node does not have a form ancestor.'); } } while ('form' !== $node->nodeName); } elseif ('form' !== $node->nodeName) { throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); } $this->node = $node; } private function initialize() { $this->fields = new FormFieldRegistry(); $xpath = new \DOMXPath($this->node->ownerDocument); if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) { if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) { $name = $this->button->getAttribute('name'); $this->button->setAttribute('value', '0'); $this->button->setAttribute('name', $name.'.x'); $this->set(new Field\InputFormField($this->button)); $this->button->setAttribute('name', $name.'.y'); $this->set(new Field\InputFormField($this->button)); $this->button->setAttribute('name', $name); } else { $this->set(new Field\InputFormField($this->button)); } } if ($this->node->hasAttribute('id')) { $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); $fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%s] | descendant::textarea[@form=%s] | descendant::select[@form=%s] | //form[@id=%s]//input[not(@form)] | //form[@id=%s]//button[not(@form)] | //form[@id=%s]//textarea[not(@form)] | //form[@id=%s]//select[not(@form)]', $formId, $formId, $formId, $formId, $formId, $formId, $formId, $formId)); foreach ($fieldNodes as $node) { $this->addField($node); } } else { $fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $this->node); foreach ($fieldNodes as $node) { $this->addField($node); } } if ($this->baseHref && '' !== $this->node->getAttribute('action')) { $this->currentUri = $this->baseHref; } } private function addField(\DOMElement $node) { if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { return; } $nodeName = $node->nodeName; if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) { $this->set(new Field\ChoiceFormField($node)); } elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) { if ($this->has($node->getAttribute('name')) && $this->get($node->getAttribute('name')) instanceof ChoiceFormField) { $this->get($node->getAttribute('name'))->addChoice($node); } else { $this->set(new Field\ChoiceFormField($node)); } } elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) { $this->set(new Field\FileFormField($node)); } elseif ('input' == $nodeName && !in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image'))) { $this->set(new Field\InputFormField($node)); } elseif ('textarea' == $nodeName) { $this->set(new Field\TextareaFormField($node)); } } } node->nodeName && 'button' !== $this->node->nodeName) { throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); } if ('checkbox' === strtolower($this->node->getAttribute('type'))) { throw new \LogicException('Checkboxes should be instances of ChoiceFormField.'); } if ('file' === strtolower($this->node->getAttribute('type'))) { throw new \LogicException('File inputs should be instances of FileFormField.'); } $this->value = $this->node->getAttribute('value'); } } node->nodeName) { throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); } $this->value = ''; foreach ($this->node->childNodes as $node) { $this->value .= $node->wholeText; } } } type, array('checkbox', 'radio')) && null === $this->value) { return false; } return true; } public function isDisabled() { if (parent::isDisabled() && 'select' === $this->type) { return true; } foreach ($this->options as $option) { if ($option['value'] == $this->value && $option['disabled']) { return true; } } return false; } public function select($value) { $this->setValue($value); } public function tick() { if ('checkbox' !== $this->type) { throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); } $this->setValue(true); } public function untick() { if ('checkbox' !== $this->type) { throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); } $this->setValue(false); } public function setValue($value) { if ('checkbox' === $this->type && false === $value) { $this->value = null; } elseif ('checkbox' === $this->type && true === $value) { $this->value = $this->options[0]['value']; } else { if (is_array($value)) { if (!$this->multiple) { throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name)); } foreach ($value as $v) { if (!$this->containsOption($v, $this->options)) { throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $v, implode(', ', $this->availableOptionValues()))); } } } elseif (!$this->containsOption($value, $this->options)) { throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $value, implode(', ', $this->availableOptionValues()))); } if ($this->multiple) { $value = (array) $value; } if (is_array($value)) { $this->value = $value; } else { parent::setValue($value); } } } public function addChoice(\DOMElement $node) { if (!$this->multiple && 'radio' !== $this->type) { throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); } $option = $this->buildOptionValue($node); $this->options[] = $option; if ($node->hasAttribute('checked')) { $this->value = $option['value']; } } public function getType() { return $this->type; } public function isMultiple() { return $this->multiple; } protected function initialize() { if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) { throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); } if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) { throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is %s).', $this->node->getAttribute('type'))); } $this->value = null; $this->options = array(); $this->multiple = false; if ('input' == $this->node->nodeName) { $this->type = strtolower($this->node->getAttribute('type')); $optionValue = $this->buildOptionValue($this->node); $this->options[] = $optionValue; if ($this->node->hasAttribute('checked')) { $this->value = $optionValue['value']; } } else { $this->type = 'select'; if ($this->node->hasAttribute('multiple')) { $this->multiple = true; $this->value = array(); $this->name = str_replace('[]', '', $this->name); } $found = false; foreach ($this->xpath->query('descendant::option', $this->node) as $option) { $optionValue = $this->buildOptionValue($option); $this->options[] = $optionValue; if ($option->hasAttribute('selected')) { $found = true; if ($this->multiple) { $this->value[] = $optionValue['value']; } else { $this->value = $optionValue['value']; } } } if (!$found && !$this->multiple && !empty($this->options)) { $this->value = $this->options[0]['value']; } } } private function buildOptionValue(\DOMElement $node) { $option = array(); $defaultDefaultValue = 'select' === $this->node->nodeName ? '' : 'on'; $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : $defaultDefaultValue; $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue; $option['disabled'] = $node->hasAttribute('disabled'); return $option; } public function containsOption($optionValue, $options) { if ($this->validationDisabled) { return true; } foreach ($options as $option) { if ($option['value'] == $optionValue) { return true; } } return false; } public function availableOptionValues() { $values = array(); foreach ($this->options as $option) { $values[] = $option['value']; } return $values; } public function disableValidation() { $this->validationDisabled = true; return $this; } } value = array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0); } public function upload($value) { $this->setValue($value); } public function setValue($value) { if (null !== $value && is_readable($value)) { $error = UPLOAD_ERR_OK; $size = filesize($value); $info = pathinfo($value); $name = $info['basename']; $tmp = sys_get_temp_dir().'/'.sha1(uniqid(mt_rand(), true)); if (array_key_exists('extension', $info)) { $tmp .= '.'.$info['extension']; } if (is_file($tmp)) { unlink($tmp); } copy($value, $tmp); $value = $tmp; } else { $error = UPLOAD_ERR_NO_FILE; $size = 0; $name = ''; $value = ''; } $this->value = array('name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size); } public function setFilePath($path) { parent::setValue($path); } protected function initialize() { if ('input' !== $this->node->nodeName) { throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); } if ('file' !== strtolower($this->node->getAttribute('type'))) { throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is %s).', $this->node->getAttribute('type'))); } $this->setValue(null); } } node = $node; $this->name = $node->getAttribute('name'); $this->xpath = new \DOMXPath($node->ownerDocument); $this->initialize(); } public function getLabel() { $xpath = new \DOMXPath($this->node->ownerDocument); if ($this->node->hasAttribute('id')) { $labels = $xpath->query(sprintf('descendant::label[@for="%s"]', $this->node->getAttribute('id'))); if ($labels->length > 0) { return $labels->item(0); } } $labels = $xpath->query('ancestor::label[1]', $this->node); if ($labels->length > 0) { return $labels->item(0); } return; } public function getName() { return $this->name; } public function getValue() { return $this->value; } public function setValue($value) { $this->value = (string) $value; } public function hasValue() { return true; } public function isDisabled() { return $this->node->hasAttribute('disabled'); } abstract protected function initialize(); } uri = $currentUri; $this->baseHref = $baseHref ?: $currentUri; $this->add($node); } public function getUri() { return $this->uri; } public function getBaseHref() { return $this->baseHref; } public function clear() { $this->nodes = array(); $this->document = null; } public function add($node) { if ($node instanceof \DOMNodeList) { $this->addNodeList($node); } elseif ($node instanceof \DOMNode) { $this->addNode($node); } elseif (is_array($node)) { $this->addNodes($node); } elseif (is_string($node)) { $this->addContent($node); } elseif (null !== $node) { throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', is_object($node) ? get_class($node) : gettype($node))); } } public function addContent($content, $type = null) { if (empty($type)) { $type = 0 === strpos($content, ']+charset *= *["\']?([a-zA-Z\-0-9_:.]+)/i', $content, $matches)) { $charset = $matches[1]; } if (null === $charset) { $charset = 'ISO-8859-1'; } if ('x' === $xmlMatches[1]) { $this->addXmlContent($content, $charset); } else { $this->addHtmlContent($content, $charset); } } public function addHtmlContent($content, $charset = 'UTF-8') { $internalErrors = libxml_use_internal_errors(true); $disableEntities = libxml_disable_entity_loader(true); $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; set_error_handler(function () { throw new \Exception(); }); try { $content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset); } catch (\Exception $e) { } restore_error_handler(); if ('' !== trim($content)) { @$dom->loadHTML($content); } libxml_use_internal_errors($internalErrors); libxml_disable_entity_loader($disableEntities); $this->addDocument($dom); $base = $this->filterRelativeXPath('descendant-or-self::base')->extract(array('href')); $baseHref = current($base); if (count($base) && !empty($baseHref)) { if ($this->baseHref) { $linkNode = $dom->createElement('a'); $linkNode->setAttribute('href', $baseHref); $link = new Link($linkNode, $this->baseHref); $this->baseHref = $link->getUri(); } else { $this->baseHref = $baseHref; } } } public function addXmlContent($content, $charset = 'UTF-8', $options = LIBXML_NONET) { if (!preg_match('/xmlns:/', $content)) { $content = str_replace('xmlns', 'ns', $content); } $internalErrors = libxml_use_internal_errors(true); $disableEntities = libxml_disable_entity_loader(true); $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; if ('' !== trim($content)) { @$dom->loadXML($content, $options); } libxml_use_internal_errors($internalErrors); libxml_disable_entity_loader($disableEntities); $this->addDocument($dom); $this->isHtml = false; } public function addDocument(\DOMDocument $dom) { if ($dom->documentElement) { $this->addNode($dom->documentElement); } } public function addNodeList(\DOMNodeList $nodes) { foreach ($nodes as $node) { if ($node instanceof \DOMNode) { $this->addNode($node); } } } public function addNodes(array $nodes) { foreach ($nodes as $node) { $this->add($node); } } public function addNode(\DOMNode $node) { if ($node instanceof \DOMDocument) { $node = $node->documentElement; } if (null !== $this->document && $this->document !== $node->ownerDocument) { throw new \InvalidArgumentException('Attaching DOM nodes from multiple documents in the same crawler is forbidden.'); } if (null === $this->document) { $this->document = $node->ownerDocument; } if (in_array($node, $this->nodes, true)) { return; } $this->nodes[] = $node; } public function eq($position) { if (isset($this->nodes[$position])) { return $this->createSubCrawler($this->nodes[$position]); } return $this->createSubCrawler(null); } public function each(\Closure $closure) { $data = array(); foreach ($this->nodes as $i => $node) { $data[] = $closure($this->createSubCrawler($node), $i); } return $data; } public function slice($offset = 0, $length = null) { return $this->createSubCrawler(array_slice($this->nodes, $offset, $length)); } public function reduce(\Closure $closure) { $nodes = array(); foreach ($this->nodes as $i => $node) { if (false !== $closure($this->createSubCrawler($node), $i)) { $nodes[] = $node; } } return $this->createSubCrawler($nodes); } public function first() { return $this->eq(0); } public function last() { return $this->eq(count($this->nodes) - 1); } public function siblings() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->createSubCrawler($this->sibling($this->getNode(0)->parentNode->firstChild)); } public function nextAll() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->createSubCrawler($this->sibling($this->getNode(0))); } public function previousAll() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->createSubCrawler($this->sibling($this->getNode(0), 'previousSibling')); } public function parents() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); $nodes = array(); while ($node = $node->parentNode) { if (XML_ELEMENT_NODE === $node->nodeType) { $nodes[] = $node; } } return $this->createSubCrawler($nodes); } public function children() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0)->firstChild; return $this->createSubCrawler($node ? $this->sibling($node) : array()); } public function attr($attribute) { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); return $node->hasAttribute($attribute) ? $node->getAttribute($attribute) : null; } public function nodeName() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->getNode(0)->nodeName; } public function text() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } return $this->getNode(0)->nodeValue; } public function html() { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $html = ''; foreach ($this->getNode(0)->childNodes as $child) { $html .= $child->ownerDocument->saveHTML($child); } return $html; } public function evaluate($xpath) { if (null === $this->document) { throw new \LogicException('Cannot evaluate the expression on an uninitialized crawler.'); } $data = array(); $domxpath = $this->createDOMXPath($this->document, $this->findNamespacePrefixes($xpath)); foreach ($this->nodes as $node) { $data[] = $domxpath->evaluate($xpath, $node); } if (isset($data[0]) && $data[0] instanceof \DOMNodeList) { return $this->createSubCrawler($data); } return $data; } public function extract($attributes) { $attributes = (array) $attributes; $count = count($attributes); $data = array(); foreach ($this->nodes as $node) { $elements = array(); foreach ($attributes as $attribute) { if ('_text' === $attribute) { $elements[] = $node->nodeValue; } else { $elements[] = $node->getAttribute($attribute); } } $data[] = $count > 1 ? $elements : $elements[0]; } return $data; } public function filterXPath($xpath) { $xpath = $this->relativize($xpath); if ('' === $xpath) { return $this->createSubCrawler(null); } return $this->filterRelativeXPath($xpath); } public function filter($selector) { if (!class_exists('Symfony\\Component\\CssSelector\\CssSelectorConverter')) { throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector 2.8+ is not installed (you can use filterXPath instead).'); } $converter = new CssSelectorConverter($this->isHtml); return $this->filterRelativeXPath($converter->toXPath($selector)); } public function selectLink($value) { $xpath = sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) ', static::xpathLiteral(' '.$value.' ')). sprintf('or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]]', static::xpathLiteral(' '.$value.' ')); return $this->filterRelativeXPath($xpath); } public function selectImage($value) { $xpath = sprintf('descendant-or-self::img[contains(normalize-space(string(@alt)), %s)]', static::xpathLiteral($value)); return $this->filterRelativeXPath($xpath); } public function selectButton($value) { $translate = 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")'; $xpath = sprintf('descendant-or-self::input[((contains(%s, "submit") or contains(%s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', $translate, $translate, static::xpathLiteral(' '.$value.' ')). sprintf('or (contains(%s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id=%s or @name=%s] ', $translate, static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)). sprintf('| descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id=%s or @name=%s]', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)); return $this->filterRelativeXPath($xpath); } public function link($method = 'get') { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_class($node))); } return new Link($node, $this->baseHref, $method); } public function links() { $links = array(); foreach ($this->nodes as $node) { if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The current node list should contain only DOMElement instances, "%s" found.', get_class($node))); } $links[] = new Link($node, $this->baseHref, 'get'); } return $links; } public function image() { if (!count($this)) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_class($node))); } return new Image($node, $this->baseHref); } public function images() { $images = array(); foreach ($this as $node) { if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The current node list should contain only DOMElement instances, "%s" found.', get_class($node))); } $images[] = new Image($node, $this->baseHref); } return $images; } public function form(array $values = null, $method = null) { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); if (!$node instanceof \DOMElement) { throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_class($node))); } $form = new Form($node, $this->uri, $method, $this->baseHref); if (null !== $values) { $form->setValues($values); } return $form; } public function setDefaultNamespacePrefix($prefix) { $this->defaultNamespacePrefix = $prefix; } public function registerNamespace($prefix, $namespace) { $this->namespaces[$prefix] = $namespace; } public static function xpathLiteral($s) { if (false === strpos($s, "'")) { return sprintf("'%s'", $s); } if (false === strpos($s, '"')) { return sprintf('"%s"', $s); } $string = $s; $parts = array(); while (true) { if (false !== $pos = strpos($string, "'")) { $parts[] = sprintf("'%s'", substr($string, 0, $pos)); $parts[] = "\"'\""; $string = substr($string, $pos + 1); } else { $parts[] = "'$string'"; break; } } return sprintf('concat(%s)', implode(', ', $parts)); } private function filterRelativeXPath($xpath) { $prefixes = $this->findNamespacePrefixes($xpath); $crawler = $this->createSubCrawler(null); foreach ($this->nodes as $node) { $domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes); $crawler->add($domxpath->query($xpath, $node)); } return $crawler; } private function relativize($xpath) { $expressions = array(); $nonMatchingExpression = 'a[name() = "b"]'; $xpathLen = strlen($xpath); $openedBrackets = 0; $startPosition = strspn($xpath, " \t\n\r\0\x0B"); for ($i = $startPosition; $i <= $xpathLen; ++$i) { $i += strcspn($xpath, '"\'[]|', $i); if ($i < $xpathLen) { switch ($xpath[$i]) { case '"': case "'": if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) { return $xpath; } continue 2; case '[': ++$openedBrackets; continue 2; case ']': --$openedBrackets; continue 2; } } if ($openedBrackets) { continue; } if ($startPosition < $xpathLen && '(' === $xpath[$startPosition]) { $j = 1 + strspn($xpath, "( \t\n\r\0\x0B", $startPosition + 1); $parenthesis = substr($xpath, $startPosition, $j); $startPosition += $j; } else { $parenthesis = ''; } $expression = rtrim(substr($xpath, $startPosition, $i - $startPosition)); if (0 === strpos($expression, 'self::*/')) { $expression = './'.substr($expression, 8); } if ('' === $expression) { $expression = $nonMatchingExpression; } elseif (0 === strpos($expression, '//')) { $expression = 'descendant-or-self::'.substr($expression, 2); } elseif (0 === strpos($expression, './/')) { $expression = 'descendant-or-self::'.substr($expression, 3); } elseif (0 === strpos($expression, './')) { $expression = 'self::'.substr($expression, 2); } elseif (0 === strpos($expression, 'child::')) { $expression = 'self::'.substr($expression, 7); } elseif ('/' === $expression[0] || '.' === $expression[0] || 0 === strpos($expression, 'self::')) { $expression = $nonMatchingExpression; } elseif (0 === strpos($expression, 'descendant::')) { $expression = 'descendant-or-self::'.substr($expression, 12); } elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) { $expression = $nonMatchingExpression; } elseif (0 !== strpos($expression, 'descendant-or-self::')) { $expression = 'self::'.$expression; } $expressions[] = $parenthesis.$expression; if ($i === $xpathLen) { return implode(' | ', $expressions); } $i += strspn($xpath, " \t\n\r\0\x0B", $i + 1); $startPosition = $i + 1; } return $xpath; } public function getNode($position) { if (isset($this->nodes[$position])) { return $this->nodes[$position]; } } public function count() { return count($this->nodes); } public function getIterator() { return new \ArrayIterator($this->nodes); } protected function sibling($node, $siblingDir = 'nextSibling') { $nodes = array(); do { if ($node !== $this->getNode(0) && $node->nodeType === 1) { $nodes[] = $node; } } while ($node = $node->$siblingDir); return $nodes; } private function createDOMXPath(\DOMDocument $document, array $prefixes = array()) { $domxpath = new \DOMXPath($document); foreach ($prefixes as $prefix) { $namespace = $this->discoverNamespace($domxpath, $prefix); if (null !== $namespace) { $domxpath->registerNamespace($prefix, $namespace); } } return $domxpath; } private function discoverNamespace(\DOMXPath $domxpath, $prefix) { if (isset($this->namespaces[$prefix])) { return $this->namespaces[$prefix]; } $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); if ($node = $namespaces->item(0)) { return $node->nodeValue; } } private function findNamespacePrefixes($xpath) { if (preg_match_all('/(?P[a-z_][a-z_0-9\-\.]*+):[^"\/:]/i', $xpath, $matches)) { return array_unique($matches['prefix']); } return array(); } private function createSubCrawler($nodes) { $crawler = new static($nodes, $this->uri, $this->baseHref); $crawler->isHtml = $this->isHtml; $crawler->document = $this->document; $crawler->namespaces = $this->namespaces; return $crawler; } } node->getAttribute('src'); } protected function setNode(\DOMElement $node) { if ('img' !== $node->nodeName) { throw new \LogicException(sprintf('Unable to visualize a "%s" tag.', $node->nodeName)); } $this->node = $node; } } getSegments($field->getName()); $target = &$this->fields; while ($segments) { if (!is_array($target)) { $target = array(); } $path = array_shift($segments); if ('' === $path) { $target = &$target[]; } else { $target = &$target[$path]; } } $target = $field; } public function remove($name) { $segments = $this->getSegments($name); $target = &$this->fields; while (count($segments) > 1) { $path = array_shift($segments); if (!array_key_exists($path, $target)) { return; } $target = &$target[$path]; } unset($target[array_shift($segments)]); } public function &get($name) { $segments = $this->getSegments($name); $target = &$this->fields; while ($segments) { $path = array_shift($segments); if (!array_key_exists($path, $target)) { throw new \InvalidArgumentException(sprintf('Unreachable field "%s"', $path)); } $target = &$target[$path]; } return $target; } public function has($name) { try { $this->get($name); return true; } catch (\InvalidArgumentException $e) { return false; } } public function set($name, $value) { $target = &$this->get($name); if ((!is_array($value) && $target instanceof Field\FormField) || $target instanceof Field\ChoiceFormField) { $target->setValue($value); } elseif (is_array($value)) { $fields = self::create($name, $value); foreach ($fields->all() as $k => $v) { $this->set($k, $v); } } else { throw new \InvalidArgumentException(sprintf('Cannot set value on a compound field "%s".', $name)); } } public function all() { return $this->walk($this->fields, $this->base); } private static function create($base, array $values) { $registry = new static(); $registry->base = $base; $registry->fields = $values; return $registry; } private function walk(array $array, $base = '', array &$output = array()) { foreach ($array as $k => $v) { $path = empty($base) ? $k : sprintf('%s[%s]', $base, $k); if (is_array($v)) { $this->walk($v, $path, $output); } else { $output[$path] = $v; } } return $output; } private function getSegments($name) { if (preg_match('/^(?P[^[]+)(?P(\[.*)|$)/', $name, $m)) { $segments = array($m['base']); while (!empty($m['extra'])) { $extra = $m['extra']; if (preg_match('/^\[(?P.*?)\](?P.*)$/', $extra, $m)) { $segments[] = $m['segment']; } else { $segments[] = $extra; } } return $segments; } return array($name); } } unescapeCharacter($match[0]); }; return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); } private function unescapeCharacter($value) { switch ($value[1]) { case '0': return "\x0"; case 'a': return "\x7"; case 'b': return "\x8"; case 't': return "\t"; case "\t": return "\t"; case 'n': return "\n"; case 'v': return "\xB"; case 'f': return "\xC"; case 'r': return "\r"; case 'e': return "\x1B"; case ' ': return ' '; case '"': return '"'; case '/': return '/'; case '\\': return '\\'; case 'N': return "\xC2\x85"; case '_': return "\xC2\xA0"; case 'L': return "\xE2\x80\xA8"; case 'P': return "\xE2\x80\xA9"; case 'x': return self::utf8chr(hexdec(substr($value, 2, 2))); case 'u': return self::utf8chr(hexdec(substr($value, 2, 4))); case 'U': return self::utf8chr(hexdec(substr($value, 2, 8))); default: throw new ParseException(sprintf('Found unknown escape character "%s".', $value)); } } private static function utf8chr($c) { if (0x80 > $c %= 0x200000) { return chr($c); } if (0x800 > $c) { return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); } if (0x10000 > $c) { return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); } return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); } } = ! % @ ` ]/x', $value); } public static function escapeWithSingleQuotes($value) { return sprintf("'%s'", str_replace('\'', '\'\'', $value)); } } ![\w!.\/:-]+) +)?'; const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; private $offset = 0; private $totalNumberOfLines; private $lines = array(); private $currentLineNb = -1; private $currentLine = ''; private $refs = array(); private $skippedLineNumbers = array(); private $locallySkippedLineNumbers = array(); public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array()) { $this->offset = $offset; $this->totalNumberOfLines = $totalNumberOfLines; $this->skippedLineNumbers = $skippedLineNumbers; } public function parse($value, $flags = 0) { if (is_bool($flags)) { @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED); if ($flags) { $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE; } else { $flags = 0; } } if (func_num_args() >= 3) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED); if (func_get_arg(2)) { $flags |= Yaml::PARSE_OBJECT; } } if (func_num_args() >= 4) { @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED); if (func_get_arg(3)) { $flags |= Yaml::PARSE_OBJECT_FOR_MAP; } } if (false === preg_match('//u', $value)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.'); } $this->refs = array(); $mbEncoding = null; $e = null; $data = null; if (2 & (int) ini_get('mbstring.func_overload')) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('UTF-8'); } try { $data = $this->doParse($value, $flags); } catch (\Exception $e) { } catch (\Throwable $e) { } if (null !== $mbEncoding) { mb_internal_encoding($mbEncoding); } $this->lines = array(); $this->currentLine = ''; $this->refs = array(); $this->skippedLineNumbers = array(); $this->locallySkippedLineNumbers = array(); if (null !== $e) { throw $e; } return $data; } private function doParse($value, $flags) { $this->currentLineNb = -1; $this->currentLine = ''; $value = $this->cleanup($value); $this->lines = explode("\n", $value); $this->locallySkippedLineNumbers = array(); if (null === $this->totalNumberOfLines) { $this->totalNumberOfLines = count($this->lines); } $data = array(); $context = null; $allowOverwrite = false; while ($this->moveToNextLine()) { if ($this->isCurrentLineEmpty()) { continue; } if ("\t" === $this->currentLine[0]) { throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } $isRef = $mergeNode = false; if (self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { if ($context && 'mapping' == $context) { throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine); } $context = 'sequence'; if (isset($values['value']) && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags); } else { if (isset($values['leadspaces']) && self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+))?$#u', rtrim($values['value']), $matches) ) { $block = $values['value']; if ($this->isNextLineIndented()) { $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1); } $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); } else { $data[] = $this->parseValue($values['value'], $flags, $context); } } if ($isRef) { $this->refs[$isRef] = end($data); } } elseif ( self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+))?$#u', rtrim($this->currentLine), $values) && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'"))) ) { if ($context && 'sequence' == $context) { throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine); } $context = 'mapping'; Inline::parse(null, $flags, $this->refs); try { Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } if (is_float($key)) { $key = (string) $key; } if ('<<' === $key) { $mergeNode = true; $allowOverwrite = true; if (isset($values['value']) && 0 === strpos($values['value'], '*')) { $refName = substr($values['value'], 1); if (!array_key_exists($refName, $this->refs)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine); } $refValue = $this->refs[$refName]; if (!is_array($refValue)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } $data += $refValue; } else { if (isset($values['value']) && $values['value'] !== '') { $value = $values['value']; } else { $value = $this->getNextEmbedBlock(); } $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); if (!is_array($parsed)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } if (isset($parsed[0])) { foreach ($parsed as $parsedItem) { if (!is_array($parsedItem)) { throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); } $data += $parsedItem; } } else { $data += $parsed; } } } elseif (isset($values['value']) && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } if ($mergeNode) { } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { if ($allowOverwrite || !isset($data[$key])) { $data[$key] = null; } else { @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); } } else { $realCurrentLineNbKey = $this->getRealCurrentLineNb(); $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); if ($allowOverwrite || !isset($data[$key])) { $data[$key] = $value; } else { @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, $realCurrentLineNbKey + 1), E_USER_DEPRECATED); } } } else { $value = $this->parseValue($values['value'], $flags, $context); if ($allowOverwrite || !isset($data[$key])) { $data[$key] = $value; } else { @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); } } if ($isRef) { $this->refs[$isRef] = $data[$key]; } } else { if ('---' === $this->currentLine) { throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine); } if (is_string($value) && $this->lines[0] === trim($value)) { try { Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); $value = Inline::parse($this->lines[0], $flags, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } return $value; } throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } } if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !is_object($data) && 'mapping' === $context) { $object = new \stdClass(); foreach ($data as $key => $value) { $object->$key = $value; } $data = $object; } return empty($data) ? null : $data; } private function parseBlock($offset, $yaml, $flags) { $skippedLineNumbers = $this->skippedLineNumbers; foreach ($this->locallySkippedLineNumbers as $lineNumber) { if ($lineNumber < $offset) { continue; } $skippedLineNumbers[] = $lineNumber; } $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers); $parser->refs = &$this->refs; return $parser->doParse($yaml, $flags); } private function getRealCurrentLineNb() { $realCurrentLineNumber = $this->currentLineNb + $this->offset; foreach ($this->skippedLineNumbers as $skippedLineNumber) { if ($skippedLineNumber > $realCurrentLineNumber) { break; } ++$realCurrentLineNumber; } return $realCurrentLineNumber; } private function getCurrentLineIndentation() { return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); } private function getNextEmbedBlock($indentation = null, $inSequence = false) { $oldLineIndentation = $this->getCurrentLineIndentation(); $blockScalarIndentations = array(); if ($this->isBlockScalarHeader()) { $blockScalarIndentations[] = $this->getCurrentLineIndentation(); } if (!$this->moveToNextLine()) { return; } if (null === $indentation) { $newIndent = $this->getCurrentLineIndentation(); $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } } else { $newIndent = $indentation; } $data = array(); if ($this->getCurrentLineIndentation() >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } else { $this->moveToPreviousLine(); return; } if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { $this->moveToPreviousLine(); return; } $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) { $blockScalarIndentations[] = $this->getCurrentLineIndentation(); } $previousLineIndentation = $this->getCurrentLineIndentation(); while ($this->moveToNextLine()) { $indent = $this->getCurrentLineIndentation(); if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && trim($this->currentLine) !== '') { foreach ($blockScalarIndentations as $key => $blockScalarIndentation) { if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) { unset($blockScalarIndentations[$key]); } } } if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) { $blockScalarIndentations[] = $this->getCurrentLineIndentation(); } $previousLineIndentation = $indent; if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { $this->moveToPreviousLine(); break; } if ($this->isCurrentLineBlank()) { $data[] = substr($this->currentLine, $newIndent); continue; } if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) { $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb(); continue; } if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } elseif (0 == $indent) { $this->moveToPreviousLine(); break; } else { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } } return implode("\n", $data); } private function moveToNextLine() { if ($this->currentLineNb >= count($this->lines) - 1) { return false; } $this->currentLine = $this->lines[++$this->currentLineNb]; return true; } private function moveToPreviousLine() { if ($this->currentLineNb < 1) { return false; } $this->currentLine = $this->lines[--$this->currentLineNb]; return true; } private function parseValue($value, $flags, $context) { if (0 === strpos($value, '*')) { if (false !== $pos = strpos($value, '#')) { $value = substr($value, 1, $pos - 2); } else { $value = substr($value, 1); } if (!array_key_exists($value, $this->refs)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine); } return $this->refs[$value]; } if (self::preg_match('/^'.self::TAG_PATTERN.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); if (isset($matches['tag']) && '!!binary' === $matches['tag']) { return Inline::evaluateBinaryScalar($data); } return $data; } try { $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null; if (null !== $quotation && preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) { return Inline::parse($value, $flags, $this->refs); } while ($this->moveToNextLine()) { if (null === $quotation && $this->getCurrentLineIndentation() === 0) { $this->moveToPreviousLine(); break; } $value .= ' '.trim($this->currentLine); if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) { break; } } Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); $parsedValue = Inline::parse($value, $flags, $this->refs); if ('mapping' === $context && is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { throw new ParseException('A colon cannot be used in an unquoted mapping value.'); } return $parsedValue; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } private function parseBlockScalar($style, $chomping = '', $indentation = 0) { $notEOF = $this->moveToNextLine(); if (!$notEOF) { return ''; } $isCurrentLineBlank = $this->isCurrentLineBlank(); $blockLines = array(); while ($notEOF && $isCurrentLineBlank) { if ($notEOF = $this->moveToNextLine()) { $blockLines[] = ''; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } if (0 === $indentation) { if (self::preg_match('/^ +/', $this->currentLine, $matches)) { $indentation = strlen($matches[0]); } } if ($indentation > 0) { $pattern = sprintf('/^ {%d}(.*)$/', $indentation); while ( $notEOF && ( $isCurrentLineBlank || self::preg_match($pattern, $this->currentLine, $matches) ) ) { if ($isCurrentLineBlank && strlen($this->currentLine) > $indentation) { $blockLines[] = substr($this->currentLine, $indentation); } elseif ($isCurrentLineBlank) { $blockLines[] = ''; } else { $blockLines[] = $matches[1]; } if ($notEOF = $this->moveToNextLine()) { $isCurrentLineBlank = $this->isCurrentLineBlank(); } } } elseif ($notEOF) { $blockLines[] = ''; } if ($notEOF) { $blockLines[] = ''; $this->moveToPreviousLine(); } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { $blockLines[] = ''; } if ('>' === $style) { $text = ''; $previousLineIndented = false; $previousLineBlank = false; for ($i = 0, $blockLinesCount = count($blockLines); $i < $blockLinesCount; ++$i) { if ('' === $blockLines[$i]) { $text .= "\n"; $previousLineIndented = false; $previousLineBlank = true; } elseif (' ' === $blockLines[$i][0]) { $text .= "\n".$blockLines[$i]; $previousLineIndented = true; $previousLineBlank = false; } elseif ($previousLineIndented) { $text .= "\n".$blockLines[$i]; $previousLineIndented = false; $previousLineBlank = false; } elseif ($previousLineBlank || 0 === $i) { $text .= $blockLines[$i]; $previousLineIndented = false; $previousLineBlank = false; } else { $text .= ' '.$blockLines[$i]; $previousLineIndented = false; $previousLineBlank = false; } } } else { $text = implode("\n", $blockLines); } if ('' === $chomping) { $text = preg_replace('/\n+$/', "\n", $text); } elseif ('-' === $chomping) { $text = preg_replace('/\n+$/', '', $text); } return $text; } private function isNextLineIndented() { $currentIndentation = $this->getCurrentLineIndentation(); $EOF = !$this->moveToNextLine(); while (!$EOF && $this->isCurrentLineEmpty()) { $EOF = !$this->moveToNextLine(); } if ($EOF) { return false; } $ret = $this->getCurrentLineIndentation() > $currentIndentation; $this->moveToPreviousLine(); return $ret; } private function isCurrentLineEmpty() { return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); } private function isCurrentLineBlank() { return '' == trim($this->currentLine, ' '); } private function isCurrentLineComment() { $ltrimmedLine = ltrim($this->currentLine, ' '); return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#'; } private function isCurrentLineLastLineInDocument() { return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); } private function cleanup($value) { $value = str_replace(array("\r\n", "\r"), "\n", $value); $count = 0; $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); $this->offset += $count; $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); if ($count == 1) { $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; } $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); if ($count == 1) { $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; $value = preg_replace('#\.\.\.\s*$#', '', $value); } return $value; } private function isNextLineUnIndentedCollection() { $currentIndentation = $this->getCurrentLineIndentation(); $notEOF = $this->moveToNextLine(); while ($notEOF && $this->isCurrentLineEmpty()) { $notEOF = $this->moveToNextLine(); } if (false === $notEOF) { return false; } $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); $this->moveToPreviousLine(); return $ret; } private function isStringUnIndentedCollectionItem() { return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); } private function isBlockScalarHeader() { return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine); } public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) { if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { switch (preg_last_error()) { case PREG_INTERNAL_ERROR: $error = 'Internal PCRE error.'; break; case PREG_BACKTRACK_LIMIT_ERROR: $error = 'pcre.backtrack_limit reached.'; break; case PREG_RECURSION_LIMIT_ERROR: $error = 'pcre.recursion_limit reached.'; break; case PREG_BAD_UTF8_ERROR: $error = 'Malformed UTF-8 data.'; break; case PREG_BAD_UTF8_OFFSET_ERROR: $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; break; default: $error = 'Error.'; } throw new ParseException($error); } return $ret; } } = 3 && !is_array($references)) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED); if ($references) { $flags |= Yaml::PARSE_OBJECT; } if (func_num_args() >= 4) { @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED); if (func_get_arg(3)) { $flags |= Yaml::PARSE_OBJECT_FOR_MAP; } } if (func_num_args() >= 5) { $references = func_get_arg(4); } else { $references = array(); } } self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); $value = trim($value); if ('' === $value) { return ''; } if (2 & (int) ini_get('mbstring.func_overload')) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('ASCII'); } $i = 0; switch ($value[0]) { case '[': $result = self::parseSequence($value, $flags, $i, $references); ++$i; break; case '{': $result = self::parseMapping($value, $flags, $i, $references); ++$i; break; default: $result = self::parseScalar($value, $flags, null, array('"', "'"), $i, true, $references); } if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); } if (isset($mbEncoding)) { mb_internal_encoding($mbEncoding); } return $result; } public static function dump($value, $flags = 0) { if (is_bool($flags)) { @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED); if ($flags) { $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE; } else { $flags = 0; } } if (func_num_args() >= 3) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED); if (func_get_arg(2)) { $flags |= Yaml::DUMP_OBJECT; } } switch (true) { case is_resource($value): if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; case $value instanceof \DateTimeInterface: return $value->format('c'); case is_object($value): if (Yaml::DUMP_OBJECT & $flags) { return '!php/object:'.serialize($value); } if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { return self::dumpArray((array) $value, $flags); } if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; case is_array($value): return self::dumpArray($value, $flags); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'$value'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } if (is_float($value)) { $repr = (string) $value; if (is_infinite($value)) { $repr = str_ireplace('INF', '.Inf', $repr); } elseif (floor($value) == $value && $repr == $value) { $repr = '!!float '.$repr; } } else { $repr = is_string($value) ? "'$value'" : (string) $value; } if (false !== $locale) { setlocale(LC_NUMERIC, $locale); } return $repr; case '' == $value: return "''"; case self::isBinaryString($value): return '!!binary '.base64_encode($value); case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): case Parser::preg_match(self::getHexRegex(), $value): case Parser::preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); default: return $value; } } public static function isHash(array $value) { $expectedKey = 0; foreach ($value as $key => $val) { if ($key !== $expectedKey++) { return true; } } return false; } private static function dumpArray($value, $flags) { if ($value && !self::isHash($value)) { $output = array(); foreach ($value as $val) { $output[] = self::dump($val, $flags); } return sprintf('[%s]', implode(', ', $output)); } $output = array(); foreach ($value as $key => $val) { $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } return sprintf('{ %s }', implode(', ', $output)); } public static function parseScalar($scalar, $flags = 0, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) { if (in_array($scalar[$i], $stringDelimiters)) { $output = self::parseQuotedScalar($scalar, $i); if (null !== $delimiters) { $tmp = ltrim(substr($scalar, $i), ' '); if (!in_array($tmp[0], $delimiters)) { throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); } } } else { if (!$delimiters) { $output = substr($scalar, $i); $i += strlen($output); if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) { $output = substr($output, 0, $match[0][1]); } } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; $i += strlen($output); } else { throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar)); } if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) { throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0])); } if ($output && '%' === $output[0]) { @trigger_error(sprintf('Not quoting the scalar "%s" starting with the "%%" indicator character is deprecated since Symfony 3.1 and will throw a ParseException in 4.0.', $output), E_USER_DEPRECATED); } if ($evaluate) { $output = self::evaluateScalar($output, $flags, $references); } } return $output; } private static function parseQuotedScalar($scalar, &$i) { if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i))); } $output = substr($match[0], 1, strlen($match[0]) - 2); $unescaper = new Unescaper(); if ('"' == $scalar[$i]) { $output = $unescaper->unescapeDoubleQuotedString($output); } else { $output = $unescaper->unescapeSingleQuotedString($output); } $i += strlen($match[0]); return $output; } private static function parseSequence($sequence, $flags, &$i = 0, $references = array()) { $output = array(); $len = strlen($sequence); ++$i; while ($i < $len) { switch ($sequence[$i]) { case '[': $output[] = self::parseSequence($sequence, $flags, $i, $references); break; case '{': $output[] = self::parseMapping($sequence, $flags, $i, $references); break; case ']': return $output; case ',': case ' ': break; default: $isQuoted = in_array($sequence[$i], array('"', "'")); $value = self::parseScalar($sequence, $flags, array(',', ']'), array('"', "'"), $i, true, $references); if (is_string($value) && !$isQuoted && false !== strpos($value, ': ')) { try { $pos = 0; $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); } catch (\InvalidArgumentException $e) { } } $output[] = $value; --$i; } ++$i; } throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence)); } private static function parseMapping($mapping, $flags, &$i = 0, $references = array()) { $output = array(); $len = strlen($mapping); ++$i; while ($i < $len) { switch ($mapping[$i]) { case ' ': case ',': ++$i; continue 2; case '}': if (self::$objectForMap) { return (object) $output; } return $output; } $key = self::parseScalar($mapping, $flags, array(':', ' '), array('"', "'"), $i, false); if (':' !== $key && false === $i = strpos($mapping, ':', $i)) { break; } if (':' !== $key && (!isset($mapping[$i + 1]) || !in_array($mapping[$i + 1], array(' ', ',', '[', ']', '{', '}'), true))) { @trigger_error('Using a colon that is not followed by an indication character (i.e. " ", ",", "[", "]", "{", "}" is deprecated since version 3.2 and will throw a ParseException in 4.0.', E_USER_DEPRECATED); } $done = false; while ($i < $len) { switch ($mapping[$i]) { case '[': $value = self::parseSequence($mapping, $flags, $i, $references); if (!isset($output[$key])) { $output[$key] = $value; } else { @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED); } $done = true; break; case '{': $value = self::parseMapping($mapping, $flags, $i, $references); if (!isset($output[$key])) { $output[$key] = $value; } else { @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED); } $done = true; break; case ':': case ' ': break; default: $value = self::parseScalar($mapping, $flags, array(',', '}'), array('"', "'"), $i, true, $references); if (!isset($output[$key])) { $output[$key] = $value; } else { @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED); } $done = true; --$i; } ++$i; if ($done) { continue 2; } } } throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping)); } private static function evaluateScalar($scalar, $flags, $references = array()) { $scalar = trim($scalar); $scalarLower = strtolower($scalar); if (0 === strpos($scalar, '*')) { if (false !== $pos = strpos($scalar, '#')) { $value = substr($scalar, 1, $pos - 2); } else { $value = substr($scalar, 1); } if (false === $value || '' === $value) { throw new ParseException('A reference must contain at least one character.'); } if (!array_key_exists($value, $references)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); } return $references[$value]; } switch (true) { case 'null' === $scalarLower: case '' === $scalar: case '~' === $scalar: return; case 'true' === $scalarLower: return true; case 'false' === $scalarLower: return false; case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]): switch (true) { case 0 === strpos($scalar, '!str'): return (string) substr($scalar, 5); case 0 === strpos($scalar, '! '): return (int) self::parseScalar(substr($scalar, 2), $flags); case 0 === strpos($scalar, '!php/object:'): if (self::$objectSupport) { return unserialize(substr($scalar, 12)); } if (self::$exceptionOnInvalidType) { throw new ParseException('Object support when parsing a YAML file has been disabled.'); } return; case 0 === strpos($scalar, '!!php/object:'): if (self::$objectSupport) { @trigger_error('The !!php/object tag to indicate dumped PHP objects is deprecated since version 3.1 and will be removed in 4.0. Use the !php/object tag instead.', E_USER_DEPRECATED); return unserialize(substr($scalar, 13)); } if (self::$exceptionOnInvalidType) { throw new ParseException('Object support when parsing a YAML file has been disabled.'); } return; case 0 === strpos($scalar, '!php/const:'): if (self::$constantSupport) { if (defined($const = substr($scalar, 11))) { return constant($const); } throw new ParseException(sprintf('The constant "%s" is not defined.', $const)); } if (self::$exceptionOnInvalidType) { throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar)); } return; case 0 === strpos($scalar, '!!float '): return (float) substr($scalar, 8); case Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar): $scalar = str_replace('_', '', (string) $scalar); case ctype_digit($scalar): $raw = $scalar; $cast = (int) $scalar; return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): $raw = $scalar; $cast = (int) $scalar; return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); case is_numeric($scalar): case Parser::preg_match(self::getHexRegex(), $scalar): $scalar = str_replace('_', '', $scalar); return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; case '.inf' === $scalarLower: case '.nan' === $scalarLower: return -log(0); case '-.inf' === $scalarLower: return log(0); case 0 === strpos($scalar, '!!binary '): return self::evaluateBinaryScalar(substr($scalar, 9)); case Parser::preg_match('/^(-|\+)?[0-9][0-9,]*(\.[0-9_]+)?$/', $scalar): case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): if (false !== strpos($scalar, ',')) { @trigger_error('Using the comma as a group separator for floats is deprecated since version 3.2 and will be removed in 4.0.', E_USER_DEPRECATED); } return (float) str_replace(array(',', '_'), '', $scalar); case Parser::preg_match(self::getTimestampRegex(), $scalar): if (Yaml::PARSE_DATETIME & $flags) { return new \DateTime($scalar, new \DateTimeZone('UTC')); } $timeZone = date_default_timezone_get(); date_default_timezone_set('UTC'); $time = strtotime($scalar); date_default_timezone_set($timeZone); return $time; } default: return (string) $scalar; } } public static function evaluateBinaryScalar($scalar) { $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); if (0 !== (strlen($parsedBinaryData) % 4)) { throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', strlen($parsedBinaryData))); } if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData)); } return base64_decode($parsedBinaryData, true); } private static function isBinaryString($value) { return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); } private static function getTimestampRegex() { return <<[0-9][0-9][0-9][0-9]) -(?P[0-9][0-9]?) -(?P[0-9][0-9]?) (?:(?:[Tt]|[ \t]+) (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) (?:\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)? $~x EOF; } private static function getHexRegex() { return '~^0x[0-9a-f_]++$~i'; } } indentation = $indentation; } public function setIndentation($num) { @trigger_error('The '.__METHOD__.' method is deprecated since version 3.1 and will be removed in 4.0. Pass the indentation to the constructor instead.', E_USER_DEPRECATED); $this->indentation = (int) $num; } public function dump($input, $inline = 0, $indent = 0, $flags = 0) { if (is_bool($flags)) { @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED); if ($flags) { $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE; } else { $flags = 0; } } if (func_num_args() >= 5) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED); if (func_get_arg(4)) { $flags |= Yaml::DUMP_OBJECT; } } $output = ''; $prefix = $indent ? str_repeat(' ', $indent) : ''; if ($inline <= 0 || !is_array($input) || empty($input)) { $output .= $prefix.Inline::dump($input, $flags); } else { $isAHash = Inline::isHash($input); foreach ($input as $key => $value) { if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && is_string($value) && false !== strpos($value, "\n")) { $output .= sprintf("%s%s%s |\n", $prefix, $isAHash ? Inline::dump($key, $flags).':' : '-', ''); foreach (preg_split('/\n|\r\n/', $value) as $row) { $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); } continue; } $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); $output .= sprintf('%s%s%s%s', $prefix, $isAHash ? Inline::dump($key, $flags).':' : '-', $willBeInlined ? ' ' : "\n", $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) ).($willBeInlined ? "\n" : ''); } } return $output; } } parsedFile = $parsedFile; $this->parsedLine = $parsedLine; $this->snippet = $snippet; $this->rawMessage = $message; $this->updateRepr(); parent::__construct($this->message, 0, $previous); } public function getSnippet() { return $this->snippet; } public function setSnippet($snippet) { $this->snippet = $snippet; $this->updateRepr(); } public function getParsedFile() { return $this->parsedFile; } public function setParsedFile($parsedFile) { $this->parsedFile = $parsedFile; $this->updateRepr(); } public function getParsedLine() { return $this->parsedLine; } public function setParsedLine($parsedLine) { $this->parsedLine = $parsedLine; $this->updateRepr(); } private function updateRepr() { $this->message = $this->rawMessage; $dot = false; if ('.' === substr($this->message, -1)) { $this->message = substr($this->message, 0, -1); $dot = true; } if (null !== $this->parsedFile) { $this->message .= sprintf(' in %s', json_encode($this->parsedFile, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } if ($this->parsedLine >= 0) { $this->message .= sprintf(' at line %d', $this->parsedLine); } if ($this->snippet) { $this->message .= sprintf(' (near "%s")', $this->snippet); } if ($dot) { $this->message .= '.'; } } } directoryIteratorProvider = $directoryIteratorProvider; $this->isReadableProvider = $isReadableProvider; } protected function configure() { $this ->setName('lint:yaml') ->setDescription('Lints a file and outputs encountered errors') ->addArgument('filename', null, 'A file or a directory or STDIN') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT the first encountered syntax error. You can validates YAML contents passed from STDIN: cat filename | php %command.full_name% You can also validate the syntax of a file: php %command.full_name% filename Or of a whole directory: php %command.full_name% dirname php %command.full_name% dirname --format=json EOF ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); $filename = $input->getArgument('filename'); $this->format = $input->getOption('format'); $this->displayCorrectFiles = $output->isVerbose(); if (!$filename) { if (!$stdin = $this->getStdin()) { throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.'); } return $this->display($io, array($this->validate($stdin))); } if (!$this->isReadable($filename)) { throw new \RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); } $filesInfo = array(); foreach ($this->getFiles($filename) as $file) { $filesInfo[] = $this->validate(file_get_contents($file), $file); } return $this->display($io, $filesInfo); } private function validate($content, $file = null) { try { $this->getParser()->parse($content); } catch (ParseException $e) { return array('file' => $file, 'valid' => false, 'message' => $e->getMessage()); } return array('file' => $file, 'valid' => true); } private function display(SymfonyStyle $io, array $files) { switch ($this->format) { case 'txt': return $this->displayTxt($io, $files); case 'json': return $this->displayJson($io, $files); default: throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); } } private function displayTxt(SymfonyStyle $io, array $filesInfo) { $countFiles = count($filesInfo); $erroredFiles = 0; foreach ($filesInfo as $info) { if ($info['valid'] && $this->displayCorrectFiles) { $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$erroredFiles; $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); $io->text(sprintf(' >> %s', $info['message'])); } } if ($erroredFiles === 0) { $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); } else { $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); } return min($erroredFiles, 1); } private function displayJson(SymfonyStyle $io, array $filesInfo) { $errors = 0; array_walk($filesInfo, function (&$v) use (&$errors) { $v['file'] = (string) $v['file']; if (!$v['valid']) { ++$errors; } }); $io->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); return min($errors, 1); } private function getFiles($fileOrDirectory) { if (is_file($fileOrDirectory)) { yield new \SplFileInfo($fileOrDirectory); return; } foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { if (!in_array($file->getExtension(), array('yml', 'yaml'))) { continue; } yield $file; } } private function getStdin() { if (0 !== ftell(STDIN)) { return; } $inputs = ''; while (!feof(STDIN)) { $inputs .= fread(STDIN, 1024); } return $inputs; } private function getParser() { if (!$this->parser) { $this->parser = new Parser(); } return $this->parser; } private function getDirectoryIterator($directory) { $default = function ($directory) { return new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), \RecursiveIteratorIterator::LEAVES_ONLY ); }; if (null !== $this->directoryIteratorProvider) { return call_user_func($this->directoryIteratorProvider, $directory, $default); } return $default($directory); } private function isReadable($fileOrDirectory) { $default = function ($fileOrDirectory) { return is_readable($fileOrDirectory); }; if (null !== $this->isReadableProvider) { return call_user_func($this->isReadableProvider, $fileOrDirectory, $default); } return $default($fileOrDirectory); } } = 3) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the PARSE_OBJECT flag instead.', E_USER_DEPRECATED); if (func_get_arg(2)) { $flags |= self::PARSE_OBJECT; } } if (func_num_args() >= 4) { @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED); if (func_get_arg(3)) { $flags |= self::PARSE_OBJECT_FOR_MAP; } } $yaml = new Parser(); return $yaml->parse($input, $flags); } public static function dump($input, $inline = 2, $indent = 4, $flags = 0) { if (is_bool($flags)) { @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED); if ($flags) { $flags = self::DUMP_EXCEPTION_ON_INVALID_TYPE; } else { $flags = 0; } } if (func_num_args() >= 5) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the DUMP_OBJECT flag instead.', E_USER_DEPRECATED); if (func_get_arg(4)) { $flags |= self::DUMP_OBJECT; } } $yaml = new Dumper($indent); return $yaml->dump($input, $inline, 0, $flags); } } cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; } public function get($name, $path = '/', $domain = null) { $this->flushExpiredCookies(); if (!empty($domain)) { foreach ($this->cookieJar as $cookieDomain => $pathCookies) { if ($cookieDomain) { $cookieDomain = '.'.ltrim($cookieDomain, '.'); if ($cookieDomain != substr('.'.$domain, -strlen($cookieDomain))) { continue; } } foreach ($pathCookies as $cookiePath => $namedCookies) { if ($cookiePath != substr($path, 0, strlen($cookiePath))) { continue; } if (isset($namedCookies[$name])) { return $namedCookies[$name]; } } } return; } foreach ($this->cookieJar as $cookies) { if (isset($cookies[$path][$name])) { return $cookies[$path][$name]; } } } public function expire($name, $path = '/', $domain = null) { if (null === $path) { $path = '/'; } if (empty($domain)) { $domains = array_keys($this->cookieJar); } else { $domains = array($domain); } foreach ($domains as $domain) { unset($this->cookieJar[$domain][$path][$name]); if (empty($this->cookieJar[$domain][$path])) { unset($this->cookieJar[$domain][$path]); if (empty($this->cookieJar[$domain])) { unset($this->cookieJar[$domain]); } } } } public function clear() { $this->cookieJar = array(); } public function updateFromSetCookie(array $setCookies, $uri = null) { $cookies = array(); foreach ($setCookies as $cookie) { foreach (explode(',', $cookie) as $i => $part) { if (0 === $i || preg_match('/^(?P\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) { $cookies[] = ltrim($part); } else { $cookies[count($cookies) - 1] .= ','.$part; } } } foreach ($cookies as $cookie) { try { $this->set(Cookie::fromString($cookie, $uri)); } catch (\InvalidArgumentException $e) { } } } public function updateFromResponse(Response $response, $uri = null) { $this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri); } public function all() { $this->flushExpiredCookies(); $flattenedCookies = array(); foreach ($this->cookieJar as $path) { foreach ($path as $cookies) { foreach ($cookies as $cookie) { $flattenedCookies[] = $cookie; } } } return $flattenedCookies; } public function allValues($uri, $returnsRawValue = false) { $this->flushExpiredCookies(); $parts = array_replace(array('path' => '/'), parse_url($uri)); $cookies = array(); foreach ($this->cookieJar as $domain => $pathCookies) { if ($domain) { $domain = '.'.ltrim($domain, '.'); if ($domain != substr('.'.$parts['host'], -strlen($domain))) { continue; } } foreach ($pathCookies as $path => $namedCookies) { if ($path != substr($parts['path'], 0, strlen($path))) { continue; } foreach ($namedCookies as $cookie) { if ($cookie->isSecure() && 'https' != $parts['scheme']) { continue; } $cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue(); } } } return $cookies; } public function allRawValues($uri) { return $this->allValues($uri, true); } public function flushExpiredCookies() { foreach ($this->cookieJar as $domain => $pathCookies) { foreach ($pathCookies as $path => $namedCookies) { foreach ($namedCookies as $name => $cookie) { if ($cookie->isExpired()) { unset($this->cookieJar[$domain][$path][$name]); } } } } } } content = $content; $this->status = $status; $this->headers = $headers; } public function __toString() { $headers = ''; foreach ($this->headers as $name => $value) { if (is_string($value)) { $headers .= $this->buildHeader($name, $value); } else { foreach ($value as $headerValue) { $headers .= $this->buildHeader($name, $headerValue); } } } return $headers."\n".$this->content; } protected function buildHeader($name, $value) { return sprintf("%s: %s\n", $name, $value); } public function getContent() { return $this->content; } public function getStatus() { return $this->status; } public function getHeaders() { return $this->headers; } public function getHeader($header, $first = true) { $normalizedHeader = str_replace('-', '_', strtolower($header)); foreach ($this->headers as $key => $value) { if (str_replace('-', '_', strtolower($key)) === $normalizedHeader) { if ($first) { return is_array($value) ? (count($value) ? $value[0] : '') : $value; } return is_array($value) ? $value : array($value); } } return $first ? null : array(); } } setServerParameters($server); $this->history = $history ?: new History(); $this->cookieJar = $cookieJar ?: new CookieJar(); } public function followRedirects($followRedirect = true) { $this->followRedirects = (bool) $followRedirect; } public function isFollowingRedirects() { return $this->followRedirects; } public function setMaxRedirects($maxRedirects) { $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects; $this->followRedirects = -1 != $this->maxRedirects; } public function getMaxRedirects() { return $this->maxRedirects; } public function insulate($insulated = true) { if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) { throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.'); } $this->insulated = (bool) $insulated; } public function setServerParameters(array $server) { $this->server = array_merge(array( 'HTTP_USER_AGENT' => 'Symfony BrowserKit', ), $server); } public function setServerParameter($key, $value) { $this->server[$key] = $value; } public function getServerParameter($key, $default = '') { return isset($this->server[$key]) ? $this->server[$key] : $default; } public function getHistory() { return $this->history; } public function getCookieJar() { return $this->cookieJar; } public function getCrawler() { return $this->crawler; } public function getInternalResponse() { return $this->internalResponse; } public function getResponse() { return $this->response; } public function getInternalRequest() { return $this->internalRequest; } public function getRequest() { return $this->request; } public function click(Link $link) { if ($link instanceof Form) { return $this->submit($link); } return $this->request($link->getMethod(), $link->getUri()); } public function submit(Form $form, array $values = array()) { $form->setValues($values); return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles()); } public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true) { if ($this->isMainRequest) { $this->redirectCount = 0; } else { ++$this->redirectCount; } $uri = $this->getAbsoluteUri($uri); $server = array_merge($this->server, $server); if (isset($server['HTTPS'])) { $uri = preg_replace('{^'.parse_url($uri, PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri); } if (!$this->history->isEmpty()) { $server['HTTP_REFERER'] = $this->history->current()->getUri(); } if (empty($server['HTTP_HOST'])) { $server['HTTP_HOST'] = $this->extractHost($uri); } $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME); $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content); $this->request = $this->filterRequest($this->internalRequest); if (true === $changeHistory) { $this->history->add($this->internalRequest); } if ($this->insulated) { $this->response = $this->doRequestInProcess($this->request); } else { $this->response = $this->doRequest($this->request); } $this->internalResponse = $this->filterResponse($this->response); $this->cookieJar->updateFromResponse($this->internalResponse, $uri); $status = $this->internalResponse->getStatus(); if ($status >= 300 && $status < 400) { $this->redirect = $this->internalResponse->getHeader('Location'); } else { $this->redirect = null; } if ($this->followRedirects && $this->redirect) { return $this->crawler = $this->followRedirect(); } return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); } protected function doRequestInProcess($request) { $process = new PhpProcess($this->getScript($request), null, null); $process->run(); if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) { throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s', $process->getOutput(), $process->getErrorOutput())); } return unserialize($process->getOutput()); } abstract protected function doRequest($request); protected function getScript($request) { throw new \LogicException('To insulate requests, you need to override the getScript() method.'); } protected function filterRequest(Request $request) { return $request; } protected function filterResponse($response) { return $response; } protected function createCrawlerFromContent($uri, $content, $type) { if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { return; } $crawler = new Crawler(null, $uri); $crawler->addContent($content, $type); return $crawler; } public function back() { return $this->requestFromRequest($this->history->back(), false); } public function forward() { return $this->requestFromRequest($this->history->forward(), false); } public function reload() { return $this->requestFromRequest($this->history->current(), false); } public function followRedirect() { if (empty($this->redirect)) { throw new \LogicException('The request was not redirected.'); } if (-1 !== $this->maxRedirects) { if ($this->redirectCount > $this->maxRedirects) { throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects)); } } $request = $this->internalRequest; if (in_array($this->internalResponse->getStatus(), array(302, 303))) { $method = 'GET'; $files = array(); $content = null; } else { $method = $request->getMethod(); $files = $request->getFiles(); $content = $request->getContent(); } if ('GET' === strtoupper($method)) { $parameters = array(); } else { $parameters = $request->getParameters(); } $server = $request->getServer(); $server = $this->updateServerFromUri($server, $this->redirect); $this->isMainRequest = false; $response = $this->request($method, $this->redirect, $parameters, $files, $server, $content); $this->isMainRequest = true; return $response; } public function restart() { $this->cookieJar->clear(); $this->history->clear(); } protected function getAbsoluteUri($uri) { if (0 === strpos($uri, 'http://') || 0 === strpos($uri, 'https://')) { return $uri; } if (!$this->history->isEmpty()) { $currentUri = $this->history->current()->getUri(); } else { $currentUri = sprintf('http%s://%s/', isset($this->server['HTTPS']) ? 's' : '', isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost' ); } if (0 === strpos($uri, '//')) { return parse_url($currentUri, PHP_URL_SCHEME).':'.$uri; } if (!$uri || '#' == $uri[0] || '?' == $uri[0]) { return preg_replace('/[#?].*?$/', '', $currentUri).$uri; } if ('/' !== $uri[0]) { $path = parse_url($currentUri, PHP_URL_PATH); if ('/' !== substr($path, -1)) { $path = substr($path, 0, strrpos($path, '/') + 1); } $uri = $path.$uri; } return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri; } protected function requestFromRequest(Request $request, $changeHistory = true) { return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory); } private function updateServerFromUri($server, $uri) { $server['HTTP_HOST'] = $this->extractHost($uri); $scheme = parse_url($uri, PHP_URL_SCHEME); $server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme; unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']); return $server; } private function extractHost($uri) { $host = parse_url($uri, PHP_URL_HOST); if ($port = parse_url($uri, PHP_URL_PORT)) { return $host.':'.$port; } return $host; } } value = urldecode($value); $this->rawValue = $value; } else { $this->value = $value; $this->rawValue = urlencode($value); } $this->name = $name; $this->path = empty($path) ? '/' : $path; $this->domain = $domain; $this->secure = (bool) $secure; $this->httponly = (bool) $httponly; if (null !== $expires) { $timestampAsDateTime = \DateTime::createFromFormat('U', $expires); if (false === $timestampAsDateTime) { throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.', $expires)); } $this->expires = $timestampAsDateTime->format('U'); } } public function __toString() { $cookie = sprintf('%s=%s', $this->name, $this->rawValue); if (null !== $this->expires) { $dateTime = \DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT')); $cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::$dateFormats[0])); } if ('' !== $this->domain) { $cookie .= '; domain='.$this->domain; } if ($this->path) { $cookie .= '; path='.$this->path; } if ($this->secure) { $cookie .= '; secure'; } if ($this->httponly) { $cookie .= '; httponly'; } return $cookie; } public static function fromString($cookie, $url = null) { $parts = explode(';', $cookie); if (false === strpos($parts[0], '=')) { throw new \InvalidArgumentException(sprintf('The cookie string "%s" is not valid.', $parts[0])); } list($name, $value) = explode('=', array_shift($parts), 2); $values = array( 'name' => trim($name), 'value' => trim($value), 'expires' => null, 'path' => '/', 'domain' => '', 'secure' => false, 'httponly' => false, 'passedRawValue' => true, ); if (null !== $url) { if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host'])) { throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url)); } $values['domain'] = $urlParts['host']; $values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : ''; } foreach ($parts as $part) { $part = trim($part); if ('secure' === strtolower($part)) { if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) { continue; } $values['secure'] = true; continue; } if ('httponly' === strtolower($part)) { $values['httponly'] = true; continue; } if (2 === count($elements = explode('=', $part, 2))) { if ('expires' === strtolower($elements[0])) { $elements[1] = self::parseDate($elements[1]); } $values[strtolower($elements[0])] = $elements[1]; } } return new static( $values['name'], $values['value'], $values['expires'], $values['path'], $values['domain'], $values['secure'], $values['httponly'], $values['passedRawValue'] ); } private static function parseDate($dateValue) { if (($length = strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length - 1]) { $dateValue = substr($dateValue, 1, -1); } foreach (self::$dateFormats as $dateFormat) { if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) { return $date->format('U'); } } if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) { return $date->format('U'); } } public function getName() { return $this->name; } public function getValue() { return $this->value; } public function getRawValue() { return $this->rawValue; } public function getExpiresTime() { return $this->expires; } public function getPath() { return $this->path; } public function getDomain() { return $this->domain; } public function isSecure() { return $this->secure; } public function isHttpOnly() { return $this->httponly; } public function isExpired() { return null !== $this->expires && 0 != $this->expires && $this->expires < time(); } } uri = $uri; $this->method = $method; $this->parameters = $parameters; $this->files = $files; $this->cookies = $cookies; $this->server = $server; $this->content = $content; } public function getUri() { return $this->uri; } public function getMethod() { return $this->method; } public function getParameters() { return $this->parameters; } public function getFiles() { return $this->files; } public function getCookies() { return $this->cookies; } public function getServer() { return $this->server; } public function getContent() { return $this->content; } } stack = array(); $this->position = -1; } public function add(Request $request) { $this->stack = array_slice($this->stack, 0, $this->position + 1); $this->stack[] = clone $request; $this->position = count($this->stack) - 1; } public function isEmpty() { return count($this->stack) == 0; } public function back() { if ($this->position < 1) { throw new \LogicException('You are already on the first page.'); } return clone $this->stack[--$this->position]; } public function forward() { if ($this->position > count($this->stack) - 2) { throw new \LogicException('You are already on the last page.'); } return clone $this->stack[++$this->position]; } public function current() { if (-1 == $this->position) { throw new \LogicException('The page history is empty.'); } return clone $this->stack[$this->position]; } } 'OK', 1 => 'General error', 2 => 'Misuse of shell builtins', 126 => 'Invoked command cannot execute', 127 => 'Command not found', 128 => 'Invalid exit argument', 129 => 'Hangup', 130 => 'Interrupt', 131 => 'Quit and dump core', 132 => 'Illegal instruction', 133 => 'Trace/breakpoint trap', 134 => 'Process aborted', 135 => 'Bus error: "access to undefined portion of memory object"', 136 => 'Floating point exception: "erroneous arithmetic operation"', 137 => 'Kill (terminate immediately)', 138 => 'User-defined 1', 139 => 'Segmentation violation', 140 => 'User-defined 2', 141 => 'Write to pipe with no one reading', 142 => 'Signal raised by alarm', 143 => 'Termination (request to terminate)', 145 => 'Child process terminated, stopped (or continued*)', 146 => 'Continue if stopped', 147 => 'Stop executing temporarily', 148 => 'Terminal stop signal', 149 => 'Background process attempting to read from tty ("in")', 150 => 'Background process attempting to write to tty ("out")', 151 => 'Urgent data available on socket', 152 => 'CPU time limit exceeded', 153 => 'File size limit exceeded', 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', 155 => 'Profiling timer expired', 157 => 'Pollable event', 159 => 'Bad syscall', ); public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) { if (!function_exists('proc_open')) { throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); } $this->commandline = $commandline; $this->cwd = $cwd; if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { $this->cwd = getcwd(); } if (null !== $env) { $this->setEnv($env); } $this->setInput($input); $this->setTimeout($timeout); $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR; $this->pty = false; $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options); } public function __destruct() { $this->stop(0); } public function __clone() { $this->resetProcessData(); } public function run($callback = null) { $this->start($callback); return $this->wait(); } public function mustRun(callable $callback = null) { if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); } if (0 !== $this->run($callback)) { throw new ProcessFailedException($this); } return $this; } public function start(callable $callback = null) { if ($this->isRunning()) { throw new RuntimeException('Process is already running'); } $this->resetProcessData(); $this->starttime = $this->lastOutputTime = microtime(true); $this->callback = $this->buildCallback($callback); $this->hasCallback = null !== $callback; $descriptors = $this->getDescriptors(); $inheritEnv = $this->inheritEnv; $commandline = $this->commandline; $env = $this->env; $envBackup = array(); if (null !== $env && $inheritEnv) { if ('\\' === DIRECTORY_SEPARATOR && !empty($this->options['bypass_shell']) && !$this->enhanceWindowsCompatibility) { throw new LogicException('The "bypass_shell" option must be false to inherit environment variables while enhanced Windows compatibility is off'); } foreach ($env as $k => $v) { $envBackup[$k] = getenv($k); putenv(false === $v || null === $v ? $k : "$k=$v"); } $env = null; } if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { $commandline = 'cmd /V:ON /E:ON /D /C "('.$commandline.')'; foreach ($this->processPipes->getFiles() as $offset => $filename) { $commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename); } $commandline .= '"'; if (!isset($this->options['bypass_shell'])) { $this->options['bypass_shell'] = true; } } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { $descriptors[3] = array('pipe', 'w'); $commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; $ptsWorkaround = fopen(__FILE__, 'r'); } $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $env, $this->options); foreach ($envBackup as $k => $v) { putenv(false === $v ? $k : "$k=$v"); } if (!is_resource($this->process)) { throw new RuntimeException('Unable to launch a new process.'); } $this->status = self::STATUS_STARTED; if (isset($descriptors[3])) { $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); } if ($this->tty) { return; } $this->updateStatus(false); $this->checkTimeout(); } public function restart(callable $callback = null) { if ($this->isRunning()) { throw new RuntimeException('Process is already running'); } $process = clone $this; $process->start($callback); return $process; } public function wait(callable $callback = null) { $this->requireProcessIsStarted(__FUNCTION__); $this->updateStatus(false); if (null !== $callback) { if (!$this->processPipes->haveReadSupport()) { $this->stop(0); throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait'); } $this->callback = $this->buildCallback($callback); } do { $this->checkTimeout(); $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running); } while ($running); while ($this->isRunning()) { usleep(1000); } if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); } return $this->exitcode; } public function getPid() { return $this->isRunning() ? $this->processInformation['pid'] : null; } public function signal($signal) { $this->doSignal($signal, true); return $this; } public function disableOutput() { if ($this->isRunning()) { throw new RuntimeException('Disabling output while the process is running is not possible.'); } if (null !== $this->idleTimeout) { throw new LogicException('Output can not be disabled while an idle timeout is set.'); } $this->outputDisabled = true; return $this; } public function enableOutput() { if ($this->isRunning()) { throw new RuntimeException('Enabling output while the process is running is not possible.'); } $this->outputDisabled = false; return $this; } public function isOutputDisabled() { return $this->outputDisabled; } public function getOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { return ''; } return $ret; } public function getIncrementalOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); $this->incrementalOutputOffset = ftell($this->stdout); if (false === $latest) { return ''; } return $latest; } public function getIterator($flags = 0) { $this->readPipesForOutput(__FUNCTION__, false); $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); $blocking = !(self::ITER_NON_BLOCKING & $flags); $yieldOut = !(self::ITER_SKIP_OUT & $flags); $yieldErr = !(self::ITER_SKIP_ERR & $flags); while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { if ($yieldOut) { $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); if (isset($out[0])) { if ($clearOutput) { $this->clearOutput(); } else { $this->incrementalOutputOffset = ftell($this->stdout); } yield self::OUT => $out; } } if ($yieldErr) { $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); if (isset($err[0])) { if ($clearOutput) { $this->clearErrorOutput(); } else { $this->incrementalErrorOutputOffset = ftell($this->stderr); } yield self::ERR => $err; } } if (!$blocking && !isset($out[0]) && !isset($err[0])) { yield self::OUT => ''; } $this->checkTimeout(); $this->readPipesForOutput(__FUNCTION__, $blocking); } } public function clearOutput() { ftruncate($this->stdout, 0); fseek($this->stdout, 0); $this->incrementalOutputOffset = 0; return $this; } public function getErrorOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { return ''; } return $ret; } public function getIncrementalErrorOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); $this->incrementalErrorOutputOffset = ftell($this->stderr); if (false === $latest) { return ''; } return $latest; } public function clearErrorOutput() { ftruncate($this->stderr, 0); fseek($this->stderr, 0); $this->incrementalErrorOutputOffset = 0; return $this; } public function getExitCode() { if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); } $this->updateStatus(false); return $this->exitcode; } public function getExitCodeText() { if (null === $exitcode = $this->getExitCode()) { return; } return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; } public function isSuccessful() { return 0 === $this->getExitCode(); } public function hasBeenSignaled() { $this->requireProcessIsTerminated(__FUNCTION__); if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } return $this->processInformation['signaled']; } public function getTermSignal() { $this->requireProcessIsTerminated(__FUNCTION__); if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } return $this->processInformation['termsig']; } public function hasBeenStopped() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopped']; } public function getStopSignal() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopsig']; } public function isRunning() { if (self::STATUS_STARTED !== $this->status) { return false; } $this->updateStatus(false); return $this->processInformation['running']; } public function isStarted() { return $this->status != self::STATUS_READY; } public function isTerminated() { $this->updateStatus(false); return $this->status == self::STATUS_TERMINATED; } public function getStatus() { $this->updateStatus(false); return $this->status; } public function stop($timeout = 10, $signal = null) { $timeoutMicro = microtime(true) + $timeout; if ($this->isRunning()) { $this->doSignal(15, false); do { usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); if ($this->isRunning()) { $this->doSignal($signal ?: 9, false); } } if ($this->isRunning()) { if (isset($this->fallbackStatus['pid'])) { unset($this->fallbackStatus['pid']); return $this->stop(0, $signal); } $this->close(); } return $this->exitcode; } public function addOutput($line) { $this->lastOutputTime = microtime(true); fseek($this->stdout, 0, SEEK_END); fwrite($this->stdout, $line); fseek($this->stdout, $this->incrementalOutputOffset); } public function addErrorOutput($line) { $this->lastOutputTime = microtime(true); fseek($this->stderr, 0, SEEK_END); fwrite($this->stderr, $line); fseek($this->stderr, $this->incrementalErrorOutputOffset); } public function getCommandLine() { return $this->commandline; } public function setCommandLine($commandline) { $this->commandline = $commandline; return $this; } public function getTimeout() { return $this->timeout; } public function getIdleTimeout() { return $this->idleTimeout; } public function setTimeout($timeout) { $this->timeout = $this->validateTimeout($timeout); return $this; } public function setIdleTimeout($timeout) { if (null !== $timeout && $this->outputDisabled) { throw new LogicException('Idle timeout can not be set while the output is disabled.'); } $this->idleTimeout = $this->validateTimeout($timeout); return $this; } public function setTty($tty) { if ('\\' === DIRECTORY_SEPARATOR && $tty) { throw new RuntimeException('TTY mode is not supported on Windows platform.'); } if ($tty) { static $isTtySupported; if (null === $isTtySupported) { $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', array(array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('file', '/dev/tty', 'w')), $pipes); } if (!$isTtySupported) { throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); } } $this->tty = (bool) $tty; return $this; } public function isTty() { return $this->tty; } public function setPty($bool) { $this->pty = (bool) $bool; return $this; } public function isPty() { return $this->pty; } public function getWorkingDirectory() { if (null === $this->cwd) { return getcwd() ?: null; } return $this->cwd; } public function setWorkingDirectory($cwd) { $this->cwd = $cwd; return $this; } public function getEnv() { return $this->env; } public function setEnv(array $env) { $env = array_filter($env, function ($value) { return !is_array($value); }); $this->env = $env; return $this; } public function getInput() { return $this->input; } public function setInput($input) { if ($this->isRunning()) { throw new LogicException('Input can not be set while the process is running.'); } $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } public function getOptions() { return $this->options; } public function setOptions(array $options) { $this->options = $options; return $this; } public function getEnhanceWindowsCompatibility() { return $this->enhanceWindowsCompatibility; } public function setEnhanceWindowsCompatibility($enhance) { $this->enhanceWindowsCompatibility = (bool) $enhance; return $this; } public function getEnhanceSigchildCompatibility() { return $this->enhanceSigchildCompatibility; } public function setEnhanceSigchildCompatibility($enhance) { $this->enhanceSigchildCompatibility = (bool) $enhance; return $this; } public function inheritEnvironmentVariables($inheritEnv = true) { $this->inheritEnv = (bool) $inheritEnv; return $this; } public function areEnvironmentVariablesInherited() { return $this->inheritEnv; } public function checkTimeout() { if ($this->status !== self::STATUS_STARTED) { return; } if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); } if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); } } public static function isPtySupported() { static $result; if (null !== $result) { return $result; } if ('\\' === DIRECTORY_SEPARATOR) { return $result = false; } return $result = (bool) @proc_open('echo 1 >/dev/null', array(array('pty'), array('pty'), array('pty')), $pipes); } private function getDescriptors() { if ($this->input instanceof \Iterator) { $this->input->rewind(); } if ('\\' === DIRECTORY_SEPARATOR) { $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); } else { $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); } return $this->processPipes->getDescriptors(); } protected function buildCallback(callable $callback = null) { if ($this->outputDisabled) { return function ($type, $data) use ($callback) { if (null !== $callback) { call_user_func($callback, $type, $data); } }; } $out = self::OUT; return function ($type, $data) use ($callback, $out) { if ($out == $type) { $this->addOutput($data); } else { $this->addErrorOutput($data); } if (null !== $callback) { call_user_func($callback, $type, $data); } }; } protected function updateStatus($blocking) { if (self::STATUS_STARTED !== $this->status) { return; } $this->processInformation = proc_get_status($this->process); $running = $this->processInformation['running']; $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running); if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { $this->processInformation = $this->fallbackStatus + $this->processInformation; } if (!$running) { $this->close(); } } protected function isSigchildEnabled() { if (null !== self::$sigchild) { return self::$sigchild; } if (!function_exists('phpinfo') || defined('HHVM_VERSION')) { return self::$sigchild = false; } ob_start(); phpinfo(INFO_GENERAL); return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); } private function readPipesForOutput($caller, $blocking = false) { if ($this->outputDisabled) { throw new LogicException('Output has been disabled.'); } $this->requireProcessIsStarted($caller); $this->updateStatus($blocking); } private function validateTimeout($timeout) { $timeout = (float) $timeout; if (0.0 === $timeout) { $timeout = null; } elseif ($timeout < 0) { throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); } return $timeout; } private function readPipes($blocking, $close) { $result = $this->processPipes->readAndWrite($blocking, $close); $callback = $this->callback; foreach ($result as $type => $data) { if (3 !== $type) { $callback($type === self::STDOUT ? self::OUT : self::ERR, $data); } elseif (!isset($this->fallbackStatus['signaled'])) { $this->fallbackStatus['exitcode'] = (int) $data; } } } private function close() { $this->processPipes->close(); if (is_resource($this->process)) { proc_close($this->process); } $this->exitcode = $this->processInformation['exitcode']; $this->status = self::STATUS_TERMINATED; if (-1 === $this->exitcode) { if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { $this->exitcode = 128 + $this->processInformation['termsig']; } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { $this->processInformation['signaled'] = true; $this->processInformation['termsig'] = -1; } } $this->callback = null; return $this->exitcode; } private function resetProcessData() { $this->starttime = null; $this->callback = null; $this->exitcode = null; $this->fallbackStatus = array(); $this->processInformation = null; $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+'); $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+'); $this->process = null; $this->latestSignal = null; $this->status = self::STATUS_READY; $this->incrementalOutputOffset = 0; $this->incrementalErrorOutputOffset = 0; } private function doSignal($signal, $throwException) { if (null === $pid = $this->getPid()) { if ($throwException) { throw new LogicException('Can not send signal on a non running process.'); } return false; } if ('\\' === DIRECTORY_SEPARATOR) { exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); if ($exitCode && $this->isRunning()) { if ($throwException) { throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); } return false; } } else { if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) { $ok = @proc_terminate($this->process, $signal); } elseif (function_exists('posix_kill')) { $ok = @posix_kill($pid, $signal); } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) { $ok = false === fgets($pipes[2]); } if (!$ok) { if ($throwException) { throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); } return false; } } $this->latestSignal = (int) $signal; $this->fallbackStatus['signaled'] = true; $this->fallbackStatus['exitcode'] = -1; $this->fallbackStatus['termsig'] = $this->latestSignal; return true; } private function requireProcessIsStarted($functionName) { if (!$this->isStarted()) { throw new LogicException(sprintf('Process must be started before calling %s.', $functionName)); } } private function requireProcessIsTerminated($functionName) { if (!$this->isTerminated()) { throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); } } } find()) { $php = null; } if ('phpdbg' === PHP_SAPI) { $file = tempnam(sys_get_temp_dir(), 'dbg'); file_put_contents($file, $script); register_shutdown_function('unlink', $file); $php .= ' '.ProcessUtils::escapeArgument($file); $script = null; } if ('\\' !== DIRECTORY_SEPARATOR && null !== $php) { $php = 'exec '.$php; } parent::__construct($php, $cwd, $env, $script, $timeout, $options); } public function setPhpBinary($php) { $this->setCommandLine($php); } public function start(callable $callback = null) { if (null === $this->getCommandLine()) { throw new RuntimeException('Unable to find the PHP executable.'); } parent::start($callback); } } executableFinder = new ExecutableFinder(); } public function find($includeArgs = true) { $args = $this->findArguments(); $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; if (defined('HHVM_VERSION')) { return (getenv('PHP_BINARY') ?: PHP_BINARY).$args; } if (PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) { return PHP_BINARY.$args; } if ($php = getenv('PHP_PATH')) { if (!is_executable($php)) { return false; } return $php; } if ($php = getenv('PHP_PEAR_PHP_BIN')) { if (is_executable($php)) { return $php; } } $dirs = array(PHP_BINDIR); if ('\\' === DIRECTORY_SEPARATOR) { $dirs[] = 'C:\xampp\php\\'; } return $this->executableFinder->find('php', false, $dirs); } public function findArguments() { $arguments = array(); if (defined('HHVM_VERSION')) { $arguments[] = '--php'; } elseif ('phpdbg' === PHP_SAPI) { $arguments[] = '-qrr'; } return $arguments; } } arguments = $arguments; } public static function create(array $arguments = array()) { return new static($arguments); } public function add($argument) { $this->arguments[] = $argument; return $this; } public function setPrefix($prefix) { $this->prefix = is_array($prefix) ? $prefix : array($prefix); return $this; } public function setArguments(array $arguments) { $this->arguments = $arguments; return $this; } public function setWorkingDirectory($cwd) { $this->cwd = $cwd; return $this; } public function inheritEnvironmentVariables($inheritEnv = true) { $this->inheritEnv = $inheritEnv; return $this; } public function setEnv($name, $value) { $this->env[$name] = $value; return $this; } public function addEnvironmentVariables(array $variables) { $this->env = array_replace($this->env, $variables); return $this; } public function setInput($input) { $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } public function setTimeout($timeout) { if (null === $timeout) { $this->timeout = null; return $this; } $timeout = (float) $timeout; if ($timeout < 0) { throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); } $this->timeout = $timeout; return $this; } public function setOption($name, $value) { $this->options[$name] = $value; return $this; } public function disableOutput() { $this->outputDisabled = true; return $this; } public function enableOutput() { $this->outputDisabled = false; return $this; } public function getProcess() { if (0 === count($this->prefix) && 0 === count($this->arguments)) { throw new LogicException('You must add() command arguments before calling getProcess().'); } $options = $this->options; $arguments = array_merge($this->prefix, $this->arguments); $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments)); $process = new Process($script, $this->cwd, $this->env, $this->input, $this->timeout, $options); if ($this->inheritEnv) { $process->inheritEnvironmentVariables(); } if ($this->outputDisabled) { $process->disableOutput(); } return $process; } } suffixes = $suffixes; } public function addSuffix($suffix) { $this->suffixes[] = $suffix; } public function find($name, $default = null, array $extraDirs = array()) { if (ini_get('open_basedir')) { $searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir')); $dirs = array(); foreach ($searchPath as $path) { if (@is_dir($path)) { $dirs[] = $path; } else { if (basename($path) == $name && @is_executable($path)) { return $path; } } } } else { $dirs = array_merge( explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), $extraDirs ); } $suffixes = array(''); if ('\\' === DIRECTORY_SEPARATOR) { $pathExt = getenv('PATHEXT'); $suffixes = array_merge($suffixes, $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes); } foreach ($suffixes as $suffix) { foreach ($dirs as $dir) { if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) { return $file; } } } return $default; } } isSuccessful()) { throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); } $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText(), $process->getWorkingDirectory() ); if (!$process->isOutputDisabled()) { $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput() ); } parent::__construct($error); $this->process = $process; } public function getProcess() { return $this->process; } } process = $process; $this->timeoutType = $timeoutType; parent::__construct(sprintf( 'The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout() )); } public function getProcess() { return $this->process; } public function isGeneralTimeout() { return $this->timeoutType === self::TYPE_GENERAL; } public function isIdleTimeout() { return $this->timeoutType === self::TYPE_IDLE; } public function getExceededTimeout() { switch ($this->timeoutType) { case self::TYPE_GENERAL: return $this->process->getTimeout(); case self::TYPE_IDLE: return $this->process->getIdleTimeout(); default: throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); } } } ttyMode = (bool) $ttyMode; $this->ptyMode = (bool) $ptyMode; $this->haveReadSupport = (bool) $haveReadSupport; parent::__construct($input); } public function __destruct() { $this->close(); } public function getDescriptors() { if (!$this->haveReadSupport) { $nullstream = fopen('/dev/null', 'c'); return array( array('pipe', 'r'), $nullstream, $nullstream, ); } if ($this->ttyMode) { return array( array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('file', '/dev/tty', 'w'), ); } if ($this->ptyMode && Process::isPtySupported()) { return array( array('pty'), array('pty'), array('pty'), ); } return array( array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'), ); } public function getFiles() { return array(); } public function readAndWrite($blocking, $close = false) { $this->unblock(); $w = $this->write(); $read = $e = array(); $r = $this->pipes; unset($r[0]); if (($r || $w) && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { if (!$this->hasSystemCallBeenInterrupted()) { $this->pipes = array(); } return $read; } foreach ($r as $pipe) { $read[$type = array_search($pipe, $this->pipes, true)] = ''; do { $data = fread($pipe, self::CHUNK_SIZE); $read[$type] .= $data; } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); if (!isset($read[$type][0])) { unset($read[$type]); } if ($close && feof($pipe)) { fclose($pipe); unset($this->pipes[$type]); } } return $read; } public function haveReadSupport() { return $this->haveReadSupport; } public function areOpen() { return (bool) $this->pipes; } } input = $input; } elseif (is_string($input)) { $this->inputBuffer = $input; } else { $this->inputBuffer = (string) $input; } } public function close() { foreach ($this->pipes as $pipe) { fclose($pipe); } $this->pipes = array(); } protected function hasSystemCallBeenInterrupted() { $lastError = error_get_last(); return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); } protected function unblock() { if (!$this->blocked) { return; } foreach ($this->pipes as $pipe) { stream_set_blocking($pipe, 0); } if (is_resource($this->input)) { stream_set_blocking($this->input, 0); } $this->blocked = false; } protected function write() { if (!isset($this->pipes[0])) { return; } $input = $this->input; if ($input instanceof \Iterator) { if (!$input->valid()) { $input = null; } elseif (is_resource($input = $input->current())) { stream_set_blocking($input, 0); } elseif (!isset($this->inputBuffer[0])) { if (!is_string($input)) { if (!is_scalar($input)) { throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', get_class($this->input), gettype($input))); } $input = (string) $input; } $this->inputBuffer = $input; $this->input->next(); $input = null; } else { $input = null; } } $r = $e = array(); $w = array($this->pipes[0]); if (false === $n = @stream_select($r, $w, $e, 0, 0)) { return; } foreach ($w as $stdin) { if (isset($this->inputBuffer[0])) { $written = fwrite($stdin, $this->inputBuffer); $this->inputBuffer = substr($this->inputBuffer, $written); if (isset($this->inputBuffer[0])) { return array($this->pipes[0]); } } if ($input) { for (;;) { $data = fread($input, self::CHUNK_SIZE); if (!isset($data[0])) { break; } $written = fwrite($stdin, $data); $data = substr($data, $written); if (isset($data[0])) { $this->inputBuffer = $data; return array($this->pipes[0]); } } if (feof($input)) { if ($this->input instanceof \Iterator) { $this->input->next(); } else { $this->input = null; } } } } if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { $this->input = null; fclose($this->pipes[0]); unset($this->pipes[0]); } elseif (!$w) { return array($this->pipes[0]); } } } 0, Process::STDERR => 0, ); private $haveReadSupport; public function __construct($input, $haveReadSupport) { $this->haveReadSupport = (bool) $haveReadSupport; if ($this->haveReadSupport) { $pipes = array( Process::STDOUT => Process::OUT, Process::STDERR => Process::ERR, ); $tmpCheck = false; $tmpDir = sys_get_temp_dir(); $lastError = 'unknown reason'; set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); for ($i = 0;; ++$i) { foreach ($pipes as $pipe => $name) { $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); if (file_exists($file) && !unlink($file)) { continue 2; } $h = fopen($file, 'xb'); if (!$h) { $error = $lastError; if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) { continue; } restore_error_handler(); throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error)); } if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) { continue 2; } if (isset($this->files[$pipe])) { unlink($this->files[$pipe]); } $this->files[$pipe] = $file; } break; } restore_error_handler(); } parent::__construct($input); } public function __destruct() { $this->close(); $this->removeFiles(); } public function getDescriptors() { if (!$this->haveReadSupport) { $nullstream = fopen('NUL', 'c'); return array( array('pipe', 'r'), $nullstream, $nullstream, ); } return array( array('pipe', 'r'), array('file', 'NUL', 'w'), array('file', 'NUL', 'w'), ); } public function getFiles() { return $this->files; } public function readAndWrite($blocking, $close = false) { $this->unblock(); $w = $this->write(); $read = $r = $e = array(); if ($blocking) { if ($w) { @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); } elseif ($this->fileHandles) { usleep(Process::TIMEOUT_PRECISION * 1E6); } } foreach ($this->fileHandles as $type => $fileHandle) { $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); if (isset($data[0])) { $this->readBytes[$type] += strlen($data); $read[$type] = $data; } if ($close) { fclose($fileHandle); unset($this->fileHandles[$type]); } } return $read; } public function haveReadSupport() { return $this->haveReadSupport; } public function areOpen() { return $this->pipes && $this->fileHandles; } public function close() { parent::close(); foreach ($this->fileHandles as $handle) { fclose($handle); } $this->fileHandles = array(); } private function removeFiles() { foreach ($this->files as $filename) { if (file_exists($filename)) { @unlink($filename); } } $this->files = array(); } } onEmpty = $onEmpty; } public function write($input) { if (null === $input) { return; } if ($this->isClosed()) { throw new RuntimeException(sprintf('%s is closed', static::class)); } $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); } public function close() { $this->open = false; } public function isClosed() { return !$this->open; } public function getIterator() { $this->open = true; while ($this->open || $this->input) { if (!$this->input) { yield ''; continue; } $current = array_shift($this->input); if ($current instanceof \Iterator) { foreach ($current as $cur) { yield $cur; } } else { yield $current; } if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { $this->write($onEmpty($this)); } } } } getIterator($input::ITER_SKIP_ERR); } if ($input instanceof \Iterator) { return $input; } if ($input instanceof \Traversable) { return new \IteratorIterator($input); } throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller)); } return $input; } private static function isSurroundedBy($arg, $char) { return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; } } xmlns = $xmlns; } public function toDom(TokenCollection $tokens): DOMDocument { $dom = new DOMDocument(); $dom->preserveWhiteSpace = false; $dom->loadXML($this->toXML($tokens)); return $dom; } public function toXML(TokenCollection $tokens): string { $this->writer = new \XMLWriter(); $this->writer->openMemory(); $this->writer->setIndent(true); $this->writer->startDocument(); $this->writer->startElement('source'); $this->writer->writeAttribute('xmlns', $this->xmlns->asString()); $this->writer->startElement('line'); $this->writer->writeAttribute('no', '1'); $this->previousToken = $tokens[0]; foreach ($tokens as $token) { $this->addToken($token); } $this->writer->endElement(); $this->writer->endElement(); $this->writer->endDocument(); return $this->writer->outputMemory(); } private function addToken(Token $token) { if ($this->previousToken->getLine() < $token->getLine()) { $this->writer->endElement(); $this->writer->startElement('line'); $this->writer->writeAttribute('no', (string)$token->getLine()); $this->previousToken = $token; } if ($token->getValue() !== '') { $this->writer->startElement('token'); $this->writer->writeAttribute('name', $token->getName()); $this->writer->writeRaw(htmlspecialchars($token->getValue(), ENT_NOQUOTES | ENT_DISALLOWED | ENT_XML1)); $this->writer->endElement(); } } } line = $line; $this->name = $name; $this->value = $value; } public function getLine(): int { return $this->line; } public function getName(): string { return $this->name; } public function getValue(): string { return $this->value; } } tokens[] = $token; } public function current(): Token { return current($this->tokens); } public function key(): int { return key($this->tokens); } public function next() { next($this->tokens); $this->pos++; } public function valid(): bool { return $this->count() > $this->pos; } public function rewind() { reset($this->tokens); $this->pos = 0; } public function count(): int { return count($this->tokens); } public function offsetExists($offset): bool { return isset($this->tokens[$offset]); } public function offsetGet($offset): Token { if (!$this->offsetExists($offset)) { throw new TokenCollectionException( sprintf('No Token at offest %s', $offset) ); } return $this->tokens[$offset]; } public function offsetSet($offset, $value) { if (!is_int($offset)) { $type = gettype($offset); throw new TokenCollectionException( sprintf( 'Offset must be of type integer, %s given', $type === 'object' ? get_class($value) : $type ) ); } if (!$value instanceof Token) { $type = gettype($value); throw new TokenCollectionException( sprintf( 'Value must be of type %s, %s given', Token::class, $type === 'object' ? get_class($value) : $type ) ); } $this->tokens[$offset] = $value; } public function offsetUnset($offset) { unset($this->tokens[$offset]); } } ensureValidUri($value); $this->value = $value; } public function asString(): string { return $this->value; } private function ensureValidUri($value) { if (strpos($value, ':') === false) { throw new NamespaceUriException( sprintf("Namespace URI '%s' must contain at least one colon", $value) ); } } } 'T_OPEN_BRACKET', ')' => 'T_CLOSE_BRACKET', '[' => 'T_OPEN_SQUARE', ']' => 'T_CLOSE_SQUARE', '{' => 'T_OPEN_CURLY', '}' => 'T_CLOSE_CURLY', ';' => 'T_SEMICOLON', '.' => 'T_DOT', ',' => 'T_COMMA', '=' => 'T_EQUAL', '<' => 'T_LT', '>' => 'T_GT', '+' => 'T_PLUS', '-' => 'T_MINUS', '*' => 'T_MULT', '/' => 'T_DIV', '?' => 'T_QUESTION_MARK', '!' => 'T_EXCLAMATION_MARK', ':' => 'T_COLON', '"' => 'T_DOUBLE_QUOTES', '@' => 'T_AT', '&' => 'T_AMPERSAND', '%' => 'T_PERCENT', '|' => 'T_PIPE', '$' => 'T_DOLLAR', '^' => 'T_CARET', '~' => 'T_TILDE', '`' => 'T_BACKTICK' ]; public function parse(string $source): TokenCollection { $result = new TokenCollection(); $tokens = token_get_all($source); $lastToken = new Token( $tokens[0][2], 'Placeholder', '' ); foreach ($tokens as $pos => $tok) { if (is_string($tok)) { $token = new Token( $lastToken->getLine(), $this->map[$tok], $tok ); $result->addToken($token); $lastToken = $token; continue; } $line = $tok[2]; $values = preg_split('/\R+/Uu', $tok[1]); foreach ($values as $v) { $token = new Token( $line, token_name($tok[0]), $v ); $result->addToken($token); $line++; $lastToken = $token; } } return $result; } } prefixesPsr0)) { return call_user_func_array('array_merge', $this->prefixesPsr0); } return array(); } public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } public function getFallbackDirs() { return $this->fallbackDirsPsr0; } public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } public function getClassMap() { return $this->classMap; } public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } public function add($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( (array) $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, (array) $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = (array) $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( (array) $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], (array) $paths ); } } public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } elseif ($prepend) { $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } public function getUseIncludePath() { return $this->useIncludePath; } public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); } public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); } public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } } public function findFile($class) { if ('\\' == $class[0]) { $class = substr($class, 1); } if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } $file = $this->findFileWithExtension($class, '.php'); if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (false === $file) { $this->missingClasses[$class] = true; } return $file; } private function findFileWithExtension($class, $ext) { $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { if (0 === strpos($class, $prefix)) { foreach ($this->prefixDirsPsr4[$prefix] as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } } } } foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } if (false !== $pos = strrpos($class, '\\')) { $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } function includeFile($file) { include $file; } = 50600 && !defined('HHVM_VERSION'); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitc4909e075a7b68a94b7e4489eb2bb42e::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->register(true); if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitc4909e075a7b68a94b7e4489eb2bb42e::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequirec4909e075a7b68a94b7e4489eb2bb42e($fileIdentifier, $file); } return $loader; } } function composerRequirec4909e075a7b68a94b7e4489eb2bb42e($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '3a37ebac017bc098e9a86b35401e7a68' => __DIR__ . '/..' . '/mongodb/mongodb/src/functions.php', 'c65d09b6820da036953a371c8c73a9b1' => __DIR__ . '/..' . '/facebook/graph-sdk/src/Facebook/polyfills.php', '1a296e41175bbbdf647d8b8ed1f41f41' => __DIR__ . '/../..' . '/shim.php', 'ba7c0cf09bd8c80b156d0906efed4a9d' => __DIR__ . '/../..' . '/phpunit5-loggers.php', ); public static $prefixLengthsPsr4 = array ( 'p' => array ( 'phpDocumentor\\Reflection\\' => 25, ), 'W' => array ( 'Webmozart\\Assert\\' => 17, ), 'S' => array ( 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Component\\Yaml\\' => 23, 'Symfony\\Component\\Process\\' => 26, 'Symfony\\Component\\Finder\\' => 25, 'Symfony\\Component\\EventDispatcher\\' => 34, 'Symfony\\Component\\DomCrawler\\' => 29, 'Symfony\\Component\\Debug\\' => 24, 'Symfony\\Component\\CssSelector\\' => 30, 'Symfony\\Component\\Console\\' => 26, 'Symfony\\Component\\BrowserKit\\' => 29, 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\' => 49, ), 'P' => array ( 'Psr\\Log\\' => 8, 'Psr\\Http\\Message\\' => 17, 'Predis\\' => 7, 'PhpAmqpLib\\' => 11, 'Pheanstalk\\' => 11, ), 'M' => array ( 'Monolog\\' => 8, 'MongoDB\\' => 8, ), 'L' => array ( 'League\\FactoryMuffin\\Faker\\' => 27, 'League\\FactoryMuffin\\' => 21, ), 'G' => array ( 'GuzzleHttp\\Psr7\\' => 16, 'GuzzleHttp\\Promise\\' => 19, 'GuzzleHttp\\' => 11, ), 'F' => array ( 'Faker\\' => 6, 'Facebook\\WebDriver\\' => 19, 'Facebook\\' => 9, ), 'D' => array ( 'Dotenv\\' => 7, 'Doctrine\\Instantiator\\' => 22, 'DeepCopy\\' => 9, ), 'C' => array ( 'Codeception\\Extension\\' => 22, 'Codeception\\' => 12, ), ); public static $prefixDirsPsr4 = array ( 'phpDocumentor\\Reflection\\' => array ( 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', 1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', 2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', ), 'Webmozart\\Assert\\' => array ( 0 => __DIR__ . '/..' . '/webmozart/assert/src', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Component\\Yaml\\' => array ( 0 => __DIR__ . '/..' . '/symfony/yaml', ), 'Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), 'Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', ), 'Symfony\\Component\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', ), 'Symfony\\Component\\DomCrawler\\' => array ( 0 => __DIR__ . '/..' . '/symfony/dom-crawler', ), 'Symfony\\Component\\Debug\\' => array ( 0 => __DIR__ . '/..' . '/symfony/debug', ), 'Symfony\\Component\\CssSelector\\' => array ( 0 => __DIR__ . '/..' . '/symfony/css-selector', ), 'Symfony\\Component\\Console\\' => array ( 0 => __DIR__ . '/..' . '/symfony/console', ), 'Symfony\\Component\\BrowserKit\\' => array ( 0 => __DIR__ . '/..' . '/symfony/browser-kit', ), 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\' => array ( 0 => __DIR__ . '/..' . '/stecman/symfony-console-completion/src', ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), 'Psr\\Http\\Message\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-message/src', ), 'Predis\\' => array ( 0 => __DIR__ . '/..' . '/predis/predis/src', ), 'PhpAmqpLib\\' => array ( 0 => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib', ), 'Pheanstalk\\' => array ( 0 => __DIR__ . '/..' . '/pda/pheanstalk/src', ), 'Monolog\\' => array ( 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', ), 'MongoDB\\' => array ( 0 => __DIR__ . '/..' . '/mongodb/mongodb/src', ), 'League\\FactoryMuffin\\Faker\\' => array ( 0 => __DIR__ . '/..' . '/league/factory-muffin-faker/src', ), 'League\\FactoryMuffin\\' => array ( 0 => __DIR__ . '/..' . '/league/factory-muffin/src', ), 'GuzzleHttp\\Psr7\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', ), 'GuzzleHttp\\Promise\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', ), 'GuzzleHttp\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', ), 'Faker\\' => array ( 0 => __DIR__ . '/..' . '/fzaninotto/faker/src/Faker', ), 'Facebook\\WebDriver\\' => array ( 0 => __DIR__ . '/..' . '/facebook/webdriver/lib', ), 'Facebook\\' => array ( 0 => __DIR__ . '/..' . '/facebook/graph-sdk/src/Facebook', ), 'Dotenv\\' => array ( 0 => __DIR__ . '/..' . '/vlucas/phpdotenv/src', ), 'Doctrine\\Instantiator\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator', ), 'DeepCopy\\' => array ( 0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy', ), 'Codeception\\Extension\\' => array ( 0 => __DIR__ . '/../..' . '/ext', ), 'Codeception\\' => array ( 0 => __DIR__ . '/../..' . '/src/Codeception', ), ); public static $prefixesPsr0 = array ( 'P' => array ( 'Prophecy\\' => array ( 0 => __DIR__ . '/..' . '/phpspec/prophecy/src', ), ), 'F' => array ( 'Flow\\JSONPath\\Test' => array ( 0 => __DIR__ . '/..' . '/flow/jsonpath/tests', ), 'Flow\\JSONPath' => array ( 0 => __DIR__ . '/..' . '/flow/jsonpath/src', ), ), 'C' => array ( 'Codeception\\' => array ( 0 => __DIR__ . '/..' . '/codeception/specify/src', ), ), 'B' => array ( 'Behat\\Gherkin' => array ( 0 => __DIR__ . '/..' . '/behat/gherkin/src', ), ), ); public static $classMap = array ( 'File_Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php', 'File_Iterator_Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php', 'File_Iterator_Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php', 'Generic_Sniffs_Arrays_DisallowLongArraySyntaxSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Arrays/DisallowLongArraySyntaxSniff.php', 'Generic_Sniffs_Arrays_DisallowShortArraySyntaxSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php', 'Generic_Sniffs_Classes_DuplicateClassNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', 'Generic_Sniffs_Classes_OpeningBraceSameLineSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Classes/OpeningBraceSameLineSniff.php', 'Generic_Sniffs_CodeAnalysis_EmptyStatementSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/EmptyStatementSniff.php', 'Generic_Sniffs_CodeAnalysis_ForLoopShouldBeWhileLoopSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/ForLoopShouldBeWhileLoopSniff.php', 'Generic_Sniffs_CodeAnalysis_ForLoopWithTestFunctionCallSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php', 'Generic_Sniffs_CodeAnalysis_JumbledIncrementerSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/JumbledIncrementerSniff.php', 'Generic_Sniffs_CodeAnalysis_UnconditionalIfStatementSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php', 'Generic_Sniffs_CodeAnalysis_UnnecessaryFinalModifierSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php', 'Generic_Sniffs_CodeAnalysis_UnusedFunctionParameterSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php', 'Generic_Sniffs_CodeAnalysis_UselessOverridingMethodSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php', 'Generic_Sniffs_Commenting_DocCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php', 'Generic_Sniffs_Commenting_FixmeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Commenting/FixmeSniff.php', 'Generic_Sniffs_Commenting_TodoSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Commenting/TodoSniff.php', 'Generic_Sniffs_ControlStructures_InlineControlStructureSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php', 'Generic_Sniffs_Debug_CSSLintSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/CSSLintSniff.php', 'Generic_Sniffs_Debug_ClosureLinterSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/ClosureLinterSniff.php', 'Generic_Sniffs_Debug_ESLintSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/ESLintSniff.php', 'Generic_Sniffs_Debug_JSHintSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/JSHintSniff.php', 'Generic_Sniffs_Files_ByteOrderMarkSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/ByteOrderMarkSniff.php', 'Generic_Sniffs_Files_EndFileNewlineSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/EndFileNewlineSniff.php', 'Generic_Sniffs_Files_EndFileNoNewlineSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/EndFileNoNewlineSniff.php', 'Generic_Sniffs_Files_InlineHTMLSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/InlineHTMLSniff.php', 'Generic_Sniffs_Files_LineEndingsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/LineEndingsSniff.php', 'Generic_Sniffs_Files_LineLengthSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/LineLengthSniff.php', 'Generic_Sniffs_Files_LowercasedFilenameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/LowercasedFilenameSniff.php', 'Generic_Sniffs_Files_OneClassPerFileSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/OneClassPerFileSniff.php', 'Generic_Sniffs_Files_OneInterfacePerFileSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/OneInterfacePerFileSniff.php', 'Generic_Sniffs_Files_OneTraitPerFileSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/OneTraitPerFileSniff.php', 'Generic_Sniffs_Formatting_DisallowMultipleStatementsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php', 'Generic_Sniffs_Formatting_MultipleStatementAlignmentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php', 'Generic_Sniffs_Formatting_NoSpaceAfterCastSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/NoSpaceAfterCastSniff.php', 'Generic_Sniffs_Formatting_SpaceAfterCastSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/SpaceAfterCastSniff.php', 'Generic_Sniffs_Formatting_SpaceAfterNotSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/SpaceAfterNotSniff.php', 'Generic_Sniffs_Functions_CallTimePassByReferenceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/CallTimePassByReferenceSniff.php', 'Generic_Sniffs_Functions_FunctionCallArgumentSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php', 'Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php', 'Generic_Sniffs_Functions_OpeningFunctionBraceKernighanRitchieSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceKernighanRitchieSniff.php', 'Generic_Sniffs_Metrics_CyclomaticComplexitySniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php', 'Generic_Sniffs_Metrics_NestingLevelSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Metrics/NestingLevelSniff.php', 'Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php', 'Generic_Sniffs_NamingConventions_ConstructorNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php', 'Generic_Sniffs_NamingConventions_UpperCaseConstantNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php', 'Generic_Sniffs_PHP_BacktickOperatorSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/BacktickOperatorSniff.php', 'Generic_Sniffs_PHP_CharacterBeforePHPOpeningTagSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/CharacterBeforePHPOpeningTagSniff.php', 'Generic_Sniffs_PHP_ClosingPHPTagSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/ClosingPHPTagSniff.php', 'Generic_Sniffs_PHP_DeprecatedFunctionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/DeprecatedFunctionsSniff.php', 'Generic_Sniffs_PHP_DisallowAlternativePHPTagsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/DisallowAlternativePHPTagsSniff.php', 'Generic_Sniffs_PHP_DisallowShortOpenTagSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/DisallowShortOpenTagSniff.php', 'Generic_Sniffs_PHP_ForbiddenFunctionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php', 'Generic_Sniffs_PHP_LowerCaseConstantSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php', 'Generic_Sniffs_PHP_LowerCaseKeywordSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php', 'Generic_Sniffs_PHP_NoSilencedErrorsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/NoSilencedErrorsSniff.php', 'Generic_Sniffs_PHP_SAPIUsageSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php', 'Generic_Sniffs_PHP_SyntaxSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/SyntaxSniff.php', 'Generic_Sniffs_PHP_UpperCaseConstantSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php', 'Generic_Sniffs_Strings_UnnecessaryStringConcatSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Strings/UnnecessaryStringConcatSniff.php', 'Generic_Sniffs_VersionControl_SubversionPropertiesSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/VersionControl/SubversionPropertiesSniff.php', 'Generic_Sniffs_WhiteSpace_DisallowSpaceIndentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php', 'Generic_Sniffs_WhiteSpace_DisallowTabIndentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php', 'Generic_Sniffs_WhiteSpace_ScopeIndentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php', 'MySource_Sniffs_CSS_BrowserSpecificStylesSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/CSS/BrowserSpecificStylesSniff.php', 'MySource_Sniffs_Channels_DisallowSelfActionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/DisallowSelfActionsSniff.php', 'MySource_Sniffs_Channels_IncludeOwnSystemSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/IncludeOwnSystemSniff.php', 'MySource_Sniffs_Channels_IncludeSystemSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/IncludeSystemSniff.php', 'MySource_Sniffs_Channels_UnusedSystemSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/UnusedSystemSniff.php', 'MySource_Sniffs_Commenting_FunctionCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Commenting/FunctionCommentSniff.php', 'MySource_Sniffs_Debug_DebugCodeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Debug/DebugCodeSniff.php', 'MySource_Sniffs_Debug_FirebugConsoleSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Debug/FirebugConsoleSniff.php', 'MySource_Sniffs_Objects_AssignThisSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Objects/AssignThisSniff.php', 'MySource_Sniffs_Objects_CreateWidgetTypeCallbackSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php', 'MySource_Sniffs_Objects_DisallowNewWidgetSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Objects/DisallowNewWidgetSniff.php', 'MySource_Sniffs_PHP_AjaxNullComparisonSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/AjaxNullComparisonSniff.php', 'MySource_Sniffs_PHP_EvalObjectFactorySniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/EvalObjectFactorySniff.php', 'MySource_Sniffs_PHP_GetRequestDataSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/GetRequestDataSniff.php', 'MySource_Sniffs_PHP_ReturnFunctionValueSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/ReturnFunctionValueSniff.php', 'MySource_Sniffs_Strings_JoinStringsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Strings/JoinStringsSniff.php', 'PEAR_Sniffs_Classes_ClassDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php', 'PEAR_Sniffs_Commenting_ClassCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php', 'PEAR_Sniffs_Commenting_FileCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php', 'PEAR_Sniffs_Commenting_FunctionCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php', 'PEAR_Sniffs_Commenting_InlineCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/InlineCommentSniff.php', 'PEAR_Sniffs_ControlStructures_ControlSignatureSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/ControlStructures/ControlSignatureSniff.php', 'PEAR_Sniffs_ControlStructures_MultiLineConditionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/ControlStructures/MultiLineConditionSniff.php', 'PEAR_Sniffs_Files_IncludingFileSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Files/IncludingFileSniff.php', 'PEAR_Sniffs_Formatting_MultiLineAssignmentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Formatting/MultiLineAssignmentSniff.php', 'PEAR_Sniffs_Functions_FunctionCallSignatureSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php', 'PEAR_Sniffs_Functions_FunctionDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php', 'PEAR_Sniffs_Functions_ValidDefaultValueSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php', 'PEAR_Sniffs_NamingConventions_ValidClassNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidClassNameSniff.php', 'PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php', 'PEAR_Sniffs_NamingConventions_ValidVariableNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidVariableNameSniff.php', 'PEAR_Sniffs_WhiteSpace_ObjectOperatorIndentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php', 'PEAR_Sniffs_WhiteSpace_ScopeClosingBraceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php', 'PEAR_Sniffs_WhiteSpace_ScopeIndentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/WhiteSpace/ScopeIndentSniff.php', 'PHPUnit\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php', 'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php', 'PHPUnit\\Framework\\AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/AssertionFailedError.php', 'PHPUnit\\Framework\\BaseTestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/BaseTestListener.php', 'PHPUnit\\Framework\\CodeCoverageException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/CodeCoverageException.php', 'PHPUnit\\Framework\\Constraint\\ArrayHasKey' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', 'PHPUnit\\Framework\\Constraint\\ArraySubset' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', 'PHPUnit\\Framework\\Constraint\\Attribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', 'PHPUnit\\Framework\\Constraint\\Callback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', 'PHPUnit\\Framework\\Constraint\\ClassHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\ClassHasStaticAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', 'PHPUnit\\Framework\\Constraint\\Composite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', 'PHPUnit\\Framework\\Constraint\\Constraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Constraint.php', 'PHPUnit\\Framework\\Constraint\\Count' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Count.php', 'PHPUnit\\Framework\\Constraint\\DirectoryExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php', 'PHPUnit\\Framework\\Constraint\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', 'PHPUnit\\Framework\\Constraint\\ExceptionCode' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessage' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessageRegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegularExpression.php', 'PHPUnit\\Framework\\Constraint\\FileExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', 'PHPUnit\\Framework\\Constraint\\GreaterThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', 'PHPUnit\\Framework\\Constraint\\IsAnything' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', 'PHPUnit\\Framework\\Constraint\\IsEmpty' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', 'PHPUnit\\Framework\\Constraint\\IsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', 'PHPUnit\\Framework\\Constraint\\IsFalse' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', 'PHPUnit\\Framework\\Constraint\\IsFinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php', 'PHPUnit\\Framework\\Constraint\\IsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', 'PHPUnit\\Framework\\Constraint\\IsInfinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php', 'PHPUnit\\Framework\\Constraint\\IsInstanceOf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', 'PHPUnit\\Framework\\Constraint\\IsJson' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', 'PHPUnit\\Framework\\Constraint\\IsNan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php', 'PHPUnit\\Framework\\Constraint\\IsNull' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', 'PHPUnit\\Framework\\Constraint\\IsReadable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php', 'PHPUnit\\Framework\\Constraint\\IsTrue' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', 'PHPUnit\\Framework\\Constraint\\IsType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', 'PHPUnit\\Framework\\Constraint\\IsWritable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php', 'PHPUnit\\Framework\\Constraint\\JsonMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', 'PHPUnit\\Framework\\Constraint\\JsonMatchesErrorMessageProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php', 'PHPUnit\\Framework\\Constraint\\LessThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', 'PHPUnit\\Framework\\Constraint\\LogicalAnd' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalAnd.php', 'PHPUnit\\Framework\\Constraint\\LogicalNot' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalNot.php', 'PHPUnit\\Framework\\Constraint\\LogicalOr' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalOr.php', 'PHPUnit\\Framework\\Constraint\\LogicalXor' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LogicalXor.php', 'PHPUnit\\Framework\\Constraint\\ObjectHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\RegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/RegularExpression.php', 'PHPUnit\\Framework\\Constraint\\SameSize' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', 'PHPUnit\\Framework\\Constraint\\StringContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', 'PHPUnit\\Framework\\Constraint\\StringEndsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', 'PHPUnit\\Framework\\Constraint\\StringMatchesFormatDescription' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringMatchesFormatDescription.php', 'PHPUnit\\Framework\\Constraint\\StringStartsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', 'PHPUnit\\Framework\\Constraint\\TraversableContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsOnly' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', 'PHPUnit\\Framework\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.php', 'PHPUnit\\Framework\\DataProviderTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/DataProviderTestSuite.php', 'PHPUnit\\Framework\\Error\\Deprecated' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', 'PHPUnit\\Framework\\Error\\Error' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Error.php', 'PHPUnit\\Framework\\Error\\Notice' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Notice.php', 'PHPUnit\\Framework\\Error\\Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Warning.php', 'PHPUnit\\Framework\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception.php', 'PHPUnit\\Framework\\ExceptionWrapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', 'PHPUnit\\Framework\\ExpectationFailedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php', 'PHPUnit\\Framework\\IncompleteTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTest.php', 'PHPUnit\\Framework\\IncompleteTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', 'PHPUnit\\Framework\\IncompleteTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestError.php', 'PHPUnit\\Framework\\InvalidCoversTargetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php', 'PHPUnit\\Framework\\MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.php', 'PHPUnit\\Framework\\OutputError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/OutputError.php', 'PHPUnit\\Framework\\RiskyTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTest.php', 'PHPUnit\\Framework\\RiskyTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTestError.php', 'PHPUnit\\Framework\\SelfDescribing' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SelfDescribing.php', 'PHPUnit\\Framework\\SkippedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTest.php', 'PHPUnit\\Framework\\SkippedTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', 'PHPUnit\\Framework\\SkippedTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestError.php', 'PHPUnit\\Framework\\SkippedTestSuiteError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php', 'PHPUnit\\Framework\\SyntheticError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SyntheticError.php', 'PHPUnit\\Framework\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Test.php', 'PHPUnit\\Framework\\TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestCase.php', 'PHPUnit\\Framework\\TestFailure' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestFailure.php', 'PHPUnit\\Framework\\TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListener.php', 'PHPUnit\\Framework\\TestResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestResult.php', 'PHPUnit\\Framework\\TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite.php', 'PHPUnit\\Framework\\TestSuiteIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuiteIterator.php', 'PHPUnit\\Framework\\UnintentionallyCoveredCodeError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php', 'PHPUnit\\Framework\\Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Warning.php', 'PHPUnit\\Framework\\WarningTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/WarningTestCase.php', 'PHPUnit\\Runner\\BaseTestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', 'PHPUnit\\Runner\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Exception.php', 'PHPUnit\\Runner\\Filter\\ExcludeGroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\Factory' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Factory.php', 'PHPUnit\\Runner\\Filter\\GroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\IncludeGroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\NameFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php', 'PHPUnit\\Runner\\PhptTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/PhptTestCase.php', 'PHPUnit\\Runner\\StandardTestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', 'PHPUnit\\Runner\\TestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', 'PHPUnit\\Runner\\Version' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Version.php', 'PHPUnit\\TextUI\\Command' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Command.php', 'PHPUnit\\TextUI\\ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', 'PHPUnit\\TextUI\\TestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/TestRunner.php', 'PHPUnit\\Util\\Blacklist' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Blacklist.php', 'PHPUnit\\Util\\Configuration' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Configuration.php', 'PHPUnit\\Util\\ConfigurationGenerator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php', 'PHPUnit\\Util\\ErrorHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ErrorHandler.php', 'PHPUnit\\Util\\Fileloader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Fileloader.php', 'PHPUnit\\Util\\Filesystem' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filesystem.php', 'PHPUnit\\Util\\Filter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filter.php', 'PHPUnit\\Util\\Getopt' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Getopt.php', 'PHPUnit\\Util\\GlobalState' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/GlobalState.php', 'PHPUnit\\Util\\InvalidArgumentHelper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php', 'PHPUnit\\Util\\Log\\JUnit' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JUnit.php', 'PHPUnit\\Util\\Log\\TeamCity' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TeamCity.php', 'PHPUnit\\Util\\PHP\\AbstractPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php', 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php', 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php', 'PHPUnit\\Util\\Printer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Printer.php', 'PHPUnit\\Util\\RegularExpression' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/RegularExpression.php', 'PHPUnit\\Util\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Test.php', 'PHPUnit\\Util\\TestDox\\HtmlResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php', 'PHPUnit\\Util\\TestDox\\NamePrettifier' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', 'PHPUnit\\Util\\TestDox\\ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', 'PHPUnit\\Util\\TestDox\\TextResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php', 'PHPUnit\\Util\\TestDox\\XmlResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php', 'PHPUnit\\Util\\Type' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Type.php', 'PHPUnit\\Util\\Xml' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Xml.php', 'PHPUnit_Framework_MockObject_BadMethodCallException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Exception/BadMethodCallException.php', 'PHPUnit_Framework_MockObject_Builder_Identity' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Builder/Identity.php', 'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Builder/InvocationMocker.php', 'PHPUnit_Framework_MockObject_Builder_Match' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Builder/Match.php', 'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Builder/MethodNameMatch.php', 'PHPUnit_Framework_MockObject_Builder_Namespace' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Builder/Namespace.php', 'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Builder/ParametersMatch.php', 'PHPUnit_Framework_MockObject_Builder_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Builder/Stub.php', 'PHPUnit_Framework_MockObject_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Exception/Exception.php', 'PHPUnit_Framework_MockObject_Generator' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Generator.php', 'PHPUnit_Framework_MockObject_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Invocation.php', 'PHPUnit_Framework_MockObject_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/InvocationMocker.php', 'PHPUnit_Framework_MockObject_Invocation_Object' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Invocation/Object.php', 'PHPUnit_Framework_MockObject_Invocation_Static' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Invocation/Static.php', 'PHPUnit_Framework_MockObject_Invokable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Invokable.php', 'PHPUnit_Framework_MockObject_Matcher' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher.php', 'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/AnyInvokedCount.php', 'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/AnyParameters.php', 'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/ConsecutiveParameters.php', 'PHPUnit_Framework_MockObject_Matcher_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/Invocation.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtIndex.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtLeastCount.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtLeastOnce.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtMostCount.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedCount.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedRecorder.php', 'PHPUnit_Framework_MockObject_Matcher_MethodName' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/MethodName.php', 'PHPUnit_Framework_MockObject_Matcher_Parameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/Parameters.php', 'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Matcher/StatelessInvocation.php', 'PHPUnit_Framework_MockObject_MockBuilder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/MockBuilder.php', 'PHPUnit_Framework_MockObject_MockObject' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/MockObject.php', 'PHPUnit_Framework_MockObject_RuntimeException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Exception/RuntimeException.php', 'PHPUnit_Framework_MockObject_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub.php', 'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/ConsecutiveCalls.php', 'PHPUnit_Framework_MockObject_Stub_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/Exception.php', 'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/MatcherCollection.php', 'PHPUnit_Framework_MockObject_Stub_Return' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/Return.php', 'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/ReturnArgument.php', 'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/ReturnCallback.php', 'PHPUnit_Framework_MockObject_Stub_ReturnReference' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/ReturnReference.php', 'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/ReturnSelf.php', 'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Stub/ReturnValueMap.php', 'PHPUnit_Framework_MockObject_Verifiable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Verifiable.php', 'PHP_CodeSniffer' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer.php', 'PHP_CodeSniffer_CLI' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/CLI.php', 'PHP_CodeSniffer_DocGenerators_Generator' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/Generator.php', 'PHP_CodeSniffer_DocGenerators_HTML' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/HTML.php', 'PHP_CodeSniffer_DocGenerators_Markdown' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/Markdown.php', 'PHP_CodeSniffer_DocGenerators_Text' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/Text.php', 'PHP_CodeSniffer_Exception' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Exception.php', 'PHP_CodeSniffer_File' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/File.php', 'PHP_CodeSniffer_Fixer' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Fixer.php', 'PHP_CodeSniffer_Report' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Report.php', 'PHP_CodeSniffer_Reporting' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reporting.php', 'PHP_CodeSniffer_Reports_Cbf' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Cbf.php', 'PHP_CodeSniffer_Reports_Checkstyle' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Checkstyle.php', 'PHP_CodeSniffer_Reports_Csv' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Csv.php', 'PHP_CodeSniffer_Reports_Diff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Diff.php', 'PHP_CodeSniffer_Reports_Emacs' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Emacs.php', 'PHP_CodeSniffer_Reports_Full' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Full.php', 'PHP_CodeSniffer_Reports_Gitblame' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Gitblame.php', 'PHP_CodeSniffer_Reports_Hgblame' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Hgblame.php', 'PHP_CodeSniffer_Reports_Info' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Info.php', 'PHP_CodeSniffer_Reports_Json' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Json.php', 'PHP_CodeSniffer_Reports_Junit' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Junit.php', 'PHP_CodeSniffer_Reports_Notifysend' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Notifysend.php', 'PHP_CodeSniffer_Reports_Source' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Source.php', 'PHP_CodeSniffer_Reports_Summary' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Summary.php', 'PHP_CodeSniffer_Reports_Svnblame' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Svnblame.php', 'PHP_CodeSniffer_Reports_VersionControl' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/VersionControl.php', 'PHP_CodeSniffer_Reports_Xml' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Xml.php', 'PHP_CodeSniffer_Sniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Sniff.php', 'PHP_CodeSniffer_Standards_AbstractPatternSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/AbstractPatternSniff.php', 'PHP_CodeSniffer_Standards_AbstractScopeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/AbstractScopeSniff.php', 'PHP_CodeSniffer_Standards_AbstractVariableSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/AbstractVariableSniff.php', 'PHP_CodeSniffer_Standards_IncorrectPatternException' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/IncorrectPatternException.php', 'PHP_CodeSniffer_Tokenizers_CSS' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/CSS.php', 'PHP_CodeSniffer_Tokenizers_Comment' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/Comment.php', 'PHP_CodeSniffer_Tokenizers_JS' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/JS.php', 'PHP_CodeSniffer_Tokenizers_PHP' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/PHP.php', 'PHP_CodeSniffer_Tokens' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Tokens.php', 'PHP_Timer' => __DIR__ . '/..' . '/phpunit/php-timer/src/Timer.php', 'PHP_Token' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScope' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScopeAndVisibility' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ABSTRACT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AMPERSAND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AND_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ASYNC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AWAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BACKTICK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BAD_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOL_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BREAK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CALLABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CARET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CASE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CATCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_NAME_CONSTANT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLONE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COALESCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMA' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMPILER_HALT_OFFSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONCAT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONSTANT_ENCAPSED_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONTINUE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CURLY_OPEN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEFAULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOC_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_QUOTES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELLIPSIS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSEIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EMPTY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENCAPSED_AND_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDDECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDSWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDWHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_END_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENUM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EQUALS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EVAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXCLAMATION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXTENDS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINALLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNC_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GLOBAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GOTO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_HALT_COMPILER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IMPLEMENTS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INLINE_HTML' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTANCEOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTEADOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INTERFACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ISSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_GREATER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_SMALLER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Includes' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_JOIN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LAMBDA_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LAMBDA_CP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LAMBDA_OP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LINE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LIST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_XOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_METHOD_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MOD_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MUL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NAMESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NEW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_SEPARATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NUM_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ONUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG_WITH_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PERCENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PIPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRINT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRIVATE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PROTECTED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PUBLIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_QUESTION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_RETURN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SEMICOLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SHAPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SPACESHIP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_START_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STATIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_VARNAME' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SUPER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Stream' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream.php', 'PHP_Token_Stream_CachingFactory' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', 'PHP_Token_THROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TILDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TYPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TYPELIST_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TYPELIST_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_VAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_VARIABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHERE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_ATTRIBUTE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_CATEGORY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_CATEGORY_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_CHILDREN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_REQUIRED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_TAG_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_TAG_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_TEXT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XOR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD_FROM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', 'PSR1_Sniffs_Classes_ClassDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php', 'PSR1_Sniffs_Files_SideEffectsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php', 'PSR1_Sniffs_Methods_CamelCapsMethodNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR1/Sniffs/Methods/CamelCapsMethodNameSniff.php', 'PSR2_Sniffs_Classes_ClassDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php', 'PSR2_Sniffs_Classes_PropertyDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php', 'PSR2_Sniffs_ControlStructures_ControlStructureSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/ControlStructures/ControlStructureSpacingSniff.php', 'PSR2_Sniffs_ControlStructures_ElseIfDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/ControlStructures/ElseIfDeclarationSniff.php', 'PSR2_Sniffs_ControlStructures_SwitchDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php', 'PSR2_Sniffs_Files_ClosingTagSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Files/ClosingTagSniff.php', 'PSR2_Sniffs_Files_EndFileNewlineSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Files/EndFileNewlineSniff.php', 'PSR2_Sniffs_Methods_FunctionCallSignatureSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Methods/FunctionCallSignatureSniff.php', 'PSR2_Sniffs_Methods_FunctionClosingBraceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Methods/FunctionClosingBraceSniff.php', 'PSR2_Sniffs_Methods_MethodDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Methods/MethodDeclarationSniff.php', 'PSR2_Sniffs_Namespaces_NamespaceDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php', 'PSR2_Sniffs_Namespaces_UseDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php', 'PharIo\\Manifest\\Application' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Application.php', 'PharIo\\Manifest\\ApplicationName' => __DIR__ . '/..' . '/phar-io/manifest/src/values/ApplicationName.php', 'PharIo\\Manifest\\Author' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Author.php', 'PharIo\\Manifest\\AuthorCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/AuthorCollection.php', 'PharIo\\Manifest\\AuthorCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/AuthorCollectionIterator.php', 'PharIo\\Manifest\\AuthorElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/AuthorElement.php', 'PharIo\\Manifest\\AuthorElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/AuthorElementCollection.php', 'PharIo\\Manifest\\BundledComponent' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponent.php', 'PharIo\\Manifest\\BundledComponentCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponentCollection.php', 'PharIo\\Manifest\\BundledComponentCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', 'PharIo\\Manifest\\BundlesElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/BundlesElement.php', 'PharIo\\Manifest\\ComponentElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ComponentElement.php', 'PharIo\\Manifest\\ComponentElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ComponentElementCollection.php', 'PharIo\\Manifest\\ContainsElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ContainsElement.php', 'PharIo\\Manifest\\CopyrightElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/CopyrightElement.php', 'PharIo\\Manifest\\CopyrightInformation' => __DIR__ . '/..' . '/phar-io/manifest/src/values/CopyrightInformation.php', 'PharIo\\Manifest\\ElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ElementCollection.php', 'PharIo\\Manifest\\Email' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Email.php', 'PharIo\\Manifest\\Exception' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/Exception.php', 'PharIo\\Manifest\\ExtElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtElement.php', 'PharIo\\Manifest\\ExtElementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtElementCollection.php', 'PharIo\\Manifest\\Extension' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Extension.php', 'PharIo\\Manifest\\ExtensionElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ExtensionElement.php', 'PharIo\\Manifest\\InvalidApplicationNameException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', 'PharIo\\Manifest\\InvalidEmailException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidEmailException.php', 'PharIo\\Manifest\\InvalidUrlException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/InvalidUrlException.php', 'PharIo\\Manifest\\Library' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Library.php', 'PharIo\\Manifest\\License' => __DIR__ . '/..' . '/phar-io/manifest/src/values/License.php', 'PharIo\\Manifest\\LicenseElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/LicenseElement.php', 'PharIo\\Manifest\\Manifest' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Manifest.php', 'PharIo\\Manifest\\ManifestDocument' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ManifestDocument.php', 'PharIo\\Manifest\\ManifestDocumentException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentException.php', 'PharIo\\Manifest\\ManifestDocumentLoadingException' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ManifestDocumentLoadingException.php', 'PharIo\\Manifest\\ManifestDocumentMapper' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestDocumentMapper.php', 'PharIo\\Manifest\\ManifestDocumentMapperException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', 'PharIo\\Manifest\\ManifestElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/ManifestElement.php', 'PharIo\\Manifest\\ManifestElementException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestElementException.php', 'PharIo\\Manifest\\ManifestLoader' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestLoader.php', 'PharIo\\Manifest\\ManifestLoaderException' => __DIR__ . '/..' . '/phar-io/manifest/src/exceptions/ManifestLoaderException.php', 'PharIo\\Manifest\\ManifestSerializer' => __DIR__ . '/..' . '/phar-io/manifest/src/ManifestSerializer.php', 'PharIo\\Manifest\\PhpElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/PhpElement.php', 'PharIo\\Manifest\\PhpExtensionRequirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/PhpExtensionRequirement.php', 'PharIo\\Manifest\\PhpVersionRequirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/PhpVersionRequirement.php', 'PharIo\\Manifest\\Requirement' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Requirement.php', 'PharIo\\Manifest\\RequirementCollection' => __DIR__ . '/..' . '/phar-io/manifest/src/values/RequirementCollection.php', 'PharIo\\Manifest\\RequirementCollectionIterator' => __DIR__ . '/..' . '/phar-io/manifest/src/values/RequirementCollectionIterator.php', 'PharIo\\Manifest\\RequiresElement' => __DIR__ . '/..' . '/phar-io/manifest/src/xml/RequiresElement.php', 'PharIo\\Manifest\\Type' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Type.php', 'PharIo\\Manifest\\Url' => __DIR__ . '/..' . '/phar-io/manifest/src/values/Url.php', 'PharIo\\Version\\AbstractVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/AbstractVersionConstraint.php', 'PharIo\\Version\\AndVersionConstraintGroup' => __DIR__ . '/..' . '/phar-io/version/src/AndVersionConstraintGroup.php', 'PharIo\\Version\\AnyVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/AnyVersionConstraint.php', 'PharIo\\Version\\ExactVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/ExactVersionConstraint.php', 'PharIo\\Version\\Exception' => __DIR__ . '/..' . '/phar-io/version/src/Exception.php', 'PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/GreaterThanOrEqualToVersionConstraint.php', 'PharIo\\Version\\InvalidVersionException' => __DIR__ . '/..' . '/phar-io/version/src/InvalidVersionException.php', 'PharIo\\Version\\OrVersionConstraintGroup' => __DIR__ . '/..' . '/phar-io/version/src/OrVersionConstraintGroup.php', 'PharIo\\Version\\PreReleaseSuffix' => __DIR__ . '/..' . '/phar-io/version/src/PreReleaseSuffix.php', 'PharIo\\Version\\SpecificMajorAndMinorVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/SpecificMajorAndMinorVersionConstraint.php', 'PharIo\\Version\\SpecificMajorVersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/SpecificMajorVersionConstraint.php', 'PharIo\\Version\\UnsupportedVersionConstraintException' => __DIR__ . '/..' . '/phar-io/version/src/UnsupportedVersionConstraintException.php', 'PharIo\\Version\\Version' => __DIR__ . '/..' . '/phar-io/version/src/Version.php', 'PharIo\\Version\\VersionConstraint' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraint.php', 'PharIo\\Version\\VersionConstraintParser' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintParser.php', 'PharIo\\Version\\VersionConstraintValue' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintValue.php', 'PharIo\\Version\\VersionNumber' => __DIR__ . '/..' . '/phar-io/version/src/VersionNumber.php', 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage.php', 'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Driver.php', 'SebastianBergmann\\CodeCoverage\\Driver\\HHVM' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/HHVM.php', 'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Xdebug.php', 'SebastianBergmann\\CodeCoverage\\Exception' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/Exception.php', 'SebastianBergmann\\CodeCoverage\\Filter' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Filter.php', 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', 'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php', 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Builder.php', 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Directory.php', 'SebastianBergmann\\CodeCoverage\\Node\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/File.php', 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Iterator.php', 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Clover.php', 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Crap4j.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/PHP.php', 'SebastianBergmann\\CodeCoverage\\Report\\Text' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Text.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\BuildInformation' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Source' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Source.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', 'SebastianBergmann\\CodeCoverage\\RuntimeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php', 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', 'SebastianBergmann\\CodeCoverage\\Util' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Util.php', 'SebastianBergmann\\CodeCoverage\\Version' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Version.php', 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => __DIR__ . '/..' . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', 'SebastianBergmann\\Comparator\\ArrayComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ArrayComparator.php', 'SebastianBergmann\\Comparator\\Comparator' => __DIR__ . '/..' . '/sebastian/comparator/src/Comparator.php', 'SebastianBergmann\\Comparator\\ComparisonFailure' => __DIR__ . '/..' . '/sebastian/comparator/src/ComparisonFailure.php', 'SebastianBergmann\\Comparator\\DOMNodeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DOMNodeComparator.php', 'SebastianBergmann\\Comparator\\DateTimeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DateTimeComparator.php', 'SebastianBergmann\\Comparator\\DoubleComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DoubleComparator.php', 'SebastianBergmann\\Comparator\\ExceptionComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ExceptionComparator.php', 'SebastianBergmann\\Comparator\\Factory' => __DIR__ . '/..' . '/sebastian/comparator/src/Factory.php', 'SebastianBergmann\\Comparator\\MockObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/MockObjectComparator.php', 'SebastianBergmann\\Comparator\\NumericComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/NumericComparator.php', 'SebastianBergmann\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ObjectComparator.php', 'SebastianBergmann\\Comparator\\ResourceComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ResourceComparator.php', 'SebastianBergmann\\Comparator\\ScalarComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ScalarComparator.php', 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/SplObjectStorageComparator.php', 'SebastianBergmann\\Comparator\\TypeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/TypeComparator.php', 'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php', 'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php', 'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php', 'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php', 'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', 'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', 'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php', 'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php', 'SebastianBergmann\\Environment\\Console' => __DIR__ . '/..' . '/sebastian/environment/src/Console.php', 'SebastianBergmann\\Environment\\Runtime' => __DIR__ . '/..' . '/sebastian/environment/src/Runtime.php', 'SebastianBergmann\\Exporter\\Exporter' => __DIR__ . '/..' . '/sebastian/exporter/src/Exporter.php', 'SebastianBergmann\\GlobalState\\Blacklist' => __DIR__ . '/..' . '/sebastian/global-state/src/Blacklist.php', 'SebastianBergmann\\GlobalState\\CodeExporter' => __DIR__ . '/..' . '/sebastian/global-state/src/CodeExporter.php', 'SebastianBergmann\\GlobalState\\Exception' => __DIR__ . '/..' . '/sebastian/global-state/src/exceptions/Exception.php', 'SebastianBergmann\\GlobalState\\Restorer' => __DIR__ . '/..' . '/sebastian/global-state/src/Restorer.php', 'SebastianBergmann\\GlobalState\\RuntimeException' => __DIR__ . '/..' . '/sebastian/global-state/src/exceptions/RuntimeException.php', 'SebastianBergmann\\GlobalState\\Snapshot' => __DIR__ . '/..' . '/sebastian/global-state/src/Snapshot.php', 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Enumerator.php', 'SebastianBergmann\\ObjectEnumerator\\Exception' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Exception.php', 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\Exception' => __DIR__ . '/..' . '/sebastian/object-reflector/src/Exception.php', 'SebastianBergmann\\ObjectReflector\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-reflector/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\ObjectReflector' => __DIR__ . '/..' . '/sebastian/object-reflector/src/ObjectReflector.php', 'SebastianBergmann\\RecursionContext\\Context' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Context.php', 'SebastianBergmann\\RecursionContext\\Exception' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Exception.php', 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/recursion-context/src/InvalidArgumentException.php', 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => __DIR__ . '/..' . '/sebastian/resource-operations/src/ResourceOperations.php', 'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php', 'Squiz_Sniffs_Arrays_ArrayBracketSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Arrays/ArrayBracketSpacingSniff.php', 'Squiz_Sniffs_Arrays_ArrayDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php', 'Squiz_Sniffs_CSS_ClassDefinitionClosingBraceSpaceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ClassDefinitionClosingBraceSpaceSniff.php', 'Squiz_Sniffs_CSS_ClassDefinitionNameSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ClassDefinitionNameSpacingSniff.php', 'Squiz_Sniffs_CSS_ClassDefinitionOpeningBraceSpaceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ClassDefinitionOpeningBraceSpaceSniff.php', 'Squiz_Sniffs_CSS_ColonSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ColonSpacingSniff.php', 'Squiz_Sniffs_CSS_ColourDefinitionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ColourDefinitionSniff.php', 'Squiz_Sniffs_CSS_DisallowMultipleStyleDefinitionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/DisallowMultipleStyleDefinitionsSniff.php', 'Squiz_Sniffs_CSS_DuplicateClassDefinitionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/DuplicateClassDefinitionSniff.php', 'Squiz_Sniffs_CSS_DuplicateStyleDefinitionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/DuplicateStyleDefinitionSniff.php', 'Squiz_Sniffs_CSS_EmptyClassDefinitionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/EmptyClassDefinitionSniff.php', 'Squiz_Sniffs_CSS_EmptyStyleDefinitionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/EmptyStyleDefinitionSniff.php', 'Squiz_Sniffs_CSS_ForbiddenStylesSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ForbiddenStylesSniff.php', 'Squiz_Sniffs_CSS_IndentationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/IndentationSniff.php', 'Squiz_Sniffs_CSS_LowercaseStyleDefinitionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/LowercaseStyleDefinitionSniff.php', 'Squiz_Sniffs_CSS_MissingColonSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/MissingColonSniff.php', 'Squiz_Sniffs_CSS_NamedColoursSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/NamedColoursSniff.php', 'Squiz_Sniffs_CSS_OpacitySniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/OpacitySniff.php', 'Squiz_Sniffs_CSS_SemicolonSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/SemicolonSpacingSniff.php', 'Squiz_Sniffs_CSS_ShorthandSizeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ShorthandSizeSniff.php', 'Squiz_Sniffs_Classes_ClassDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/ClassDeclarationSniff.php', 'Squiz_Sniffs_Classes_ClassFileNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/ClassFileNameSniff.php', 'Squiz_Sniffs_Classes_DuplicatePropertySniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/DuplicatePropertySniff.php', 'Squiz_Sniffs_Classes_LowercaseClassKeywordsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/LowercaseClassKeywordsSniff.php', 'Squiz_Sniffs_Classes_SelfMemberReferenceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php', 'Squiz_Sniffs_Classes_ValidClassNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php', 'Squiz_Sniffs_Commenting_BlockCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php', 'Squiz_Sniffs_Commenting_ClassCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php', 'Squiz_Sniffs_Commenting_ClosingDeclarationCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php', 'Squiz_Sniffs_Commenting_DocCommentAlignmentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php', 'Squiz_Sniffs_Commenting_EmptyCatchCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/EmptyCatchCommentSniff.php', 'Squiz_Sniffs_Commenting_FileCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php', 'Squiz_Sniffs_Commenting_FunctionCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php', 'Squiz_Sniffs_Commenting_FunctionCommentThrowTagSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php', 'Squiz_Sniffs_Commenting_InlineCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php', 'Squiz_Sniffs_Commenting_LongConditionClosingCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/LongConditionClosingCommentSniff.php', 'Squiz_Sniffs_Commenting_PostStatementCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php', 'Squiz_Sniffs_Commenting_VariableCommentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php', 'Squiz_Sniffs_ControlStructures_ControlSignatureSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ControlSignatureSniff.php', 'Squiz_Sniffs_ControlStructures_ElseIfDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ElseIfDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_ForEachLoopDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ForEachLoopDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_ForLoopDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ForLoopDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_InlineIfDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/InlineIfDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_LowercaseDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/LowercaseDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_SwitchDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/SwitchDeclarationSniff.php', 'Squiz_Sniffs_Debug_JSLintSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Debug/JSLintSniff.php', 'Squiz_Sniffs_Debug_JavaScriptLintSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php', 'Squiz_Sniffs_Files_FileExtensionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Files/FileExtensionSniff.php', 'Squiz_Sniffs_Formatting_OperatorBracketSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php', 'Squiz_Sniffs_Functions_FunctionDeclarationArgumentSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php', 'Squiz_Sniffs_Functions_FunctionDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/FunctionDeclarationSniff.php', 'Squiz_Sniffs_Functions_FunctionDuplicateArgumentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/FunctionDuplicateArgumentSniff.php', 'Squiz_Sniffs_Functions_GlobalFunctionSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/GlobalFunctionSniff.php', 'Squiz_Sniffs_Functions_LowercaseFunctionKeywordsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/LowercaseFunctionKeywordsSniff.php', 'Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/MultiLineFunctionDeclarationSniff.php', 'Squiz_Sniffs_NamingConventions_ValidFunctionNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/NamingConventions/ValidFunctionNameSniff.php', 'Squiz_Sniffs_NamingConventions_ValidVariableNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php', 'Squiz_Sniffs_Objects_DisallowObjectStringIndexSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Objects/DisallowObjectStringIndexSniff.php', 'Squiz_Sniffs_Objects_ObjectInstantiationSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php', 'Squiz_Sniffs_Objects_ObjectMemberCommaSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Objects/ObjectMemberCommaSniff.php', 'Squiz_Sniffs_Operators_ComparisonOperatorUsageSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php', 'Squiz_Sniffs_Operators_IncrementDecrementUsageSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Operators/IncrementDecrementUsageSniff.php', 'Squiz_Sniffs_Operators_ValidLogicalOperatorsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Operators/ValidLogicalOperatorsSniff.php', 'Squiz_Sniffs_PHP_CommentedOutCodeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/CommentedOutCodeSniff.php', 'Squiz_Sniffs_PHP_DisallowBooleanStatementSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowBooleanStatementSniff.php', 'Squiz_Sniffs_PHP_DisallowComparisonAssignmentSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php', 'Squiz_Sniffs_PHP_DisallowInlineIfSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowInlineIfSniff.php', 'Squiz_Sniffs_PHP_DisallowMultipleAssignmentsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php', 'Squiz_Sniffs_PHP_DisallowObEndFlushSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowObEndFlushSniff.php', 'Squiz_Sniffs_PHP_DisallowSizeFunctionsInLoopsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php', 'Squiz_Sniffs_PHP_DiscouragedFunctionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DiscouragedFunctionsSniff.php', 'Squiz_Sniffs_PHP_EmbeddedPhpSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php', 'Squiz_Sniffs_PHP_EvalSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/EvalSniff.php', 'Squiz_Sniffs_PHP_ForbiddenFunctionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/ForbiddenFunctionsSniff.php', 'Squiz_Sniffs_PHP_GlobalKeywordSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/GlobalKeywordSniff.php', 'Squiz_Sniffs_PHP_HeredocSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/HeredocSniff.php', 'Squiz_Sniffs_PHP_InnerFunctionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php', 'Squiz_Sniffs_PHP_LowercasePHPFunctionsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php', 'Squiz_Sniffs_PHP_NonExecutableCodeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php', 'Squiz_Sniffs_Scope_MemberVarScopeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Scope/MemberVarScopeSniff.php', 'Squiz_Sniffs_Scope_MethodScopeSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php', 'Squiz_Sniffs_Scope_StaticThisUsageSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php', 'Squiz_Sniffs_Strings_ConcatenationSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php', 'Squiz_Sniffs_Strings_DoubleQuoteUsageSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Strings/DoubleQuoteUsageSniff.php', 'Squiz_Sniffs_Strings_EchoedStringsSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Strings/EchoedStringsSniff.php', 'Squiz_Sniffs_WhiteSpace_CastSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/CastSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_ControlStructureSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_FunctionClosingBraceSpaceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php', 'Squiz_Sniffs_WhiteSpace_FunctionOpeningBraceSpaceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/FunctionOpeningBraceSpaceSniff.php', 'Squiz_Sniffs_WhiteSpace_FunctionSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/FunctionSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_LanguageConstructSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_LogicalOperatorSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/LogicalOperatorSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_MemberVarSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_ObjectOperatorSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_OperatorSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_PropertyLabelSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/PropertyLabelSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_ScopeClosingBraceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php', 'Squiz_Sniffs_WhiteSpace_ScopeKeywordSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_SemicolonSpacingSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/SemicolonSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_SuperfluousWhitespaceSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php', 'Text_Template' => __DIR__ . '/..' . '/phpunit/php-text-template/src/Template.php', 'TheSeer\\Tokenizer\\Exception' => __DIR__ . '/..' . '/theseer/tokenizer/src/Exception.php', 'TheSeer\\Tokenizer\\NamespaceUri' => __DIR__ . '/..' . '/theseer/tokenizer/src/NamespaceUri.php', 'TheSeer\\Tokenizer\\NamespaceUriException' => __DIR__ . '/..' . '/theseer/tokenizer/src/NamespaceUriException.php', 'TheSeer\\Tokenizer\\Token' => __DIR__ . '/..' . '/theseer/tokenizer/src/Token.php', 'TheSeer\\Tokenizer\\TokenCollection' => __DIR__ . '/..' . '/theseer/tokenizer/src/TokenCollection.php', 'TheSeer\\Tokenizer\\TokenCollectionException' => __DIR__ . '/..' . '/theseer/tokenizer/src/TokenCollectionException.php', 'TheSeer\\Tokenizer\\Tokenizer' => __DIR__ . '/..' . '/theseer/tokenizer/src/Tokenizer.php', 'TheSeer\\Tokenizer\\XMLSerializer' => __DIR__ . '/..' . '/theseer/tokenizer/src/XMLSerializer.php', 'Zend_Sniffs_Debug_CodeAnalyzerSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php', 'Zend_Sniffs_Files_ClosingTagSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Zend/Sniffs/Files/ClosingTagSniff.php', 'Zend_Sniffs_NamingConventions_ValidVariableNameSniff' => __DIR__ . '/..' . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitc4909e075a7b68a94b7e4489eb2bb42e::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitc4909e075a7b68a94b7e4489eb2bb42e::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInitc4909e075a7b68a94b7e4489eb2bb42e::$prefixesPsr0; $loader->classMap = ComposerStaticInitc4909e075a7b68a94b7e4489eb2bb42e::$classMap; }, null, ClassLoader::class); } } array($vendorDir . '/phpspec/prophecy/src'), 'Flow\\JSONPath\\Test' => array($vendorDir . '/flow/jsonpath/tests'), 'Flow\\JSONPath' => array($vendorDir . '/flow/jsonpath/src'), 'Codeception\\' => array($vendorDir . '/codeception/specify/src'), 'Behat\\Gherkin' => array($vendorDir . '/behat/gherkin/src'), ); $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php', 'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php', 'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php', 'Generic_Sniffs_Arrays_DisallowLongArraySyntaxSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Arrays/DisallowLongArraySyntaxSniff.php', 'Generic_Sniffs_Arrays_DisallowShortArraySyntaxSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php', 'Generic_Sniffs_Classes_DuplicateClassNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php', 'Generic_Sniffs_Classes_OpeningBraceSameLineSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Classes/OpeningBraceSameLineSniff.php', 'Generic_Sniffs_CodeAnalysis_EmptyStatementSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/EmptyStatementSniff.php', 'Generic_Sniffs_CodeAnalysis_ForLoopShouldBeWhileLoopSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/ForLoopShouldBeWhileLoopSniff.php', 'Generic_Sniffs_CodeAnalysis_ForLoopWithTestFunctionCallSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/ForLoopWithTestFunctionCallSniff.php', 'Generic_Sniffs_CodeAnalysis_JumbledIncrementerSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/JumbledIncrementerSniff.php', 'Generic_Sniffs_CodeAnalysis_UnconditionalIfStatementSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UnconditionalIfStatementSniff.php', 'Generic_Sniffs_CodeAnalysis_UnnecessaryFinalModifierSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UnnecessaryFinalModifierSniff.php', 'Generic_Sniffs_CodeAnalysis_UnusedFunctionParameterSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UnusedFunctionParameterSniff.php', 'Generic_Sniffs_CodeAnalysis_UselessOverridingMethodSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/CodeAnalysis/UselessOverridingMethodSniff.php', 'Generic_Sniffs_Commenting_DocCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Commenting/DocCommentSniff.php', 'Generic_Sniffs_Commenting_FixmeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Commenting/FixmeSniff.php', 'Generic_Sniffs_Commenting_TodoSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Commenting/TodoSniff.php', 'Generic_Sniffs_ControlStructures_InlineControlStructureSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php', 'Generic_Sniffs_Debug_CSSLintSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/CSSLintSniff.php', 'Generic_Sniffs_Debug_ClosureLinterSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/ClosureLinterSniff.php', 'Generic_Sniffs_Debug_ESLintSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/ESLintSniff.php', 'Generic_Sniffs_Debug_JSHintSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Debug/JSHintSniff.php', 'Generic_Sniffs_Files_ByteOrderMarkSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/ByteOrderMarkSniff.php', 'Generic_Sniffs_Files_EndFileNewlineSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/EndFileNewlineSniff.php', 'Generic_Sniffs_Files_EndFileNoNewlineSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/EndFileNoNewlineSniff.php', 'Generic_Sniffs_Files_InlineHTMLSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/InlineHTMLSniff.php', 'Generic_Sniffs_Files_LineEndingsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/LineEndingsSniff.php', 'Generic_Sniffs_Files_LineLengthSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/LineLengthSniff.php', 'Generic_Sniffs_Files_LowercasedFilenameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/LowercasedFilenameSniff.php', 'Generic_Sniffs_Files_OneClassPerFileSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/OneClassPerFileSniff.php', 'Generic_Sniffs_Files_OneInterfacePerFileSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/OneInterfacePerFileSniff.php', 'Generic_Sniffs_Files_OneTraitPerFileSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Files/OneTraitPerFileSniff.php', 'Generic_Sniffs_Formatting_DisallowMultipleStatementsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/DisallowMultipleStatementsSniff.php', 'Generic_Sniffs_Formatting_MultipleStatementAlignmentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php', 'Generic_Sniffs_Formatting_NoSpaceAfterCastSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/NoSpaceAfterCastSniff.php', 'Generic_Sniffs_Formatting_SpaceAfterCastSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/SpaceAfterCastSniff.php', 'Generic_Sniffs_Formatting_SpaceAfterNotSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Formatting/SpaceAfterNotSniff.php', 'Generic_Sniffs_Functions_CallTimePassByReferenceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/CallTimePassByReferenceSniff.php', 'Generic_Sniffs_Functions_FunctionCallArgumentSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php', 'Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php', 'Generic_Sniffs_Functions_OpeningFunctionBraceKernighanRitchieSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Functions/OpeningFunctionBraceKernighanRitchieSniff.php', 'Generic_Sniffs_Metrics_CyclomaticComplexitySniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Metrics/CyclomaticComplexitySniff.php', 'Generic_Sniffs_Metrics_NestingLevelSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Metrics/NestingLevelSniff.php', 'Generic_Sniffs_NamingConventions_CamelCapsFunctionNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php', 'Generic_Sniffs_NamingConventions_ConstructorNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/NamingConventions/ConstructorNameSniff.php', 'Generic_Sniffs_NamingConventions_UpperCaseConstantNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php', 'Generic_Sniffs_PHP_BacktickOperatorSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/BacktickOperatorSniff.php', 'Generic_Sniffs_PHP_CharacterBeforePHPOpeningTagSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/CharacterBeforePHPOpeningTagSniff.php', 'Generic_Sniffs_PHP_ClosingPHPTagSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/ClosingPHPTagSniff.php', 'Generic_Sniffs_PHP_DeprecatedFunctionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/DeprecatedFunctionsSniff.php', 'Generic_Sniffs_PHP_DisallowAlternativePHPTagsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/DisallowAlternativePHPTagsSniff.php', 'Generic_Sniffs_PHP_DisallowShortOpenTagSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/DisallowShortOpenTagSniff.php', 'Generic_Sniffs_PHP_ForbiddenFunctionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php', 'Generic_Sniffs_PHP_LowerCaseConstantSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/LowerCaseConstantSniff.php', 'Generic_Sniffs_PHP_LowerCaseKeywordSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/LowerCaseKeywordSniff.php', 'Generic_Sniffs_PHP_NoSilencedErrorsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/NoSilencedErrorsSniff.php', 'Generic_Sniffs_PHP_SAPIUsageSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/SAPIUsageSniff.php', 'Generic_Sniffs_PHP_SyntaxSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/SyntaxSniff.php', 'Generic_Sniffs_PHP_UpperCaseConstantSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/PHP/UpperCaseConstantSniff.php', 'Generic_Sniffs_Strings_UnnecessaryStringConcatSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/Strings/UnnecessaryStringConcatSniff.php', 'Generic_Sniffs_VersionControl_SubversionPropertiesSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/VersionControl/SubversionPropertiesSniff.php', 'Generic_Sniffs_WhiteSpace_DisallowSpaceIndentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php', 'Generic_Sniffs_WhiteSpace_DisallowTabIndentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php', 'Generic_Sniffs_WhiteSpace_ScopeIndentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php', 'MySource_Sniffs_CSS_BrowserSpecificStylesSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/CSS/BrowserSpecificStylesSniff.php', 'MySource_Sniffs_Channels_DisallowSelfActionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/DisallowSelfActionsSniff.php', 'MySource_Sniffs_Channels_IncludeOwnSystemSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/IncludeOwnSystemSniff.php', 'MySource_Sniffs_Channels_IncludeSystemSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/IncludeSystemSniff.php', 'MySource_Sniffs_Channels_UnusedSystemSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Channels/UnusedSystemSniff.php', 'MySource_Sniffs_Commenting_FunctionCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Commenting/FunctionCommentSniff.php', 'MySource_Sniffs_Debug_DebugCodeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Debug/DebugCodeSniff.php', 'MySource_Sniffs_Debug_FirebugConsoleSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Debug/FirebugConsoleSniff.php', 'MySource_Sniffs_Objects_AssignThisSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Objects/AssignThisSniff.php', 'MySource_Sniffs_Objects_CreateWidgetTypeCallbackSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Objects/CreateWidgetTypeCallbackSniff.php', 'MySource_Sniffs_Objects_DisallowNewWidgetSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Objects/DisallowNewWidgetSniff.php', 'MySource_Sniffs_PHP_AjaxNullComparisonSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/AjaxNullComparisonSniff.php', 'MySource_Sniffs_PHP_EvalObjectFactorySniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/EvalObjectFactorySniff.php', 'MySource_Sniffs_PHP_GetRequestDataSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/GetRequestDataSniff.php', 'MySource_Sniffs_PHP_ReturnFunctionValueSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/PHP/ReturnFunctionValueSniff.php', 'MySource_Sniffs_Strings_JoinStringsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/MySource/Sniffs/Strings/JoinStringsSniff.php', 'PEAR_Sniffs_Classes_ClassDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php', 'PEAR_Sniffs_Commenting_ClassCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/ClassCommentSniff.php', 'PEAR_Sniffs_Commenting_FileCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/FileCommentSniff.php', 'PEAR_Sniffs_Commenting_FunctionCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/FunctionCommentSniff.php', 'PEAR_Sniffs_Commenting_InlineCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Commenting/InlineCommentSniff.php', 'PEAR_Sniffs_ControlStructures_ControlSignatureSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/ControlStructures/ControlSignatureSniff.php', 'PEAR_Sniffs_ControlStructures_MultiLineConditionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/ControlStructures/MultiLineConditionSniff.php', 'PEAR_Sniffs_Files_IncludingFileSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Files/IncludingFileSniff.php', 'PEAR_Sniffs_Formatting_MultiLineAssignmentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Formatting/MultiLineAssignmentSniff.php', 'PEAR_Sniffs_Functions_FunctionCallSignatureSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php', 'PEAR_Sniffs_Functions_FunctionDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php', 'PEAR_Sniffs_Functions_ValidDefaultValueSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/Functions/ValidDefaultValueSniff.php', 'PEAR_Sniffs_NamingConventions_ValidClassNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidClassNameSniff.php', 'PEAR_Sniffs_NamingConventions_ValidFunctionNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidFunctionNameSniff.php', 'PEAR_Sniffs_NamingConventions_ValidVariableNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/NamingConventions/ValidVariableNameSniff.php', 'PEAR_Sniffs_WhiteSpace_ObjectOperatorIndentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php', 'PEAR_Sniffs_WhiteSpace_ScopeClosingBraceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php', 'PEAR_Sniffs_WhiteSpace_ScopeIndentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PEAR/Sniffs/WhiteSpace/ScopeIndentSniff.php', 'PHPUnit\\Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php', 'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php', 'PHPUnit\\Framework\\AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/AssertionFailedError.php', 'PHPUnit\\Framework\\BaseTestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/BaseTestListener.php', 'PHPUnit\\Framework\\CodeCoverageException' => $vendorDir . '/phpunit/phpunit/src/Framework/CodeCoverageException.php', 'PHPUnit\\Framework\\Constraint\\ArrayHasKey' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', 'PHPUnit\\Framework\\Constraint\\ArraySubset' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', 'PHPUnit\\Framework\\Constraint\\Attribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', 'PHPUnit\\Framework\\Constraint\\Callback' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', 'PHPUnit\\Framework\\Constraint\\ClassHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\ClassHasStaticAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', 'PHPUnit\\Framework\\Constraint\\Composite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', 'PHPUnit\\Framework\\Constraint\\Constraint' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Constraint.php', 'PHPUnit\\Framework\\Constraint\\Count' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Count.php', 'PHPUnit\\Framework\\Constraint\\DirectoryExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php', 'PHPUnit\\Framework\\Constraint\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', 'PHPUnit\\Framework\\Constraint\\ExceptionCode' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessage' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', 'PHPUnit\\Framework\\Constraint\\ExceptionMessageRegularExpression' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegularExpression.php', 'PHPUnit\\Framework\\Constraint\\FileExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', 'PHPUnit\\Framework\\Constraint\\GreaterThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', 'PHPUnit\\Framework\\Constraint\\IsAnything' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', 'PHPUnit\\Framework\\Constraint\\IsEmpty' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', 'PHPUnit\\Framework\\Constraint\\IsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', 'PHPUnit\\Framework\\Constraint\\IsFalse' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', 'PHPUnit\\Framework\\Constraint\\IsFinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php', 'PHPUnit\\Framework\\Constraint\\IsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', 'PHPUnit\\Framework\\Constraint\\IsInfinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php', 'PHPUnit\\Framework\\Constraint\\IsInstanceOf' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', 'PHPUnit\\Framework\\Constraint\\IsJson' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', 'PHPUnit\\Framework\\Constraint\\IsNan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php', 'PHPUnit\\Framework\\Constraint\\IsNull' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', 'PHPUnit\\Framework\\Constraint\\IsReadable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php', 'PHPUnit\\Framework\\Constraint\\IsTrue' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', 'PHPUnit\\Framework\\Constraint\\IsType' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', 'PHPUnit\\Framework\\Constraint\\IsWritable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php', 'PHPUnit\\Framework\\Constraint\\JsonMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', 'PHPUnit\\Framework\\Constraint\\JsonMatchesErrorMessageProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php', 'PHPUnit\\Framework\\Constraint\\LessThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', 'PHPUnit\\Framework\\Constraint\\LogicalAnd' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalAnd.php', 'PHPUnit\\Framework\\Constraint\\LogicalNot' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalNot.php', 'PHPUnit\\Framework\\Constraint\\LogicalOr' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalOr.php', 'PHPUnit\\Framework\\Constraint\\LogicalXor' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LogicalXor.php', 'PHPUnit\\Framework\\Constraint\\ObjectHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', 'PHPUnit\\Framework\\Constraint\\RegularExpression' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/RegularExpression.php', 'PHPUnit\\Framework\\Constraint\\SameSize' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', 'PHPUnit\\Framework\\Constraint\\StringContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', 'PHPUnit\\Framework\\Constraint\\StringEndsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', 'PHPUnit\\Framework\\Constraint\\StringMatchesFormatDescription' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringMatchesFormatDescription.php', 'PHPUnit\\Framework\\Constraint\\StringStartsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', 'PHPUnit\\Framework\\Constraint\\TraversableContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', 'PHPUnit\\Framework\\Constraint\\TraversableContainsOnly' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', 'PHPUnit\\Framework\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.php', 'PHPUnit\\Framework\\DataProviderTestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/DataProviderTestSuite.php', 'PHPUnit\\Framework\\Error\\Deprecated' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', 'PHPUnit\\Framework\\Error\\Error' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Error.php', 'PHPUnit\\Framework\\Error\\Notice' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Notice.php', 'PHPUnit\\Framework\\Error\\Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Warning.php', 'PHPUnit\\Framework\\Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception.php', 'PHPUnit\\Framework\\ExceptionWrapper' => $vendorDir . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', 'PHPUnit\\Framework\\ExpectationFailedException' => $vendorDir . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php', 'PHPUnit\\Framework\\IncompleteTest' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTest.php', 'PHPUnit\\Framework\\IncompleteTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', 'PHPUnit\\Framework\\IncompleteTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestError.php', 'PHPUnit\\Framework\\InvalidCoversTargetException' => $vendorDir . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php', 'PHPUnit\\Framework\\MissingCoversAnnotationException' => $vendorDir . '/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.php', 'PHPUnit\\Framework\\OutputError' => $vendorDir . '/phpunit/phpunit/src/Framework/OutputError.php', 'PHPUnit\\Framework\\RiskyTest' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTest.php', 'PHPUnit\\Framework\\RiskyTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTestError.php', 'PHPUnit\\Framework\\SelfDescribing' => $vendorDir . '/phpunit/phpunit/src/Framework/SelfDescribing.php', 'PHPUnit\\Framework\\SkippedTest' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTest.php', 'PHPUnit\\Framework\\SkippedTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', 'PHPUnit\\Framework\\SkippedTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestError.php', 'PHPUnit\\Framework\\SkippedTestSuiteError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php', 'PHPUnit\\Framework\\SyntheticError' => $vendorDir . '/phpunit/phpunit/src/Framework/SyntheticError.php', 'PHPUnit\\Framework\\Test' => $vendorDir . '/phpunit/phpunit/src/Framework/Test.php', 'PHPUnit\\Framework\\TestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/TestCase.php', 'PHPUnit\\Framework\\TestFailure' => $vendorDir . '/phpunit/phpunit/src/Framework/TestFailure.php', 'PHPUnit\\Framework\\TestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListener.php', 'PHPUnit\\Framework\\TestResult' => $vendorDir . '/phpunit/phpunit/src/Framework/TestResult.php', 'PHPUnit\\Framework\\TestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite.php', 'PHPUnit\\Framework\\TestSuiteIterator' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuiteIterator.php', 'PHPUnit\\Framework\\UnintentionallyCoveredCodeError' => $vendorDir . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php', 'PHPUnit\\Framework\\Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Warning.php', 'PHPUnit\\Framework\\WarningTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/WarningTestCase.php', 'PHPUnit\\Runner\\BaseTestRunner' => $vendorDir . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', 'PHPUnit\\Runner\\Exception' => $vendorDir . '/phpunit/phpunit/src/Runner/Exception.php', 'PHPUnit\\Runner\\Filter\\ExcludeGroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\Factory' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Factory.php', 'PHPUnit\\Runner\\Filter\\GroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\IncludeGroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php', 'PHPUnit\\Runner\\Filter\\NameFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php', 'PHPUnit\\Runner\\PhptTestCase' => $vendorDir . '/phpunit/phpunit/src/Runner/PhptTestCase.php', 'PHPUnit\\Runner\\StandardTestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', 'PHPUnit\\Runner\\TestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', 'PHPUnit\\Runner\\Version' => $vendorDir . '/phpunit/phpunit/src/Runner/Version.php', 'PHPUnit\\TextUI\\Command' => $vendorDir . '/phpunit/phpunit/src/TextUI/Command.php', 'PHPUnit\\TextUI\\ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', 'PHPUnit\\TextUI\\TestRunner' => $vendorDir . '/phpunit/phpunit/src/TextUI/TestRunner.php', 'PHPUnit\\Util\\Blacklist' => $vendorDir . '/phpunit/phpunit/src/Util/Blacklist.php', 'PHPUnit\\Util\\Configuration' => $vendorDir . '/phpunit/phpunit/src/Util/Configuration.php', 'PHPUnit\\Util\\ConfigurationGenerator' => $vendorDir . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php', 'PHPUnit\\Util\\ErrorHandler' => $vendorDir . '/phpunit/phpunit/src/Util/ErrorHandler.php', 'PHPUnit\\Util\\Fileloader' => $vendorDir . '/phpunit/phpunit/src/Util/Fileloader.php', 'PHPUnit\\Util\\Filesystem' => $vendorDir . '/phpunit/phpunit/src/Util/Filesystem.php', 'PHPUnit\\Util\\Filter' => $vendorDir . '/phpunit/phpunit/src/Util/Filter.php', 'PHPUnit\\Util\\Getopt' => $vendorDir . '/phpunit/phpunit/src/Util/Getopt.php', 'PHPUnit\\Util\\GlobalState' => $vendorDir . '/phpunit/phpunit/src/Util/GlobalState.php', 'PHPUnit\\Util\\InvalidArgumentHelper' => $vendorDir . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php', 'PHPUnit\\Util\\Log\\JUnit' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JUnit.php', 'PHPUnit\\Util\\Log\\TeamCity' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TeamCity.php', 'PHPUnit\\Util\\PHP\\AbstractPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php', 'PHPUnit\\Util\\PHP\\DefaultPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php', 'PHPUnit\\Util\\PHP\\WindowsPhpProcess' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php', 'PHPUnit\\Util\\Printer' => $vendorDir . '/phpunit/phpunit/src/Util/Printer.php', 'PHPUnit\\Util\\RegularExpression' => $vendorDir . '/phpunit/phpunit/src/Util/RegularExpression.php', 'PHPUnit\\Util\\Test' => $vendorDir . '/phpunit/phpunit/src/Util/Test.php', 'PHPUnit\\Util\\TestDox\\HtmlResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php', 'PHPUnit\\Util\\TestDox\\NamePrettifier' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', 'PHPUnit\\Util\\TestDox\\ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', 'PHPUnit\\Util\\TestDox\\TextResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php', 'PHPUnit\\Util\\TestDox\\XmlResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php', 'PHPUnit\\Util\\Type' => $vendorDir . '/phpunit/phpunit/src/Util/Type.php', 'PHPUnit\\Util\\Xml' => $vendorDir . '/phpunit/phpunit/src/Util/Xml.php', 'PHPUnit_Framework_MockObject_BadMethodCallException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Exception/BadMethodCallException.php', 'PHPUnit_Framework_MockObject_Builder_Identity' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Builder/Identity.php', 'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Builder/InvocationMocker.php', 'PHPUnit_Framework_MockObject_Builder_Match' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Builder/Match.php', 'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Builder/MethodNameMatch.php', 'PHPUnit_Framework_MockObject_Builder_Namespace' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Builder/Namespace.php', 'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Builder/ParametersMatch.php', 'PHPUnit_Framework_MockObject_Builder_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Builder/Stub.php', 'PHPUnit_Framework_MockObject_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Exception/Exception.php', 'PHPUnit_Framework_MockObject_Generator' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Generator.php', 'PHPUnit_Framework_MockObject_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Invocation.php', 'PHPUnit_Framework_MockObject_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/InvocationMocker.php', 'PHPUnit_Framework_MockObject_Invocation_Object' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Invocation/Object.php', 'PHPUnit_Framework_MockObject_Invocation_Static' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Invocation/Static.php', 'PHPUnit_Framework_MockObject_Invokable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Invokable.php', 'PHPUnit_Framework_MockObject_Matcher' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher.php', 'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/AnyInvokedCount.php', 'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/AnyParameters.php', 'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/ConsecutiveParameters.php', 'PHPUnit_Framework_MockObject_Matcher_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/Invocation.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtIndex.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtLeastCount.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtLeastOnce.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedAtMostCount.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedCount.php', 'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/InvokedRecorder.php', 'PHPUnit_Framework_MockObject_Matcher_MethodName' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/MethodName.php', 'PHPUnit_Framework_MockObject_Matcher_Parameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/Parameters.php', 'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Matcher/StatelessInvocation.php', 'PHPUnit_Framework_MockObject_MockBuilder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/MockBuilder.php', 'PHPUnit_Framework_MockObject_MockObject' => $vendorDir . '/phpunit/phpunit-mock-objects/src/MockObject.php', 'PHPUnit_Framework_MockObject_RuntimeException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Exception/RuntimeException.php', 'PHPUnit_Framework_MockObject_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub.php', 'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/ConsecutiveCalls.php', 'PHPUnit_Framework_MockObject_Stub_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/Exception.php', 'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/MatcherCollection.php', 'PHPUnit_Framework_MockObject_Stub_Return' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/Return.php', 'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/ReturnArgument.php', 'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/ReturnCallback.php', 'PHPUnit_Framework_MockObject_Stub_ReturnReference' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/ReturnReference.php', 'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/ReturnSelf.php', 'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Stub/ReturnValueMap.php', 'PHPUnit_Framework_MockObject_Verifiable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Verifiable.php', 'PHP_CodeSniffer' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer.php', 'PHP_CodeSniffer_CLI' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/CLI.php', 'PHP_CodeSniffer_DocGenerators_Generator' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/Generator.php', 'PHP_CodeSniffer_DocGenerators_HTML' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/HTML.php', 'PHP_CodeSniffer_DocGenerators_Markdown' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/Markdown.php', 'PHP_CodeSniffer_DocGenerators_Text' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/DocGenerators/Text.php', 'PHP_CodeSniffer_Exception' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Exception.php', 'PHP_CodeSniffer_File' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/File.php', 'PHP_CodeSniffer_Fixer' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Fixer.php', 'PHP_CodeSniffer_Report' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Report.php', 'PHP_CodeSniffer_Reporting' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reporting.php', 'PHP_CodeSniffer_Reports_Cbf' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Cbf.php', 'PHP_CodeSniffer_Reports_Checkstyle' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Checkstyle.php', 'PHP_CodeSniffer_Reports_Csv' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Csv.php', 'PHP_CodeSniffer_Reports_Diff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Diff.php', 'PHP_CodeSniffer_Reports_Emacs' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Emacs.php', 'PHP_CodeSniffer_Reports_Full' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Full.php', 'PHP_CodeSniffer_Reports_Gitblame' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Gitblame.php', 'PHP_CodeSniffer_Reports_Hgblame' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Hgblame.php', 'PHP_CodeSniffer_Reports_Info' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Info.php', 'PHP_CodeSniffer_Reports_Json' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Json.php', 'PHP_CodeSniffer_Reports_Junit' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Junit.php', 'PHP_CodeSniffer_Reports_Notifysend' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Notifysend.php', 'PHP_CodeSniffer_Reports_Source' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Source.php', 'PHP_CodeSniffer_Reports_Summary' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Summary.php', 'PHP_CodeSniffer_Reports_Svnblame' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Svnblame.php', 'PHP_CodeSniffer_Reports_VersionControl' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/VersionControl.php', 'PHP_CodeSniffer_Reports_Xml' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Reports/Xml.php', 'PHP_CodeSniffer_Sniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Sniff.php', 'PHP_CodeSniffer_Standards_AbstractPatternSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/AbstractPatternSniff.php', 'PHP_CodeSniffer_Standards_AbstractScopeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/AbstractScopeSniff.php', 'PHP_CodeSniffer_Standards_AbstractVariableSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/AbstractVariableSniff.php', 'PHP_CodeSniffer_Standards_IncorrectPatternException' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/IncorrectPatternException.php', 'PHP_CodeSniffer_Tokenizers_CSS' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/CSS.php', 'PHP_CodeSniffer_Tokenizers_Comment' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/Comment.php', 'PHP_CodeSniffer_Tokenizers_JS' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/JS.php', 'PHP_CodeSniffer_Tokenizers_PHP' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Tokenizers/PHP.php', 'PHP_CodeSniffer_Tokens' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Tokens.php', 'PHP_Timer' => $vendorDir . '/phpunit/php-timer/src/Timer.php', 'PHP_Token' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScope' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_TokenWithScopeAndVisibility' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ABSTRACT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AMPERSAND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AND_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ARRAY_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ASYNC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_AWAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BACKTICK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BAD_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOLEAN_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BOOL_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_BREAK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CALLABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CARET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CASE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CATCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLASS_NAME_CONSTANT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLONE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CLOSE_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COALESCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMA' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_COMPILER_HALT_OFFSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONCAT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONSTANT_ENCAPSED_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CONTINUE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_CURLY_OPEN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DEFAULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DIV_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOC_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_DOUBLE_QUOTES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELLIPSIS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ELSEIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EMPTY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENCAPSED_AND_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDDECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDFOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDSWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENDWHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_END_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ENUM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EQUALS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EVAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXCLAMATION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_EXTENDS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FINALLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_FUNC_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GLOBAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GOTO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_HALT_COMPILER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IMPLEMENTS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INCLUDE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INLINE_HTML' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTANCEOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INSTEADOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INTERFACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_INT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ISSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_GREATER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_NOT_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_IS_SMALLER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Includes' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_JOIN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LAMBDA_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LAMBDA_CP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LAMBDA_OP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LINE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LIST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LOGICAL_XOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_METHOD_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MINUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MOD_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_MUL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NAMESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NEW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NS_SEPARATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_NUM_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_ONUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OPEN_TAG_WITH_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PERCENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PIPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PLUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_POW_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRINT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PRIVATE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PROTECTED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_PUBLIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_QUESTION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_REQUIRE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_RETURN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SEMICOLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SHAPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SPACESHIP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_START_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STATIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_STRING_VARNAME' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SUPER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_SWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_Stream' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream.php', 'PHP_Token_Stream_CachingFactory' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', 'PHP_Token_THROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TILDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRAIT_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TRY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TYPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TYPELIST_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_TYPELIST_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_UNSET_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_USE_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_VAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_VARIABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHERE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_ATTRIBUTE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_CATEGORY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_CATEGORY_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_CHILDREN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_REQUIRED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_TAG_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_TAG_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XHP_TEXT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_XOR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PHP_Token_YIELD_FROM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', 'PSR1_Sniffs_Classes_ClassDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php', 'PSR1_Sniffs_Files_SideEffectsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php', 'PSR1_Sniffs_Methods_CamelCapsMethodNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR1/Sniffs/Methods/CamelCapsMethodNameSniff.php', 'PSR2_Sniffs_Classes_ClassDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php', 'PSR2_Sniffs_Classes_PropertyDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php', 'PSR2_Sniffs_ControlStructures_ControlStructureSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/ControlStructures/ControlStructureSpacingSniff.php', 'PSR2_Sniffs_ControlStructures_ElseIfDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/ControlStructures/ElseIfDeclarationSniff.php', 'PSR2_Sniffs_ControlStructures_SwitchDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/ControlStructures/SwitchDeclarationSniff.php', 'PSR2_Sniffs_Files_ClosingTagSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Files/ClosingTagSniff.php', 'PSR2_Sniffs_Files_EndFileNewlineSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Files/EndFileNewlineSniff.php', 'PSR2_Sniffs_Methods_FunctionCallSignatureSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Methods/FunctionCallSignatureSniff.php', 'PSR2_Sniffs_Methods_FunctionClosingBraceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Methods/FunctionClosingBraceSniff.php', 'PSR2_Sniffs_Methods_MethodDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Methods/MethodDeclarationSniff.php', 'PSR2_Sniffs_Namespaces_NamespaceDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Namespaces/NamespaceDeclarationSniff.php', 'PSR2_Sniffs_Namespaces_UseDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php', 'PharIo\\Manifest\\Application' => $vendorDir . '/phar-io/manifest/src/values/Application.php', 'PharIo\\Manifest\\ApplicationName' => $vendorDir . '/phar-io/manifest/src/values/ApplicationName.php', 'PharIo\\Manifest\\Author' => $vendorDir . '/phar-io/manifest/src/values/Author.php', 'PharIo\\Manifest\\AuthorCollection' => $vendorDir . '/phar-io/manifest/src/values/AuthorCollection.php', 'PharIo\\Manifest\\AuthorCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/AuthorCollectionIterator.php', 'PharIo\\Manifest\\AuthorElement' => $vendorDir . '/phar-io/manifest/src/xml/AuthorElement.php', 'PharIo\\Manifest\\AuthorElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/AuthorElementCollection.php', 'PharIo\\Manifest\\BundledComponent' => $vendorDir . '/phar-io/manifest/src/values/BundledComponent.php', 'PharIo\\Manifest\\BundledComponentCollection' => $vendorDir . '/phar-io/manifest/src/values/BundledComponentCollection.php', 'PharIo\\Manifest\\BundledComponentCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', 'PharIo\\Manifest\\BundlesElement' => $vendorDir . '/phar-io/manifest/src/xml/BundlesElement.php', 'PharIo\\Manifest\\ComponentElement' => $vendorDir . '/phar-io/manifest/src/xml/ComponentElement.php', 'PharIo\\Manifest\\ComponentElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ComponentElementCollection.php', 'PharIo\\Manifest\\ContainsElement' => $vendorDir . '/phar-io/manifest/src/xml/ContainsElement.php', 'PharIo\\Manifest\\CopyrightElement' => $vendorDir . '/phar-io/manifest/src/xml/CopyrightElement.php', 'PharIo\\Manifest\\CopyrightInformation' => $vendorDir . '/phar-io/manifest/src/values/CopyrightInformation.php', 'PharIo\\Manifest\\ElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ElementCollection.php', 'PharIo\\Manifest\\Email' => $vendorDir . '/phar-io/manifest/src/values/Email.php', 'PharIo\\Manifest\\Exception' => $vendorDir . '/phar-io/manifest/src/exceptions/Exception.php', 'PharIo\\Manifest\\ExtElement' => $vendorDir . '/phar-io/manifest/src/xml/ExtElement.php', 'PharIo\\Manifest\\ExtElementCollection' => $vendorDir . '/phar-io/manifest/src/xml/ExtElementCollection.php', 'PharIo\\Manifest\\Extension' => $vendorDir . '/phar-io/manifest/src/values/Extension.php', 'PharIo\\Manifest\\ExtensionElement' => $vendorDir . '/phar-io/manifest/src/xml/ExtensionElement.php', 'PharIo\\Manifest\\InvalidApplicationNameException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', 'PharIo\\Manifest\\InvalidEmailException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidEmailException.php', 'PharIo\\Manifest\\InvalidUrlException' => $vendorDir . '/phar-io/manifest/src/exceptions/InvalidUrlException.php', 'PharIo\\Manifest\\Library' => $vendorDir . '/phar-io/manifest/src/values/Library.php', 'PharIo\\Manifest\\License' => $vendorDir . '/phar-io/manifest/src/values/License.php', 'PharIo\\Manifest\\LicenseElement' => $vendorDir . '/phar-io/manifest/src/xml/LicenseElement.php', 'PharIo\\Manifest\\Manifest' => $vendorDir . '/phar-io/manifest/src/values/Manifest.php', 'PharIo\\Manifest\\ManifestDocument' => $vendorDir . '/phar-io/manifest/src/xml/ManifestDocument.php', 'PharIo\\Manifest\\ManifestDocumentException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentException.php', 'PharIo\\Manifest\\ManifestDocumentLoadingException' => $vendorDir . '/phar-io/manifest/src/xml/ManifestDocumentLoadingException.php', 'PharIo\\Manifest\\ManifestDocumentMapper' => $vendorDir . '/phar-io/manifest/src/ManifestDocumentMapper.php', 'PharIo\\Manifest\\ManifestDocumentMapperException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', 'PharIo\\Manifest\\ManifestElement' => $vendorDir . '/phar-io/manifest/src/xml/ManifestElement.php', 'PharIo\\Manifest\\ManifestElementException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestElementException.php', 'PharIo\\Manifest\\ManifestLoader' => $vendorDir . '/phar-io/manifest/src/ManifestLoader.php', 'PharIo\\Manifest\\ManifestLoaderException' => $vendorDir . '/phar-io/manifest/src/exceptions/ManifestLoaderException.php', 'PharIo\\Manifest\\ManifestSerializer' => $vendorDir . '/phar-io/manifest/src/ManifestSerializer.php', 'PharIo\\Manifest\\PhpElement' => $vendorDir . '/phar-io/manifest/src/xml/PhpElement.php', 'PharIo\\Manifest\\PhpExtensionRequirement' => $vendorDir . '/phar-io/manifest/src/values/PhpExtensionRequirement.php', 'PharIo\\Manifest\\PhpVersionRequirement' => $vendorDir . '/phar-io/manifest/src/values/PhpVersionRequirement.php', 'PharIo\\Manifest\\Requirement' => $vendorDir . '/phar-io/manifest/src/values/Requirement.php', 'PharIo\\Manifest\\RequirementCollection' => $vendorDir . '/phar-io/manifest/src/values/RequirementCollection.php', 'PharIo\\Manifest\\RequirementCollectionIterator' => $vendorDir . '/phar-io/manifest/src/values/RequirementCollectionIterator.php', 'PharIo\\Manifest\\RequiresElement' => $vendorDir . '/phar-io/manifest/src/xml/RequiresElement.php', 'PharIo\\Manifest\\Type' => $vendorDir . '/phar-io/manifest/src/values/Type.php', 'PharIo\\Manifest\\Url' => $vendorDir . '/phar-io/manifest/src/values/Url.php', 'PharIo\\Version\\AbstractVersionConstraint' => $vendorDir . '/phar-io/version/src/AbstractVersionConstraint.php', 'PharIo\\Version\\AndVersionConstraintGroup' => $vendorDir . '/phar-io/version/src/AndVersionConstraintGroup.php', 'PharIo\\Version\\AnyVersionConstraint' => $vendorDir . '/phar-io/version/src/AnyVersionConstraint.php', 'PharIo\\Version\\ExactVersionConstraint' => $vendorDir . '/phar-io/version/src/ExactVersionConstraint.php', 'PharIo\\Version\\Exception' => $vendorDir . '/phar-io/version/src/Exception.php', 'PharIo\\Version\\GreaterThanOrEqualToVersionConstraint' => $vendorDir . '/phar-io/version/src/GreaterThanOrEqualToVersionConstraint.php', 'PharIo\\Version\\InvalidVersionException' => $vendorDir . '/phar-io/version/src/InvalidVersionException.php', 'PharIo\\Version\\OrVersionConstraintGroup' => $vendorDir . '/phar-io/version/src/OrVersionConstraintGroup.php', 'PharIo\\Version\\PreReleaseSuffix' => $vendorDir . '/phar-io/version/src/PreReleaseSuffix.php', 'PharIo\\Version\\SpecificMajorAndMinorVersionConstraint' => $vendorDir . '/phar-io/version/src/SpecificMajorAndMinorVersionConstraint.php', 'PharIo\\Version\\SpecificMajorVersionConstraint' => $vendorDir . '/phar-io/version/src/SpecificMajorVersionConstraint.php', 'PharIo\\Version\\UnsupportedVersionConstraintException' => $vendorDir . '/phar-io/version/src/UnsupportedVersionConstraintException.php', 'PharIo\\Version\\Version' => $vendorDir . '/phar-io/version/src/Version.php', 'PharIo\\Version\\VersionConstraint' => $vendorDir . '/phar-io/version/src/VersionConstraint.php', 'PharIo\\Version\\VersionConstraintParser' => $vendorDir . '/phar-io/version/src/VersionConstraintParser.php', 'PharIo\\Version\\VersionConstraintValue' => $vendorDir . '/phar-io/version/src/VersionConstraintValue.php', 'PharIo\\Version\\VersionNumber' => $vendorDir . '/phar-io/version/src/VersionNumber.php', 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage.php', 'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Driver.php', 'SebastianBergmann\\CodeCoverage\\Driver\\HHVM' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/HHVM.php', 'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php', 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Xdebug.php', 'SebastianBergmann\\CodeCoverage\\Exception' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/Exception.php', 'SebastianBergmann\\CodeCoverage\\Filter' => $vendorDir . '/phpunit/php-code-coverage/src/Filter.php', 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', 'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php', 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => $vendorDir . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Builder.php', 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Directory.php', 'SebastianBergmann\\CodeCoverage\\Node\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Node/File.php', 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Iterator.php', 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Clover.php', 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Crap4j.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => $vendorDir . '/phpunit/php-code-coverage/src/Report/PHP.php', 'SebastianBergmann\\CodeCoverage\\Report\\Text' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Text.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\BuildInformation' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/File.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Source' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Source.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', 'SebastianBergmann\\CodeCoverage\\RuntimeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php', 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', 'SebastianBergmann\\CodeCoverage\\Util' => $vendorDir . '/phpunit/php-code-coverage/src/Util.php', 'SebastianBergmann\\CodeCoverage\\Version' => $vendorDir . '/phpunit/php-code-coverage/src/Version.php', 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => $vendorDir . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', 'SebastianBergmann\\Comparator\\ArrayComparator' => $vendorDir . '/sebastian/comparator/src/ArrayComparator.php', 'SebastianBergmann\\Comparator\\Comparator' => $vendorDir . '/sebastian/comparator/src/Comparator.php', 'SebastianBergmann\\Comparator\\ComparisonFailure' => $vendorDir . '/sebastian/comparator/src/ComparisonFailure.php', 'SebastianBergmann\\Comparator\\DOMNodeComparator' => $vendorDir . '/sebastian/comparator/src/DOMNodeComparator.php', 'SebastianBergmann\\Comparator\\DateTimeComparator' => $vendorDir . '/sebastian/comparator/src/DateTimeComparator.php', 'SebastianBergmann\\Comparator\\DoubleComparator' => $vendorDir . '/sebastian/comparator/src/DoubleComparator.php', 'SebastianBergmann\\Comparator\\ExceptionComparator' => $vendorDir . '/sebastian/comparator/src/ExceptionComparator.php', 'SebastianBergmann\\Comparator\\Factory' => $vendorDir . '/sebastian/comparator/src/Factory.php', 'SebastianBergmann\\Comparator\\MockObjectComparator' => $vendorDir . '/sebastian/comparator/src/MockObjectComparator.php', 'SebastianBergmann\\Comparator\\NumericComparator' => $vendorDir . '/sebastian/comparator/src/NumericComparator.php', 'SebastianBergmann\\Comparator\\ObjectComparator' => $vendorDir . '/sebastian/comparator/src/ObjectComparator.php', 'SebastianBergmann\\Comparator\\ResourceComparator' => $vendorDir . '/sebastian/comparator/src/ResourceComparator.php', 'SebastianBergmann\\Comparator\\ScalarComparator' => $vendorDir . '/sebastian/comparator/src/ScalarComparator.php', 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => $vendorDir . '/sebastian/comparator/src/SplObjectStorageComparator.php', 'SebastianBergmann\\Comparator\\TypeComparator' => $vendorDir . '/sebastian/comparator/src/TypeComparator.php', 'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php', 'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php', 'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php', 'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => $vendorDir . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php', 'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', 'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', 'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php', 'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php', 'SebastianBergmann\\Environment\\Console' => $vendorDir . '/sebastian/environment/src/Console.php', 'SebastianBergmann\\Environment\\Runtime' => $vendorDir . '/sebastian/environment/src/Runtime.php', 'SebastianBergmann\\Exporter\\Exporter' => $vendorDir . '/sebastian/exporter/src/Exporter.php', 'SebastianBergmann\\GlobalState\\Blacklist' => $vendorDir . '/sebastian/global-state/src/Blacklist.php', 'SebastianBergmann\\GlobalState\\CodeExporter' => $vendorDir . '/sebastian/global-state/src/CodeExporter.php', 'SebastianBergmann\\GlobalState\\Exception' => $vendorDir . '/sebastian/global-state/src/exceptions/Exception.php', 'SebastianBergmann\\GlobalState\\Restorer' => $vendorDir . '/sebastian/global-state/src/Restorer.php', 'SebastianBergmann\\GlobalState\\RuntimeException' => $vendorDir . '/sebastian/global-state/src/exceptions/RuntimeException.php', 'SebastianBergmann\\GlobalState\\Snapshot' => $vendorDir . '/sebastian/global-state/src/Snapshot.php', 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => $vendorDir . '/sebastian/object-enumerator/src/Enumerator.php', 'SebastianBergmann\\ObjectEnumerator\\Exception' => $vendorDir . '/sebastian/object-enumerator/src/Exception.php', 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => $vendorDir . '/sebastian/object-enumerator/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\Exception' => $vendorDir . '/sebastian/object-reflector/src/Exception.php', 'SebastianBergmann\\ObjectReflector\\InvalidArgumentException' => $vendorDir . '/sebastian/object-reflector/src/InvalidArgumentException.php', 'SebastianBergmann\\ObjectReflector\\ObjectReflector' => $vendorDir . '/sebastian/object-reflector/src/ObjectReflector.php', 'SebastianBergmann\\RecursionContext\\Context' => $vendorDir . '/sebastian/recursion-context/src/Context.php', 'SebastianBergmann\\RecursionContext\\Exception' => $vendorDir . '/sebastian/recursion-context/src/Exception.php', 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => $vendorDir . '/sebastian/recursion-context/src/InvalidArgumentException.php', 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => $vendorDir . '/sebastian/resource-operations/src/ResourceOperations.php', 'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php', 'Squiz_Sniffs_Arrays_ArrayBracketSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Arrays/ArrayBracketSpacingSniff.php', 'Squiz_Sniffs_Arrays_ArrayDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Arrays/ArrayDeclarationSniff.php', 'Squiz_Sniffs_CSS_ClassDefinitionClosingBraceSpaceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ClassDefinitionClosingBraceSpaceSniff.php', 'Squiz_Sniffs_CSS_ClassDefinitionNameSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ClassDefinitionNameSpacingSniff.php', 'Squiz_Sniffs_CSS_ClassDefinitionOpeningBraceSpaceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ClassDefinitionOpeningBraceSpaceSniff.php', 'Squiz_Sniffs_CSS_ColonSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ColonSpacingSniff.php', 'Squiz_Sniffs_CSS_ColourDefinitionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ColourDefinitionSniff.php', 'Squiz_Sniffs_CSS_DisallowMultipleStyleDefinitionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/DisallowMultipleStyleDefinitionsSniff.php', 'Squiz_Sniffs_CSS_DuplicateClassDefinitionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/DuplicateClassDefinitionSniff.php', 'Squiz_Sniffs_CSS_DuplicateStyleDefinitionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/DuplicateStyleDefinitionSniff.php', 'Squiz_Sniffs_CSS_EmptyClassDefinitionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/EmptyClassDefinitionSniff.php', 'Squiz_Sniffs_CSS_EmptyStyleDefinitionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/EmptyStyleDefinitionSniff.php', 'Squiz_Sniffs_CSS_ForbiddenStylesSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ForbiddenStylesSniff.php', 'Squiz_Sniffs_CSS_IndentationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/IndentationSniff.php', 'Squiz_Sniffs_CSS_LowercaseStyleDefinitionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/LowercaseStyleDefinitionSniff.php', 'Squiz_Sniffs_CSS_MissingColonSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/MissingColonSniff.php', 'Squiz_Sniffs_CSS_NamedColoursSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/NamedColoursSniff.php', 'Squiz_Sniffs_CSS_OpacitySniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/OpacitySniff.php', 'Squiz_Sniffs_CSS_SemicolonSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/SemicolonSpacingSniff.php', 'Squiz_Sniffs_CSS_ShorthandSizeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/CSS/ShorthandSizeSniff.php', 'Squiz_Sniffs_Classes_ClassDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/ClassDeclarationSniff.php', 'Squiz_Sniffs_Classes_ClassFileNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/ClassFileNameSniff.php', 'Squiz_Sniffs_Classes_DuplicatePropertySniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/DuplicatePropertySniff.php', 'Squiz_Sniffs_Classes_LowercaseClassKeywordsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/LowercaseClassKeywordsSniff.php', 'Squiz_Sniffs_Classes_SelfMemberReferenceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/SelfMemberReferenceSniff.php', 'Squiz_Sniffs_Classes_ValidClassNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Classes/ValidClassNameSniff.php', 'Squiz_Sniffs_Commenting_BlockCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/BlockCommentSniff.php', 'Squiz_Sniffs_Commenting_ClassCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/ClassCommentSniff.php', 'Squiz_Sniffs_Commenting_ClosingDeclarationCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/ClosingDeclarationCommentSniff.php', 'Squiz_Sniffs_Commenting_DocCommentAlignmentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/DocCommentAlignmentSniff.php', 'Squiz_Sniffs_Commenting_EmptyCatchCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/EmptyCatchCommentSniff.php', 'Squiz_Sniffs_Commenting_FileCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/FileCommentSniff.php', 'Squiz_Sniffs_Commenting_FunctionCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/FunctionCommentSniff.php', 'Squiz_Sniffs_Commenting_FunctionCommentThrowTagSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/FunctionCommentThrowTagSniff.php', 'Squiz_Sniffs_Commenting_InlineCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/InlineCommentSniff.php', 'Squiz_Sniffs_Commenting_LongConditionClosingCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/LongConditionClosingCommentSniff.php', 'Squiz_Sniffs_Commenting_PostStatementCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/PostStatementCommentSniff.php', 'Squiz_Sniffs_Commenting_VariableCommentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php', 'Squiz_Sniffs_ControlStructures_ControlSignatureSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ControlSignatureSniff.php', 'Squiz_Sniffs_ControlStructures_ElseIfDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ElseIfDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_ForEachLoopDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ForEachLoopDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_ForLoopDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/ForLoopDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_InlineIfDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/InlineIfDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_LowercaseDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/LowercaseDeclarationSniff.php', 'Squiz_Sniffs_ControlStructures_SwitchDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/ControlStructures/SwitchDeclarationSniff.php', 'Squiz_Sniffs_Debug_JSLintSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Debug/JSLintSniff.php', 'Squiz_Sniffs_Debug_JavaScriptLintSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Debug/JavaScriptLintSniff.php', 'Squiz_Sniffs_Files_FileExtensionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Files/FileExtensionSniff.php', 'Squiz_Sniffs_Formatting_OperatorBracketSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php', 'Squiz_Sniffs_Functions_FunctionDeclarationArgumentSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/FunctionDeclarationArgumentSpacingSniff.php', 'Squiz_Sniffs_Functions_FunctionDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/FunctionDeclarationSniff.php', 'Squiz_Sniffs_Functions_FunctionDuplicateArgumentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/FunctionDuplicateArgumentSniff.php', 'Squiz_Sniffs_Functions_GlobalFunctionSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/GlobalFunctionSniff.php', 'Squiz_Sniffs_Functions_LowercaseFunctionKeywordsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/LowercaseFunctionKeywordsSniff.php', 'Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Functions/MultiLineFunctionDeclarationSniff.php', 'Squiz_Sniffs_NamingConventions_ValidFunctionNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/NamingConventions/ValidFunctionNameSniff.php', 'Squiz_Sniffs_NamingConventions_ValidVariableNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/NamingConventions/ValidVariableNameSniff.php', 'Squiz_Sniffs_Objects_DisallowObjectStringIndexSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Objects/DisallowObjectStringIndexSniff.php', 'Squiz_Sniffs_Objects_ObjectInstantiationSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Objects/ObjectInstantiationSniff.php', 'Squiz_Sniffs_Objects_ObjectMemberCommaSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Objects/ObjectMemberCommaSniff.php', 'Squiz_Sniffs_Operators_ComparisonOperatorUsageSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Operators/ComparisonOperatorUsageSniff.php', 'Squiz_Sniffs_Operators_IncrementDecrementUsageSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Operators/IncrementDecrementUsageSniff.php', 'Squiz_Sniffs_Operators_ValidLogicalOperatorsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Operators/ValidLogicalOperatorsSniff.php', 'Squiz_Sniffs_PHP_CommentedOutCodeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/CommentedOutCodeSniff.php', 'Squiz_Sniffs_PHP_DisallowBooleanStatementSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowBooleanStatementSniff.php', 'Squiz_Sniffs_PHP_DisallowComparisonAssignmentSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowComparisonAssignmentSniff.php', 'Squiz_Sniffs_PHP_DisallowInlineIfSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowInlineIfSniff.php', 'Squiz_Sniffs_PHP_DisallowMultipleAssignmentsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowMultipleAssignmentsSniff.php', 'Squiz_Sniffs_PHP_DisallowObEndFlushSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowObEndFlushSniff.php', 'Squiz_Sniffs_PHP_DisallowSizeFunctionsInLoopsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DisallowSizeFunctionsInLoopsSniff.php', 'Squiz_Sniffs_PHP_DiscouragedFunctionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/DiscouragedFunctionsSniff.php', 'Squiz_Sniffs_PHP_EmbeddedPhpSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/EmbeddedPhpSniff.php', 'Squiz_Sniffs_PHP_EvalSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/EvalSniff.php', 'Squiz_Sniffs_PHP_ForbiddenFunctionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/ForbiddenFunctionsSniff.php', 'Squiz_Sniffs_PHP_GlobalKeywordSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/GlobalKeywordSniff.php', 'Squiz_Sniffs_PHP_HeredocSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/HeredocSniff.php', 'Squiz_Sniffs_PHP_InnerFunctionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/InnerFunctionsSniff.php', 'Squiz_Sniffs_PHP_LowercasePHPFunctionsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/LowercasePHPFunctionsSniff.php', 'Squiz_Sniffs_PHP_NonExecutableCodeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php', 'Squiz_Sniffs_Scope_MemberVarScopeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Scope/MemberVarScopeSniff.php', 'Squiz_Sniffs_Scope_MethodScopeSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Scope/MethodScopeSniff.php', 'Squiz_Sniffs_Scope_StaticThisUsageSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Scope/StaticThisUsageSniff.php', 'Squiz_Sniffs_Strings_ConcatenationSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Strings/ConcatenationSpacingSniff.php', 'Squiz_Sniffs_Strings_DoubleQuoteUsageSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Strings/DoubleQuoteUsageSniff.php', 'Squiz_Sniffs_Strings_EchoedStringsSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/Strings/EchoedStringsSniff.php', 'Squiz_Sniffs_WhiteSpace_CastSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/CastSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_ControlStructureSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_FunctionClosingBraceSpaceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php', 'Squiz_Sniffs_WhiteSpace_FunctionOpeningBraceSpaceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/FunctionOpeningBraceSpaceSniff.php', 'Squiz_Sniffs_WhiteSpace_FunctionSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/FunctionSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_LanguageConstructSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/LanguageConstructSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_LogicalOperatorSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/LogicalOperatorSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_MemberVarSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/MemberVarSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_ObjectOperatorSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_OperatorSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_PropertyLabelSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/PropertyLabelSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_ScopeClosingBraceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php', 'Squiz_Sniffs_WhiteSpace_ScopeKeywordSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/ScopeKeywordSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_SemicolonSpacingSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/SemicolonSpacingSniff.php', 'Squiz_Sniffs_WhiteSpace_SuperfluousWhitespaceSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Squiz/Sniffs/WhiteSpace/SuperfluousWhitespaceSniff.php', 'Text_Template' => $vendorDir . '/phpunit/php-text-template/src/Template.php', 'TheSeer\\Tokenizer\\Exception' => $vendorDir . '/theseer/tokenizer/src/Exception.php', 'TheSeer\\Tokenizer\\NamespaceUri' => $vendorDir . '/theseer/tokenizer/src/NamespaceUri.php', 'TheSeer\\Tokenizer\\NamespaceUriException' => $vendorDir . '/theseer/tokenizer/src/NamespaceUriException.php', 'TheSeer\\Tokenizer\\Token' => $vendorDir . '/theseer/tokenizer/src/Token.php', 'TheSeer\\Tokenizer\\TokenCollection' => $vendorDir . '/theseer/tokenizer/src/TokenCollection.php', 'TheSeer\\Tokenizer\\TokenCollectionException' => $vendorDir . '/theseer/tokenizer/src/TokenCollectionException.php', 'TheSeer\\Tokenizer\\Tokenizer' => $vendorDir . '/theseer/tokenizer/src/Tokenizer.php', 'TheSeer\\Tokenizer\\XMLSerializer' => $vendorDir . '/theseer/tokenizer/src/XMLSerializer.php', 'Zend_Sniffs_Debug_CodeAnalyzerSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Zend/Sniffs/Debug/CodeAnalyzerSniff.php', 'Zend_Sniffs_Files_ClosingTagSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Zend/Sniffs/Files/ClosingTagSniff.php', 'Zend_Sniffs_NamingConventions_ValidVariableNameSniff' => $vendorDir . '/squizlabs/php_codesniffer/CodeSniffer/Standards/Zend/Sniffs/NamingConventions/ValidVariableNameSniff.php', ); array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-docblock/src'), 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'), 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\' => array($vendorDir . '/stecman/symfony-console-completion/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), 'Predis\\' => array($vendorDir . '/predis/predis/src'), 'PhpAmqpLib\\' => array($vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib'), 'Pheanstalk\\' => array($vendorDir . '/pda/pheanstalk/src'), 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), 'MongoDB\\' => array($vendorDir . '/mongodb/mongodb/src'), 'League\\FactoryMuffin\\Faker\\' => array($vendorDir . '/league/factory-muffin-faker/src'), 'League\\FactoryMuffin\\' => array($vendorDir . '/league/factory-muffin/src'), 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), 'Faker\\' => array($vendorDir . '/fzaninotto/faker/src/Faker'), 'Facebook\\WebDriver\\' => array($vendorDir . '/facebook/webdriver/lib'), 'Facebook\\' => array($vendorDir . '/facebook/graph-sdk/src/Facebook'), 'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'), 'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'), 'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), 'Codeception\\Extension\\' => array($baseDir . '/ext'), 'Codeception\\' => array($baseDir . '/src/Codeception'), ); $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '3a37ebac017bc098e9a86b35401e7a68' => $vendorDir . '/mongodb/mongodb/src/functions.php', 'c65d09b6820da036953a371c8c73a9b1' => $vendorDir . '/facebook/graph-sdk/src/Facebook/polyfills.php', '1a296e41175bbbdf647d8b8ed1f41f41' => $baseDir . '/shim.php', 'ba7c0cf09bd8c80b156d0906efed4a9d' => $baseDir . '/phpunit5-loggers.php', ); copier) return; $this->copier = new \DeepCopy\DeepCopy(); $this->copier->skipUncloneable(); if (!$this->specifyConfig) $this->specifyConfig = Config::create(); } function specify($specification, \Closure $callable = null, $params = []) { if (!$callable) return; $this->specifyInit(); $test = $callable->bindTo($this); $oldName = $this->getName(); $newName = $oldName . ' | ' . $specification; $this->setName($newName); $properties = $this->getSpecifyObjectProperties(); $throws = $this->getSpecifyExpectedException($params); $examples = $this->getSpecifyExamples($params); $showExamplesIndex = $examples !== [[]]; foreach ($examples as $idx => $example) { if ($showExamplesIndex) { $this->setName($newName . ' | examples index ' . $idx); } $this->specifyCloneProperties($properties); if (!empty($this->beforeSpecify) && is_array($this->beforeSpecify)) { foreach ($this->beforeSpecify as $closure) { if ($closure instanceof \Closure) $closure->__invoke(); } } $this->specifyExecute($test, $throws, $example); $this->specifyRestoreProperties($properties); if (!empty($this->afterSpecify) && is_array($this->afterSpecify)) { foreach ($this->afterSpecify as $closure) { if ($closure instanceof \Closure) $closure->__invoke(); } } } $this->setName($oldName); } private function getSpecifyExamples($params) { if (isset($params['examples'])) { if (!is_array($params['examples'])) throw new \RuntimeException("Examples should be an array"); return $params['examples']; } return [[]]; } private function getSpecifyExpectedException($params) { if (isset($params['throws'])) { $throws = (is_array($params['throws'])) ? $params['throws'][0] : $params['throws']; if (is_object($throws)) { $throws = get_class($throws); } if ($throws === 'fail') { $throws = 'PHPUnit_Framework_AssertionFailedError'; } $message = (is_array($params['throws']) && isset($params['throws'][1])) ? $params['throws'][1] : false; return [$throws, $message]; } return false; } private function specifyExecute($test, $throws = false, $examples = array()) { $message = false; if (is_array($throws)) { $message = ($throws[1]) ? strtolower($throws[1]) : false; $throws = $throws[0]; } $result = $this->getTestResultObject(); try { call_user_func_array($test, $examples); $this->specifyCheckMockObjects(); } catch (\PHPUnit_Framework_AssertionFailedError $e) { if ($throws !== get_class($e)){ $result->addFailure(clone($this), $e, $result->time()); } if ($message !==false && $message !== strtolower($e->getMessage())) { $f = new \PHPUnit_Framework_AssertionFailedError("exception message '$message' was expected, but '" . $e->getMessage() . "' was received"); $result->addFailure(clone($this), $f, $result->time()); } } catch (\Exception $e) { if ($throws) { if ($throws !== get_class($e)) { $f = new \PHPUnit_Framework_AssertionFailedError("exception '$throws' was expected, but " . get_class($e) . ' was thrown'); $result->addFailure(clone($this), $f, $result->time()); } if ($message !==false && $message !== strtolower($e->getMessage())) { $f = new \PHPUnit_Framework_AssertionFailedError("exception message '$message' was expected, but '" . $e->getMessage() . "' was received"); $result->addFailure(clone($this), $f, $result->time()); } } else { throw $e; } } if ($throws) { if (isset($e)) { $this->assertTrue(true, 'exception handled'); } else { $f = new \PHPUnit_Framework_AssertionFailedError("exception '$throws' was not thrown as expected"); $result->addFailure(clone($this), $f, $result->time()); } } } public function specifyConfig() { if (!$this->specifyConfig) $this->specifyConfig = Config::create(); return new ConfigBuilder($this->specifyConfig); } function beforeSpecify(\Closure $callable = null) { $this->beforeSpecify[] = $callable->bindTo($this); } function afterSpecify(\Closure $callable = null) { $this->afterSpecify[] = $callable->bindTo($this); } function cleanSpecify() { $this->beforeSpecify = $this->afterSpecify = array(); } private function specifyCloneProperties($properties) { foreach ($properties as $property) { $propertyName = $property->getName(); $propertyValue = $property->getValue(); if ($this->specifyConfig->classIgnored($propertyValue)) { continue; } if ($this->specifyConfig->propertyIsShallowCloned($propertyName)) { if (is_object($propertyValue)) { $property->setValue(clone $propertyValue); } else { $property->setValue($propertyValue); } } if ($this->specifyConfig->propertyIsDeeplyCloned($propertyName)) { $property->setValue($this->copier->copy($propertyValue)); } } } private function specifyRestoreProperties($properties) { foreach ($properties as $property) { $property->restoreValue(); } } private function getSpecifyObjectProperties() { $objectReflection = new \ReflectionObject($this); $propertiesToClone = $objectReflection->getProperties(); if (($classProperties = $this->specifyGetClassPrivateProperties()) !== []) { $propertiesToClone = array_merge($propertiesToClone, $classProperties); } $properties = []; foreach ($propertiesToClone as $property) { if ($this->specifyConfig->propertyIgnored($property->getName())) { continue; } $properties[] = new ObjectProperty($this, $property); } if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) { $properties[] = $mockObjects = new ObjectProperty( $this, $phpUnitReflection->getProperty('mockObjects') ); $mockObjects->setValue([]); } return $properties; } private function specifyCheckMockObjects() { if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) { $verifyMockObjects = $phpUnitReflection->getMethod('verifyMockObjects'); $verifyMockObjects->setAccessible(true); $verifyMockObjects->invoke($this); } } private function specifyGetClassPrivateProperties() { static $properties = []; if (!isset($properties[__CLASS__])) { $reflection = new \ReflectionClass(__CLASS__); $properties[__CLASS__] = (get_class($this) !== __CLASS__) ? $reflection->getProperties(\ReflectionProperty::IS_PRIVATE) : []; } return $properties[__CLASS__]; } private function specifyGetPhpUnitReflection() { if ($this instanceof \PHPUnit_Framework_TestCase) { return new \ReflectionClass('\PHPUnit_Framework_TestCase'); } } } _owner = $owner; $this->_property = $property; if (!($this->_property instanceof \ReflectionProperty)) { $this->_property = new \ReflectionProperty($owner, $this->_property); } $this->_property->setAccessible(true); $this->_initValue = ($value === null ? $this->getValue() : $value); } public function getName() { return $this->_property->getName(); } public function restoreValue() { $this->setValue($this->_initValue); } public function getValue() { return $this->_property->getValue($this->_owner); } public function setValue($value) { $this->_property->setValue($this->_owner, $value); } } config = $config; if (!$config) { $this->config = Config::create(); } } public function ignore($properties = array()) { if (!is_array($properties)) { $properties = func_get_args(); } $this->config->ignore = array_merge($this->config->ignore, $properties); return $this; } public function ignoreClasses($classes = array()) { $this->config->ignore_classes = array_merge($this->config->ignore_classes, $classes); return $this; } public function deepClone($properties = true) { if (is_bool($properties)) { $this->config->is_deep = $properties; return $this; } if (!is_array($properties)) { $properties = func_get_args(); } $this->config->deep = $properties; return $this; } public function shallowClone($properties = true) { if (is_bool($properties)) { $this->config->is_deep = !$properties; return $this; } if (!is_array($properties)) { $properties = func_get_args(); } $this->config->shallow = array_merge($this->config->shallow, $properties); return $this; } public function cloneOnly($properties) { if (!is_array($properties)) { $properties = func_get_args(); } $this->config->only = $properties; return $this; } } only) { return !in_array($property, $this->only); } return in_array($property, $this->ignore); } public function classIgnored($value) { if (!is_object($value)) return false; return in_array(get_class($value), $this->ignore_classes); } public function propertyIsShallowCloned($property) { if ($this->only and !$this->is_deep) { return in_array($property, $this->only); } if (!$this->is_deep and !in_array($property, $this->deep)) { return true; } return in_array($property, $this->shallow); } public function propertyIsDeeplyCloned($property) { if ($this->only and $this->is_deep) { return in_array($property, $this->only); } if ($this->is_deep and !in_array($property, $this->shallow)) { return true; } return in_array($property, $this->deep); } public static function setDeepClone($deepClone) { self::$deepClone = $deepClone; } public static function setIgnoredClasses($ignoredClasses) { self::$ignoredClasses = $ignoredClasses; } public static function setIgnoredProperties($ignoredProperties) { self::$ignoredProperties = $ignoredProperties; } public static function addIgnoredClasses($ignoredClasses) { self::$ignoredClasses = array_merge(self::$ignoredClasses, $ignoredClasses); } private function __construct() { } static function create() { $config = new Config(); $config->is_deep = self::$deepClone; $config->ignore = self::$ignoredProperties; $config->ignore_classes = self::$ignoredClasses; $config->shallow = array(); $config->deep = array(); $config->only = null; return $config; } }test(); $version = file_get_contents('VERSION'); $this->docs(); $this->taskGitHubRelease($version) ->uri('Codeception/Specify') ->askDescription() ->run(); } public function changed($description) { $this->taskChangelog() ->version(file_get_contents('VERSION')) ->change($description) ->run(); } protected $docs = [ 'docs/GlobalConfig.md' => '\Codeception\Specify\Config', 'docs/LocalConfig.md' => '\Codeception\Specify\ConfigBuilder', ]; public function docs() { foreach ($this->docs as $file => $class) { class_exists($class, true); $this->taskGenDoc($file) ->docClass($class) ->processProperty(false) ->run(); } } public function test() { $res = $this->taskPHPUnit()->run(); if (!$res) exit; } } bodyTemplate = $bodyTemplate; $this->tags = $tags; } public function render(Formatter $formatter = null) { if ($formatter === null) { $formatter = new PassthroughFormatter(); } $tags = []; foreach ($this->tags as $tag) { $tags[] = '{' . $formatter->format($tag) . '}'; } return vsprintf($this->bodyTemplate, $tags); } public function __toString() { return $this->render(); } } tagFactory = $tagFactory; } public function create($contents, TypeContext $context = null) { list($text, $tags) = $this->parse($this->lex($contents), $context); return new Description($text, $tags); } private function lex($contents) { $contents = $this->removeSuperfluousStartingWhitespace($contents); if (strpos($contents, '{@') === false) { return [$contents]; } return preg_split( '/\{ # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally. (?!@\}) # We want to capture the whole tag line, but without the inline tag delimiters. (\@ # Match everything up to the next delimiter. [^{}]* # Nested inline tag content should not be captured, or it will appear in the result separately. (?: # Match nested inline tags. (?: # Because we did not catch the tag delimiters earlier, we must be explicit with them here. # Notice that this also matches "{}", as a way to later introduce it as an escape sequence. \{(?1)?\} | # Make sure we match hanging "{". \{ ) # Match content after the nested inline tag. [^{}]* )* # If there are more inline tags, match them as well. We use "*" since there may not be any # nested inline tags. ) \}/Sux', $contents, null, PREG_SPLIT_DELIM_CAPTURE ); } private function parse($tokens, TypeContext $context) { $count = count($tokens); $tagCount = 0; $tags = []; for ($i = 1; $i < $count; $i += 2) { $tags[] = $this->tagFactory->create($tokens[$i], $context); $tokens[$i] = '%' . ++$tagCount . '$s'; } for ($i = 0; $i < $count; $i += 2) { $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]); } return [implode('', $tokens), $tags]; } private function removeSuperfluousStartingWhitespace($contents) { $lines = explode("\n", $contents); if (count($lines) <= 1) { return $contents; } $startingSpaceCount = 9999999; for ($i = 1; $i < count($lines); $i++) { if (strlen(trim($lines[$i])) === 0) { continue; } $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i]))); } if ($startingSpaceCount > 0) { for ($i = 1; $i < count($lines); $i++) { $lines[$i] = substr($lines[$i], $startingSpaceCount); } } return implode("\n", $lines); } } getFilePath(); $file = $this->getExampleFileContents($filename); if (!$file) { return "** File not found : {$filename} **"; } return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); } public function setSourceDirectory($directory = '') { $this->sourceDirectory = $directory; } public function getSourceDirectory() { return $this->sourceDirectory; } public function setExampleDirectories(array $directories) { $this->exampleDirectories = $directories; } public function getExampleDirectories() { return $this->exampleDirectories; } private function getExampleFileContents($filename) { $normalizedPath = null; foreach ($this->exampleDirectories as $directory) { $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); if (is_readable($exampleFileFromConfig)) { $normalizedPath = $exampleFileFromConfig; break; } } if (!$normalizedPath) { if (is_readable($this->getExamplePathFromSource($filename))) { $normalizedPath = $this->getExamplePathFromSource($filename); } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); } elseif (is_readable($filename)) { $normalizedPath = $filename; } } return $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : null; } private function getExamplePathFromExampleDirectory($file) { return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; } private function constructExamplePath($directory, $file) { return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; } private function getExamplePathFromSource($file) { return sprintf( '%s%s%s', trim($this->getSourceDirectory(), '\\/'), DIRECTORY_SEPARATOR, trim($file, '"') ); } } version = $version; $this->description = $description; } public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) { Assert::nullOrString($body); if (empty($body)) { return new static(); } $matches = []; if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { return new static( null, null !== $descriptionFactory ? $descriptionFactory->create($body, $context) : null ); } return new static( $matches[1], $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context) ); } public function getVersion() { return $this->version; } public function __toString() { return $this->version . ($this->description ? ' ' . $this->description->render() : ''); } } refers = $refers; $this->description = $description; } public static function create( $body, DescriptionFactory $descriptionFactory = null, FqsenResolver $resolver = null, TypeContext $context = null ) { Assert::string($body); Assert::notEmpty($body); $parts = preg_split('/\s+/Su', $body, 2); return new static( $resolver->resolve($parts[0], $context), $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context) ); } public function getReference() { return $this->refers; } public function __toString() { return $this->refers . ($this->description ? ' ' . $this->description->render() : ''); } } name; } public function getDescription() { return $this->description; } public function render(Formatter $formatter = null) { if ($formatter === null) { $formatter = new Formatter\PassthroughFormatter(); } return $formatter->format($this); } } link = $link; $this->description = $description; } public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) { Assert::string($body); Assert::notNull($descriptionFactory); $parts = preg_split('/\s+/Su', $body, 2); $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; return new static($parts[0], $description); } public function getLink() { return $this->link; } public function __toString() { return $this->link . ($this->description ? ' ' . $this->description->render() : ''); } } authorName = $authorName; $this->authorEmail = $authorEmail; } public function getAuthorName() { return $this->authorName; } public function getEmail() { return $this->authorEmail; } public function __toString() { return $this->authorName . '<' . $this->authorEmail . '>'; } public static function create($body) { Assert::string($body); $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches); if (!$splitTagContent) { return null; } $authorName = trim($matches[1]); $email = isset($matches[2]) ? trim($matches[2]) : ''; return new static($authorName, $email); } } description) { $filePath = '"' . $this->filePath . '"'; if ($this->isURI) { $filePath = $this->isUriRelative($this->filePath) ? str_replace('%2F', '/', rawurlencode($this->filePath)) :$this->filePath; } $this->description = $filePath . ' ' . parent::getContent(); } return $this->description; } public static function create($body) { if (! preg_match('/^(?:\"([^\"]+)\"|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) { return null; } $filePath = null; $fileUri = null; if ('' !== $matches[1]) { $filePath = $matches[1]; } else { $fileUri = $matches[2]; } $startingLine = 1; $lineCount = null; $description = null; if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $matches[3], $matches)) { $startingLine = (int)$matches[1]; if (isset($matches[2]) && $matches[2] !== '') { $lineCount = (int)$matches[2]; } $description = $matches[3]; } return new static($filePath, $fileUri, $startingLine, $lineCount, $description); } public function getFilePath() { return $this->filePath; } public function setFilePath($filePath) { $this->isURI = false; $this->filePath = trim($filePath); $this->description = null; return $this; } public function setFileURI($uri) { $this->isURI = true; $this->description = null; $this->filePath = $this->isUriRelative($uri) ? rawurldecode(str_replace(array('/', '\\'), '%2F', $uri)) : $this->filePath = $uri; return $this; } public function __toString() { return $this->filePath . ($this->description ? ' ' . $this->description->render() : ''); } private function isUriRelative($uri) { return false === strpos($uri, ':'); } } refers = $refers; $this->description = $description; } public static function create( $body, FqsenResolver $resolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::string($body); Assert::allNotNull([$resolver, $descriptionFactory]); $parts = preg_split('/\s+/Su', $body, 2); $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; return new static($resolver->resolve($parts[0], $context), $description); } public function getReference() { return $this->refers; } public function __toString() { return $this->refers . ($this->description ? ' ' . $this->description->render() : ''); } } variableName = $variableName; $this->type = $type; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::stringNotEmpty($body); Assert::allNotNull([$typeResolver, $descriptionFactory]); $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); $type = null; $variableName = ''; if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { $type = $typeResolver->resolve(array_shift($parts), $context); array_shift($parts); } if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) { $variableName = array_shift($parts); array_shift($parts); if (substr($variableName, 0, 1) === '$') { $variableName = substr($variableName, 1); } } $description = $descriptionFactory->create(implode('', $parts), $context); return new static($variableName, $type, $description); } public function getVariableName() { return $this->variableName; } public function getType() { return $this->type; } public function __toString() { return ($this->type ? $this->type . ' ' : '') . '$' . $this->variableName . ($this->description ? ' ' . $this->description : ''); } } variableName = $variableName; $this->type = $type; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::stringNotEmpty($body); Assert::allNotNull([$typeResolver, $descriptionFactory]); $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); $type = null; $variableName = ''; if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { $type = $typeResolver->resolve(array_shift($parts), $context); array_shift($parts); } if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) { $variableName = array_shift($parts); array_shift($parts); if (substr($variableName, 0, 1) === '$') { $variableName = substr($variableName, 1); } } $description = $descriptionFactory->create(implode('', $parts), $context); return new static($variableName, $type, $description); } public function getVariableName() { return $this->variableName; } public function getType() { return $this->type; } public function __toString() { return ($this->type ? $this->type . ' ' : '') . '$' . $this->variableName . ($this->description ? ' ' . $this->description : ''); } } variableName = $variableName; $this->type = $type; $this->isVariadic = $isVariadic; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::stringNotEmpty($body); Assert::allNotNull([$typeResolver, $descriptionFactory]); $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); $type = null; $variableName = ''; $isVariadic = false; if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { $type = $typeResolver->resolve(array_shift($parts), $context); array_shift($parts); } if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$' || substr($parts[0], 0, 4) === '...$')) { $variableName = array_shift($parts); array_shift($parts); if (substr($variableName, 0, 3) === '...') { $isVariadic = true; $variableName = substr($variableName, 3); } if (substr($variableName, 0, 1) === '$') { $variableName = substr($variableName, 1); } } $description = $descriptionFactory->create(implode('', $parts), $context); return new static($variableName, $type, $isVariadic, $description); } public function getVariableName() { return $this->variableName; } public function getType() { return $this->type; } public function isVariadic() { return $this->isVariadic; } public function __toString() { return ($this->type ? $this->type . ' ' : '') . ($this->isVariadic() ? '...' : '') . '$' . $this->variableName . ($this->description ? ' ' . $this->description : ''); } } variableName = $variableName; $this->type = $type; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::stringNotEmpty($body); Assert::allNotNull([$typeResolver, $descriptionFactory]); $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); $type = null; $variableName = ''; if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { $type = $typeResolver->resolve(array_shift($parts), $context); array_shift($parts); } if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) { $variableName = array_shift($parts); array_shift($parts); if (substr($variableName, 0, 1) === '$') { $variableName = substr($variableName, 1); } } $description = $descriptionFactory->create(implode('', $parts), $context); return new static($variableName, $type, $description); } public function getVariableName() { return $this->variableName; } public function getType() { return $this->type; } public function __toString() { return ($this->type ? $this->type . ' ' : '') . '$' . $this->variableName . ($this->description ? ' ' . $this->description : ''); } } type = $type; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::string($body); Assert::allNotNull([$typeResolver, $descriptionFactory]); $parts = preg_split('/\s+/Su', $body, 2); $type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context); $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context); return new static($type, $description); } public function getType() { return $this->type; } public function __toString() { return $this->type . ' ' . $this->description; } } validateTagName($name); $this->name = $name; $this->description = $description; } public static function create( $body, $name = '', DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::string($body); Assert::stringNotEmpty($name); Assert::notNull($descriptionFactory); $description = $descriptionFactory && $body ? $descriptionFactory->create($body, $context) : null; return new static($name, $description); } public function __toString() { return ($this->description ? $this->description->render() : ''); } private function validateTagName($name) { if (! preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { throw new \InvalidArgumentException( 'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' . 'hyphens and backslashes.' ); } } } getName() . ' ' . (string)$tag; } } startingLine = (int)$startingLine; $this->lineCount = $lineCount !== null ? (int)$lineCount : null; $this->description = $description; } public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) { Assert::stringNotEmpty($body); Assert::notNull($descriptionFactory); $startingLine = 1; $lineCount = null; $description = null; if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) { $startingLine = (int)$matches[1]; if (isset($matches[2]) && $matches[2] !== '') { $lineCount = (int)$matches[2]; } $description = $matches[3]; } return new static($startingLine, $lineCount, $descriptionFactory->create($description, $context)); } public function getStartingLine() { return $this->startingLine; } public function getLineCount() { return $this->lineCount; } public function __toString() { return $this->startingLine . ($this->lineCount !== null ? ' ' . $this->lineCount : '') . ($this->description ? ' ' . $this->description->render() : ''); } } type = $type; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::string($body); Assert::allNotNull([$typeResolver, $descriptionFactory]); $parts = preg_split('/\s+/Su', $body, 2); $type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context); $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context); return new static($type, $description); } public function getType() { return $this->type; } public function __toString() { return $this->type . ' ' . $this->description; } } methodName = $methodName; $this->arguments = $this->filterArguments($arguments); $this->returnType = $returnType; $this->isStatic = $static; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::stringNotEmpty($body); Assert::allNotNull([ $typeResolver, $descriptionFactory ]); if (!preg_match( '/^ # Static keyword # Declares a static method ONLY if type is also present (?: (static) \s+ )? # Return type (?: ( (?:[\w\|_\\\\]+) # array notation (?:\[\])* )? \s+ )? # Legacy method name (not captured) (?: [\w_]+\(\)\s+ )? # Method name ([\w\|_\\\\]+) # Arguments (?: \(([^\)]*)\) )? \s* # Description (.*) $/sux', $body, $matches )) { return null; } list(, $static, $returnType, $methodName, $arguments, $description) = $matches; $static = $static === 'static'; $returnType = $typeResolver->resolve($returnType, $context); $description = $descriptionFactory->create($description, $context); if ('' !== $arguments) { $arguments = explode(',', $arguments); foreach($arguments as &$argument) { $argument = explode(' ', self::stripRestArg(trim($argument)), 2); if ($argument[0][0] === '$') { $argumentName = substr($argument[0], 1); $argumentType = new Void_(); } else { $argumentType = $typeResolver->resolve($argument[0], $context); $argumentName = ''; if (isset($argument[1])) { $argument[1] = self::stripRestArg($argument[1]); $argumentName = substr($argument[1], 1); } } $argument = [ 'name' => $argumentName, 'type' => $argumentType]; } } else { $arguments = []; } return new static($methodName, $arguments, $returnType, $static, $description); } public function getMethodName() { return $this->methodName; } public function getArguments() { return $this->arguments; } public function isStatic() { return $this->isStatic; } public function getReturnType() { return $this->returnType; } public function __toString() { $arguments = []; foreach ($this->arguments as $argument) { $arguments[] = $argument['type'] . ' $' . $argument['name']; } return ($this->isStatic() ? 'static ' : '') . (string)$this->returnType . ' ' . $this->methodName . '(' . implode(', ', $arguments) . ')' . ($this->description ? ' ' . $this->description->render() : ''); } private function filterArguments($arguments) { foreach ($arguments as &$argument) { if (is_string($argument)) { $argument = [ 'name' => $argument ]; } if (! isset($argument['type'])) { $argument['type'] = new Void_(); } $keys = array_keys($argument); if ($keys !== [ 'name', 'type' ]) { throw new \InvalidArgumentException( 'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true) ); } } return $arguments; } private static function stripRestArg($argument) { if (strpos($argument, '...') === 0) { $argument = trim(substr($argument, 3)); } return $argument; } } variableName = $variableName; $this->type = $type; $this->description = $description; } public static function create( $body, TypeResolver $typeResolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::stringNotEmpty($body); Assert::allNotNull([$typeResolver, $descriptionFactory]); $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); $type = null; $variableName = ''; if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { $type = $typeResolver->resolve(array_shift($parts), $context); array_shift($parts); } if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) { $variableName = array_shift($parts); array_shift($parts); if (substr($variableName, 0, 1) === '$') { $variableName = substr($variableName, 1); } } $description = $descriptionFactory->create(implode('', $parts), $context); return new static($variableName, $type, $description); } public function getVariableName() { return $this->variableName; } public function getType() { return $this->type; } public function __toString() { return ($this->type ? $this->type . ' ' : '') . '$' . $this->variableName . ($this->description ? ' ' . $this->description : ''); } } refers = $refers; $this->description = $description; } public static function create( $body, FqsenResolver $resolver = null, DescriptionFactory $descriptionFactory = null, TypeContext $context = null ) { Assert::string($body); Assert::allNotNull([$resolver, $descriptionFactory]); $parts = preg_split('/\s+/Su', $body, 2); return new static( $resolver->resolve($parts[0], $context), $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context) ); } public function getReference() { return $this->refers; } public function __toString() { return $this->refers . ' ' . $this->description->render(); } } version = $version; $this->description = $description; } public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) { Assert::nullOrString($body); if (empty($body)) { return new static(); } $matches = []; if (! preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { return null; } return new static( $matches[1], $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context) ); } public function getVersion() { return $this->version; } public function __toString() { return $this->version . ($this->description ? ' ' . $this->description->render() : ''); } } version = $version; $this->description = $description; } public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) { Assert::nullOrString($body); if (empty($body)) { return new static(); } $matches = []; if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { return null; } return new static( $matches[1], $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context) ); } public function getVersion() { return $this->version; } public function __toString() { return $this->version . ($this->description ? ' ' . $this->description->render() : ''); } } '\phpDocumentor\Reflection\DocBlock\Tags\Author', 'covers' => '\phpDocumentor\Reflection\DocBlock\Tags\Covers', 'deprecated' => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated', 'link' => '\phpDocumentor\Reflection\DocBlock\Tags\Link', 'method' => '\phpDocumentor\Reflection\DocBlock\Tags\Method', 'param' => '\phpDocumentor\Reflection\DocBlock\Tags\Param', 'property-read' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead', 'property' => '\phpDocumentor\Reflection\DocBlock\Tags\Property', 'property-write' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite', 'return' => '\phpDocumentor\Reflection\DocBlock\Tags\Return_', 'see' => '\phpDocumentor\Reflection\DocBlock\Tags\See', 'since' => '\phpDocumentor\Reflection\DocBlock\Tags\Since', 'source' => '\phpDocumentor\Reflection\DocBlock\Tags\Source', 'throw' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', 'throws' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', 'uses' => '\phpDocumentor\Reflection\DocBlock\Tags\Uses', 'var' => '\phpDocumentor\Reflection\DocBlock\Tags\Var_', 'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version' ]; private $tagHandlerParameterCache = []; private $fqsenResolver; private $serviceLocator = []; public function __construct(FqsenResolver $fqsenResolver, array $tagHandlers = null) { $this->fqsenResolver = $fqsenResolver; if ($tagHandlers !== null) { $this->tagHandlerMappings = $tagHandlers; } $this->addService($fqsenResolver, FqsenResolver::class); } public function create($tagLine, TypeContext $context = null) { if (! $context) { $context = new TypeContext(''); } list($tagName, $tagBody) = $this->extractTagParts($tagLine); return $this->createTag($tagBody, $tagName, $context); } public function addParameter($name, $value) { $this->serviceLocator[$name] = $value; } public function addService($service, $alias = null) { $this->serviceLocator[$alias ?: get_class($service)] = $service; } public function registerTagHandler($tagName, $handler) { Assert::stringNotEmpty($tagName); Assert::stringNotEmpty($handler); Assert::classExists($handler); Assert::implementsInterface($handler, StaticMethod::class); if (strpos($tagName, '\\') && $tagName[0] !== '\\') { throw new \InvalidArgumentException( 'A namespaced tag must have a leading backslash as it must be fully qualified' ); } $this->tagHandlerMappings[$tagName] = $handler; } private function extractTagParts($tagLine) { $matches = array(); if (! preg_match('/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)?/us', $tagLine, $matches)) { throw new \InvalidArgumentException( 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' ); } if (count($matches) < 3) { $matches[] = ''; } return array_slice($matches, 1); } private function createTag($body, $name, TypeContext $context) { $handlerClassName = $this->findHandlerClassName($name, $context); $arguments = $this->getArgumentsForParametersFromWiring( $this->fetchParametersForHandlerFactoryMethod($handlerClassName), $this->getServiceLocatorWithDynamicParameters($context, $name, $body) ) ; return call_user_func_array([$handlerClassName, 'create'], $arguments); } private function findHandlerClassName($tagName, TypeContext $context) { $handlerClassName = Generic::class; if (isset($this->tagHandlerMappings[$tagName])) { $handlerClassName = $this->tagHandlerMappings[$tagName]; } elseif ($this->isAnnotation($tagName)) { } return $handlerClassName; } private function getArgumentsForParametersFromWiring($parameters, $locator) { $arguments = []; foreach ($parameters as $index => $parameter) { $typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null; if (isset($locator[$typeHint])) { $arguments[] = $locator[$typeHint]; continue; } $parameterName = $parameter->getName(); if (isset($locator[$parameterName])) { $arguments[] = $locator[$parameterName]; continue; } $arguments[] = null; } return $arguments; } private function fetchParametersForHandlerFactoryMethod($handlerClassName) { if (! isset($this->tagHandlerParameterCache[$handlerClassName])) { $methodReflection = new \ReflectionMethod($handlerClassName, 'create'); $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); } return $this->tagHandlerParameterCache[$handlerClassName]; } private function getServiceLocatorWithDynamicParameters(TypeContext $context, $tagName, $tagBody) { $locator = array_merge( $this->serviceLocator, [ 'name' => $tagName, 'body' => $tagBody, TypeContext::class => $context ] ); return $locator; } private function isAnnotation($tagContent) { return false; } } indent = $indent; $this->indentString = $indentString; $this->isFirstLineIndented = $indentFirstLine; $this->lineLength = $lineLength; } public function getDocComment(DocBlock $docblock) { $indent = str_repeat($this->indentString, $this->indent); $firstIndent = $this->isFirstLineIndented ? $indent : ''; $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null; $text = $this->removeTrailingSpaces( $indent, $this->addAsterisksForEachLine( $indent, $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength) ) ); $comment = "{$firstIndent}/**\n{$indent} * {$text}\n{$indent} *\n"; $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); $comment .= $indent . ' */'; return $comment; } private function removeTrailingSpaces($indent, $text) { return str_replace("\n{$indent} * \n", "\n{$indent} *\n", $text); } private function addAsterisksForEachLine($indent, $text) { return str_replace("\n", "\n{$indent} * ", $text); } private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, $wrapLength) { $text = $docblock->getSummary() . ((string)$docblock->getDescription() ? "\n\n" . $docblock->getDescription() : ''); if ($wrapLength !== null) { $text = wordwrap($text, $wrapLength); return $text; } return $text; } private function addTagBlock(DocBlock $docblock, $wrapLength, $indent, $comment) { foreach ($docblock->getTags() as $tag) { $formatter = new DocBlock\Tags\Formatter\PassthroughFormatter(); $tagText = $formatter->format($tag); if ($wrapLength !== null) { $tagText = wordwrap($tagText, $wrapLength); } $tagText = str_replace("\n", "\n{$indent} * ", $tagText); $comment .= "{$indent} * {$tagText}\n"; } return $comment; } } summary = $summary; $this->description = $description ?: new DocBlock\Description(''); foreach ($tags as $tag) { $this->addTag($tag); } $this->context = $context; $this->location = $location; $this->isTemplateEnd = $isTemplateEnd; $this->isTemplateStart = $isTemplateStart; } public function getSummary() { return $this->summary; } public function getDescription() { return $this->description; } public function getContext() { return $this->context; } public function getLocation() { return $this->location; } public function isTemplateStart() { return $this->isTemplateStart; } public function isTemplateEnd() { return $this->isTemplateEnd; } public function getTags() { return $this->tags; } public function getTagsByName($name) { Assert::string($name); $result = array(); foreach ($this->getTags() as $tag) { if ($tag->getName() != $name) { continue; } $result[] = $tag; } return $result; } public function hasTag($name) { Assert::string($name); foreach ($this->getTags() as $tag) { if ($tag->getName() == $name) { return true; } } return false; } private function addTag(Tag $tag) { $this->tags[] = $tag; } } descriptionFactory = $descriptionFactory; $this->tagFactory = $tagFactory; } public static function createInstance(array $additionalTags = []) { $fqsenResolver = new FqsenResolver(); $tagFactory = new StandardTagFactory($fqsenResolver); $descriptionFactory = new DescriptionFactory($tagFactory); $tagFactory->addService($descriptionFactory); $tagFactory->addService(new TypeResolver($fqsenResolver)); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { $docBlockFactory->registerTagHandler($tagName, $tagHandler); } return $docBlockFactory; } public function create($docblock, Types\Context $context = null, Location $location = null) { if (is_object($docblock)) { if (!method_exists($docblock, 'getDocComment')) { $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; throw new \InvalidArgumentException($exceptionMessage); } $docblock = $docblock->getDocComment(); } Assert::stringNotEmpty($docblock); if ($context === null) { $context = new Types\Context(''); } $parts = $this->splitDocBlock($this->stripDocComment($docblock)); list($templateMarker, $summary, $description, $tags) = $parts; return new DocBlock( $summary, $description ? $this->descriptionFactory->create($description, $context) : null, array_filter($this->parseTagBlock($tags, $context), function($tag) { return $tag instanceof Tag; }), $context, $location, $templateMarker === '#@+', $templateMarker === '#@-' ); } public function registerTagHandler($tagName, $handler) { $this->tagFactory->registerTagHandler($tagName, $handler); } private function stripDocComment($comment) { $comment = trim(preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', '$1', $comment)); if (substr($comment, -2) == '*/') { $comment = trim(substr($comment, 0, -2)); } return str_replace(array("\r\n", "\r"), "\n", $comment); } private function splitDocBlock($comment) { if (strpos($comment, '@') === 0) { return array('', '', '', $comment); } $comment = preg_replace('/\h*$/Sum', '', $comment); preg_match( '/ \A # 1. Extract the template marker (?:(\#\@\+|\#\@\-)\n?)? # 2. Extract the summary (?: (?! @\pL ) # The summary may not start with an @ ( [^\n.]+ (?: (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line [^\n.]+ # Include anything else )* \.? )? ) # 3. Extract the description (?: \s* # Some form of whitespace _must_ precede a description because a summary must be there (?! @\pL ) # The description may not start with an @ ( [^\n]+ (?: \n+ (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line [^\n]+ # Include anything else )* ) )? # 4. Extract the tags (anything that follows) (\s+ [\s\S]*)? # everything that follows /ux', $comment, $matches ); array_shift($matches); while (count($matches) < 4) { $matches[] = ''; } return $matches; } private function parseTagBlock($tags, Types\Context $context) { $tags = $this->filterTagBlock($tags); if (!$tags) { return []; } $result = $this->splitTagBlockIntoTagLines($tags); foreach ($result as $key => $tagLine) { $result[$key] = $this->tagFactory->create(trim($tagLine), $context); } return $result; } private function splitTagBlockIntoTagLines($tags) { $result = array(); foreach (explode("\n", $tags) as $tag_line) { if (isset($tag_line[0]) && ($tag_line[0] === '@')) { $result[] = $tag_line; } else { $result[count($result) - 1] .= "\n" . $tag_line; } } return $result; } private function filterTagBlock($tags) { $tags = trim($tags); if (!$tags) { return null; } if ('@' !== $tags[0]) { throw new \LogicException('A tag block started with text instead of an at-sign(@): ' . $tags); } return $tags; } } description = $description; } public static function create($body, DescriptionFactory $descriptionFactory = null, Context $context = null) { Assert::string($body); Assert::notNull($descriptionFactory); return new static($descriptionFactory->create($body, $context)); } public function __toString() { return (string)$this->description; } } $docComment = << MyTag::class]; $factory = DocBlockFactory::createInstance($customTags); $docblock = $factory->create($docComment); $customTagObjects = $docblock->getTagsByName('my-tag'); $serializer = new Serializer(); $reconstitutedDocComment = $serializer->getDocComment($docblock); create($docComment); $description = $docblock->getDescription(); $receivedDocComment = <<render(); create($docComment); $serializer = new Serializer(); $reconstitutedDocComment = $serializer->getDocComment($docblock); create($docComment); $summary = $docblock->getSummary(); $description = $docblock->getDescription(); create($docComment); $hasSeeTag = $docblock->hasTag('see'); $tags = $docblock->getTags(); $seeTags = $docblock->getTagsByName('see'); fqsen = $fqsen; if (isset($matches[2])) { $this->name = $matches[2]; } else { $matches = explode('\\', $fqsen); $this->name = trim(end($matches), '()'); } } public function __toString() { return $this->fqsen; } public function getName() { return $this->name; } } lineNumber = $lineNumber; $this->columnNumber = $columnNumber; } public function getLineNumber() { return $this->lineNumber; } public function getColumnNumber() { return $this->columnNumber; } } getDeclaringClass(); } $fileName = $reflector->getFileName(); $namespace = $reflector->getNamespaceName(); if (file_exists($fileName)) { return $this->createForNamespace($namespace, file_get_contents($fileName)); } return new Context($namespace, []); } public function createForNamespace($namespace, $fileContents) { $namespace = trim($namespace, '\\'); $useStatements = []; $currentNamespace = ''; $tokens = new \ArrayIterator(token_get_all($fileContents)); while ($tokens->valid()) { switch ($tokens->current()[0]) { case T_NAMESPACE: $currentNamespace = $this->parseNamespace($tokens); break; case T_CLASS: $braceLevel = 0; $firstBraceFound = false; while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) { if ($tokens->current() === '{' || $tokens->current()[0] === T_CURLY_OPEN || $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) { if (!$firstBraceFound) { $firstBraceFound = true; } $braceLevel++; } if ($tokens->current() === '}') { $braceLevel--; } $tokens->next(); } break; case T_USE: if ($currentNamespace === $namespace) { $useStatements = array_merge($useStatements, $this->parseUseStatement($tokens)); } break; } $tokens->next(); } return new Context($namespace, $useStatements); } private function parseNamespace(\ArrayIterator $tokens) { $this->skipToNextStringOrNamespaceSeparator($tokens); $name = ''; while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) ) { $name .= $tokens->current()[1]; $tokens->next(); } return $name; } private function parseUseStatement(\ArrayIterator $tokens) { $uses = []; $continue = true; while ($continue) { $this->skipToNextStringOrNamespaceSeparator($tokens); list($alias, $fqnn) = $this->extractUseStatement($tokens); $uses[$alias] = $fqnn; if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) { $continue = false; } } return $uses; } private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens) { while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) { $tokens->next(); } } private function extractUseStatement(\ArrayIterator $tokens) { $result = ['']; while ($tokens->valid() && ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR) && ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE) ) { if ($tokens->current()[0] === T_AS) { $result[] = ''; } if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) { $result[count($result) - 1] .= $tokens->current()[1]; } $tokens->next(); } if (count($result) == 1) { $backslashPos = strrpos($result[0], '\\'); if (false !== $backslashPos) { $result[] = substr($result[0], $backslashPos + 1); } else { $result[] = $result[0]; } } return array_reverse($result); } } namespace = ('global' !== $namespace && 'default' !== $namespace) ? trim((string)$namespace, '\\') : ''; foreach ($namespaceAliases as $alias => $fqnn) { if ($fqnn[0] === '\\') { $fqnn = substr($fqnn, 1); } if ($fqnn[strlen($fqnn) - 1] === '\\') { $fqnn = substr($fqnn, 0, -1); } $namespaceAliases[$alias] = $fqnn; } $this->namespaceAliases = $namespaceAliases; } public function getNamespace() { return $this->namespace; } public function getNamespaceAliases() { return $this->namespaceAliases; } } valueType = $valueType; $this->keyType = $keyType; } public function getKeyType() { return $this->keyType; } public function getValueType() { return $this->valueType; } public function __toString() { if ($this->valueType instanceof Mixed) { return 'array'; } return $this->valueType . '[]'; } } fqsen = $fqsen; } public function getFqsen() { return $this->fqsen; } public function __toString() { if ($this->fqsen) { return (string)$this->fqsen; } return 'object'; } } types = $types; } public function get($index) { if (!$this->has($index)) { return null; } return $this->types[$index]; } public function has($index) { return isset($this->types[$index]); } public function __toString() { return implode('|', $this->types); } } isFqsen($fqsen)) { return new Fqsen($fqsen); } return $this->resolvePartialStructuralElementName($fqsen, $context); } private function isFqsen($type) { return strpos($type, self::OPERATOR_NAMESPACE) === 0; } private function resolvePartialStructuralElementName($type, Context $context) { $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2); $namespaceAliases = $context->getNamespaceAliases(); if (!isset($namespaceAliases[$typeParts[0]])) { $namespace = $context->getNamespace(); if ('' !== $namespace) { $namespace .= self::OPERATOR_NAMESPACE; } return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type); } $typeParts[0] = $namespaceAliases[$typeParts[0]]; return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)); } } 'phpDocumentor\Reflection\Types\String_', 'int' => 'phpDocumentor\Reflection\Types\Integer', 'integer' => 'phpDocumentor\Reflection\Types\Integer', 'bool' => 'phpDocumentor\Reflection\Types\Boolean', 'boolean' => 'phpDocumentor\Reflection\Types\Boolean', 'float' => 'phpDocumentor\Reflection\Types\Float_', 'double' => 'phpDocumentor\Reflection\Types\Float_', 'object' => 'phpDocumentor\Reflection\Types\Object_', 'mixed' => 'phpDocumentor\Reflection\Types\Mixed', 'array' => 'phpDocumentor\Reflection\Types\Array_', 'resource' => 'phpDocumentor\Reflection\Types\Resource', 'void' => 'phpDocumentor\Reflection\Types\Void_', 'null' => 'phpDocumentor\Reflection\Types\Null_', 'scalar' => 'phpDocumentor\Reflection\Types\Scalar', 'callback' => 'phpDocumentor\Reflection\Types\Callable_', 'callable' => 'phpDocumentor\Reflection\Types\Callable_', 'false' => 'phpDocumentor\Reflection\Types\Boolean', 'true' => 'phpDocumentor\Reflection\Types\Boolean', 'self' => 'phpDocumentor\Reflection\Types\Self_', '$this' => 'phpDocumentor\Reflection\Types\This', 'static' => 'phpDocumentor\Reflection\Types\Static_' ); private $fqsenResolver; public function __construct(FqsenResolver $fqsenResolver = null) { $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver(); } public function resolve($type, Context $context = null) { if (!is_string($type)) { throw new \InvalidArgumentException( 'Attempted to resolve type but it appeared not to be a string, received: ' . var_export($type, true) ); } $type = trim($type); if (!$type) { throw new \InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty'); } if ($context === null) { $context = new Context(''); } switch (true) { case $this->isKeyword($type): return $this->resolveKeyword($type); case ($this->isCompoundType($type)): return $this->resolveCompoundType($type, $context); case $this->isTypedArray($type): return $this->resolveTypedArray($type, $context); case $this->isFqsen($type): return $this->resolveTypedObject($type); case $this->isPartialStructuralElementName($type): return $this->resolveTypedObject($type, $context); default: throw new \RuntimeException( 'Unable to resolve type "' . $type . '", there is no known method to resolve it' ); } } public function addKeyword($keyword, $typeClassName) { if (!class_exists($typeClassName)) { throw new \InvalidArgumentException( 'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class' . ' but we could not find the class ' . $typeClassName ); } if (!in_array(Type::class, class_implements($typeClassName))) { throw new \InvalidArgumentException( 'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"' ); } $this->keywords[$keyword] = $typeClassName; } private function isTypedArray($type) { return substr($type, -2) === self::OPERATOR_ARRAY; } private function isKeyword($type) { return in_array(strtolower($type), array_keys($this->keywords), true); } private function isPartialStructuralElementName($type) { return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type); } private function isFqsen($type) { return strpos($type, self::OPERATOR_NAMESPACE) === 0; } private function isCompoundType($type) { return strpos($type, '|') !== false; } private function resolveTypedArray($type, Context $context) { return new Array_($this->resolve(substr($type, 0, -2), $context)); } private function resolveKeyword($type) { $className = $this->keywords[strtolower($type)]; return new $className(); } private function resolveTypedObject($type, Context $context = null) { return new Object_($this->fqsenResolver->resolve($type, $context)); } private function resolveCompoundType($type, Context $context) { $types = []; foreach (explode('|', $type) as $part) { $types[] = $this->resolve($part, $context); } return new Compound($types); } } createForNamespace('My\Example', file_get_contents('Classy.php')); var_dump((string)$typeResolver->resolve('Types\Resolver', $context)); var_dump((string)$typeResolver->resolve('string', $context)); var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context)); createFromReflector(new ReflectionMethod('My\\Example\\Classy', '__construct')); var_dump((string)$typeResolver->resolve('Types\Resolver', $context)); var_dump((string)$typeResolver->resolve('string', $context)); var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context)); var_dump((string)$fqsenResolver->resolve('string', $context)); resolve('string|integer')); var_dump((string)$typeResolver->resolve('string|integer')); 'Mockery' ]); var_dump((string)$typeResolver->resolve('Types\Resolver|m\MockInterface', $context)); createFromReflector(new ReflectionClass('My\\Example\\Classy')); var_dump((string)$typeResolver->resolve('Types\Resolver', $context)); var_dump((string)$typeResolver->resolve('string', $context)); var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context)); var_dump((string)$fqsenResolver->resolve('string', $context)); resolve('Types\Resolver::resolveFqsen()', $context)); var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context)); __get($key); } if (is_object($collection) && ! $collection instanceof \ArrayAccess) { return $collection->$key; } else { return $collection[$key]; } } public static function setValue(&$collection, $key, $value) { if (is_object($collection) && ! $collection instanceof \ArrayAccess) { return $collection->$key = $value; } else { return $collection[$key] = $value; } } public static function unsetValue(&$collection, $key) { if (is_object($collection) && ! $collection instanceof \ArrayAccess) { unset($collection->$key); } else { unset($collection[$key]); } } public static function arrayValues($collection) { if (is_array($collection)) { return array_values($collection); } else if (is_object($collection)) { return array_values((array) $collection); } throw new JSONPathException("Invalid variable type for arrayValues"); } } expression = $expression; $this->expressionLength = strlen($expression); } public function parseExpressionTokens() { $dotIndexDepth = 0; $squareBracketDepth = 0; $capturing = false; $tokenValue = ''; $tokens = []; for ($i = 0; $i < $this->expressionLength; $i++) { $char = $this->expression[$i]; if ($squareBracketDepth === 0) { if ($char === '.') { if ($this->lookAhead($i, 1) === ".") { $tokens[] = new JSONPathToken(JSONPathToken::T_RECURSIVE, null); } continue; } } if ($char === '[') { $squareBracketDepth += 1; if ($squareBracketDepth === 1) { continue; } } if ($char === ']') { $squareBracketDepth -= 1; if ($squareBracketDepth === 0) { continue; } } if ($squareBracketDepth > 0) { $tokenValue .= $char; if ($this->lookAhead($i, 1) === ']' && $squareBracketDepth === 1) { $tokens[] = $this->createToken($tokenValue); $tokenValue = ''; } } if ($squareBracketDepth === 0) { $tokenValue .= $char; if ($char === "." && $dotIndexDepth > 1) { $tokens[] = $this->createToken($tokenValue); $tokenValue = ''; continue; } if ($this->lookAhead($i, 1) === '.' || $this->lookAhead($i, 1) === '[' || $this->atEnd($i)) { $tokens[] = $this->createToken($tokenValue); $tokenValue = ''; $dotIndexDepth -= 1; } } } if ($tokenValue !== '') { $tokens[] = $this->createToken($tokenValue); $tokenValue = ''; } return $tokens; } protected function lookAhead($pos, $forward = 1) { return isset($this->expression[$pos + $forward]) ? $this->expression[$pos + $forward] : null; } protected function atEnd($pos) { return $pos === $this->expressionLength; } public function parseExpression() { $tokens = $this->parseExpressionTokens(); return $tokens; } protected function createToken($value) { if (preg_match('/^(' . static::MATCH_INDEX . ')$/x', $value, $matches)) { return new JSONPathToken(JSONPathToken::T_INDEX, $value); } if (preg_match('/^' . static::MATCH_INDEXES . '$/x', $value, $matches)) { $value = explode(',', $value); foreach ($value as $i => $v) { $value[$i] = (int) trim($v); } return new JSONPathToken(JSONPathToken::T_INDEXES, $value); } if (preg_match('/^' . static::MATCH_SLICE . '$/x', $value, $matches)) { $parts = explode(':', $value); $value = [ 'start' => isset($parts[0]) && $parts[0] !== "" ? (int) $parts[0] : null, 'end' => isset($parts[1]) && $parts[1] !== "" ? (int) $parts[1] : null, 'step' => isset($parts[2]) && $parts[2] !== "" ? (int) $parts[2] : null, ]; return new JSONPathToken(JSONPathToken::T_SLICE, $value); } if (preg_match('/^' . static::MATCH_QUERY_RESULT . '$/x', $value)) { $value = substr($value, 1, -1); return new JSONPathToken(JSONPathToken::T_QUERY_RESULT, $value); } if (preg_match('/^' . static::MATCH_QUERY_MATCH . '$/x', $value)) { $value = substr($value, 2, -1); return new JSONPathToken(JSONPathToken::T_QUERY_MATCH, $value); } if (preg_match('/^' . static::MATCH_INDEX_IN_SINGLE_QUOTES . '$/x', $value, $matches)) { $value = $matches[1]; $value = trim($value); return new JSONPathToken(JSONPathToken::T_INDEX, $value); } if (preg_match('/^' . static::MATCH_INDEX_IN_DOUBLE_QUOTES . '$/x', $value, $matches)) { $value = $matches[1]; $value = trim($value); return new JSONPathToken(JSONPathToken::T_INDEX, $value); } throw new JSONPathException("Unable to parse token {$value} in expression: $this->expression"); } } data = $data; $this->options = $options; } public function find($expression) { $tokens = $this->parseTokens($expression); $collectionData = [$this->data]; foreach ($tokens as $token) { $filter = $token->buildFilter($this->options); $filteredData = []; foreach ($collectionData as $value) { if (AccessHelper::isCollectionType($value)) { $filteredValue = $filter->filter($value); $filteredData = array_merge($filteredData, $filteredValue); } } $collectionData = $filteredData; } return new static($collectionData, $this->options); } public function first() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys)) { return null; } $value = isset($this->data[$keys[0]]) ? $this->data[$keys[0]] : null; return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function last() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys)) { return null; } $value = $this->data[end($keys)] ? $this->data[end($keys)] : null; return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function firstKey() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys)) { return null; } return $keys[0]; } public function lastKey() { $keys = AccessHelper::collectionKeys($this->data); if (empty($keys) || end($keys) === false) { return null; } return end($keys); } public function parseTokens($expression) { $cacheKey = md5($expression); if (isset(static::$tokenCache[$cacheKey])) { return static::$tokenCache[$cacheKey]; } $lexer = new JSONPathLexer($expression); $tokens = $lexer->parseExpression(); static::$tokenCache[$cacheKey] = $tokens; return $tokens; } public function data() { return $this->data; } public function offsetExists($offset) { return AccessHelper::keyExists($this->data, $offset); } public function offsetGet($offset) { $value = AccessHelper::getValue($this->data, $offset); return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function offsetSet($offset, $value) { if ($offset === null) { $this->data[] = $value; } else { AccessHelper::setValue($this->data, $offset, $value); } } public function offsetUnset($offset) { AccessHelper::unsetValue($this->data, $offset); } public function jsonSerialize() { return $this->data; } public function current() { $value = current($this->data); return AccessHelper::isCollectionType($value) ? new static($value, $this->options) : $value; } public function next() { next($this->data); } public function key() { return key($this->data); } public function valid() { return key($this->data) !== null; } public function rewind() { reset($this->data); } public function __get($key) { return $this->offsetExists($key) ? $this->offsetGet($key) : null; } } token->value as $index) { if (AccessHelper::keyExists($collection, $index, $this->magicIsAllowed)) { $return[] = AccessHelper::getValue($collection, $index, $this->magicIsAllowed); } } return $return; } } \w+)\s*(?-|\+|\*|\/)\s*(?\d+)/', $this->token->value, $matches); $matchKey = $matches['key']; if (AccessHelper::keyExists($collection, $matchKey, $this->magicIsAllowed)) { $value = AccessHelper::getValue($collection, $matchKey, $this->magicIsAllowed); } else { if ($matches['key'] === 'length') { $value = count($collection); } else { return; } } switch ($matches['operator']) { case '+': $resultKey = $value + $matches['numeric']; break; case '*': $resultKey = $value * $matches['numeric']; break; case '-': $resultKey = $value - $matches['numeric']; break; case '/': $resultKey = $value / $matches['numeric']; break; default: throw new JSONPathException("Unsupported operator in expression"); break; } if (AccessHelper::keyExists($collection, $resultKey, $this->magicIsAllowed)) { $result[] = AccessHelper::getValue($collection, $resultKey, $this->magicIsAllowed); } return $result; } } token->value['start']; $end = $this->token->value['end']; $step = $this->token->value['step'] ?: 1; if ($start !== null && $end !== null) { } if ($start !== null && $end === null) { $end = $start < 0 ? -1 : $length - 1; } if ($start === null && $end !== null) { $start = 0; } if ($start === null && $end === null) { $start = 0; $end = $length - 1; } if ($end < 0 && $start >= 0) { $end = ($length + $end) - 1; } if ($start < 0 && $end === null) { $end = -1; } for ($i = $start; $i <= $end; $i += $step) { $index = $i; if ($i < 0) { $index = $length + $i; } if (AccessHelper::keyExists($collection, $index, $this->magicIsAllowed)) { $result[] = $collection[$index]; } } return $result; } } \w+)|\[["\'](?.*?)["\']\]) (\s*(?==|=|<>|!==|!=|>|<)\s*(?.+))? '; public function filter($collection) { $return = []; preg_match('/^' . static::MATCH_QUERY_OPERATORS . '$/x', $this->token->value, $matches); if (!isset($matches[1])) { throw new \Exception("Malformed filter query"); } $key = $matches['key'] ?: $matches['keySquare']; if ($key === "") { throw new \Exception("Malformed filter query: key was not set"); } $operator = isset($matches['operator']) ? $matches['operator'] : null; $comparisonValue = isset($matches['comparisonValue']) ? $matches['comparisonValue'] : null; if (strtolower($comparisonValue) === "false") { $comparisonValue = false; } if (strtolower($comparisonValue) === "true") { $comparisonValue = true; } if (strtolower($comparisonValue) === "null") { $comparisonValue = null; } $comparisonValue = preg_replace('/^[\'"]/', '', $comparisonValue); $comparisonValue = preg_replace('/[\'"]$/', '', $comparisonValue); foreach ($collection as $value) { if (AccessHelper::keyExists($value, $key, $this->magicIsAllowed)) { $value1 = AccessHelper::getValue($value, $key, $this->magicIsAllowed); if ($operator === null && AccessHelper::keyExists($value, $key, $this->magicIsAllowed)) { $return[] = $value; } if (($operator === "=" || $operator === "==") && $value1 == $comparisonValue) { $return[] = $value; } if (($operator === "!=" || $operator === "!==" || $operator === "<>") && $value1 != $comparisonValue) { $return[] = $value; } if ($operator == ">" && $value1 > $comparisonValue) { $return[] = $value; } if ($operator == "<" && $value1 < $comparisonValue) { $return[] = $value; } } } return $return; } } token = $token; $this->options = $options; $this->magicIsAllowed = $this->options & JSONPath::ALLOW_MAGIC; } public function isMagicAllowed() { return $this->magicIsAllowed; } abstract public function filter($collection); } recurse($result, $collection); return $result; } private function recurse(& $result, $data) { $result[] = $data; if (AccessHelper::isCollectionType($data)) { foreach (AccessHelper::arrayValues($data) as $key => $value) { $results[] = $value; if (AccessHelper::isCollectionType($value)) { $this->recurse($result, $value); } } } } } token->value, $this->magicIsAllowed)) { return [ AccessHelper::getValue($collection, $this->token->value, $this->magicIsAllowed) ]; } else if ($this->token->value === "*") { return AccessHelper::arrayValues($collection); } return []; } } validateType($type); $this->type = $type; $this->value = $value; } public function validateType($type) { if (!in_array($type, static::getTypes(), true)) { throw new JSONPathException('Invalid token: ' . $type); } } public static function getTypes() { return [ static::T_INDEX, static::T_RECURSIVE, static::T_QUERY_RESULT, static::T_QUERY_MATCH, static::T_SLICE, static::T_INDEXES, ]; } public function buildFilter($options) { $filterClass = 'Flow\\JSONPath\\Filters\\' . ucfirst($this->type) . 'Filter'; if (! class_exists($filterClass)) { throw new JSONPathException("No filter class exists for token [{$this->type}]"); } return new $filterClass($this, $options); } }writeResult = $writeResult; $this->insertedId = $insertedId; $this->isAcknowledged = $writeResult->isAcknowledged(); } public function getInsertedCount() { if ($this->isAcknowledged) { return $this->writeResult->getInsertedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getInsertedId() { return $this->insertedId; } public function isAcknowledged() { return $this->writeResult->isAcknowledged(); } } writeResult = $writeResult; $this->insertedIds = $insertedIds; $this->isAcknowledged = $writeResult->isAcknowledged(); } public function getDeletedCount() { if ($this->isAcknowledged) { return $this->writeResult->getDeletedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getInsertedCount() { if ($this->isAcknowledged) { return $this->writeResult->getInsertedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getInsertedIds() { return $this->insertedIds; } public function getMatchedCount() { if ($this->isAcknowledged) { return $this->writeResult->getMatchedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getModifiedCount() { if ($this->isAcknowledged) { return $this->writeResult->getModifiedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getUpsertedCount() { if ($this->isAcknowledged) { return $this->writeResult->getUpsertedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getUpsertedIds() { if ($this->isAcknowledged) { return $this->writeResult->getUpsertedIds(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function isAcknowledged() { return $this->isAcknowledged; } } writeResult = $writeResult; $this->isAcknowledged = $writeResult->isAcknowledged(); } public function getDeletedCount() { if ($this->isAcknowledged) { return $this->writeResult->getDeletedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function isAcknowledged() { return $this->isAcknowledged; } } databases = $databases; } public function current() { return new DatabaseInfo(current($this->databases)); } public function key() { return key($this->databases); } public function next() { next($this->databases); } public function rewind() { reset($this->databases); } public function valid() { return key($this->databases) !== null; } } exchangeArray($properties); return $array; } public function bsonSerialize() { return array_values($this->getArrayCopy()); } public function bsonUnserialize(array $data) { self::__construct($data); } } info = $info; } public function __debugInfo() { return $this->info; } public function getCappedMax() { return isset($this->info['options']['max']) ? (integer) $this->info['options']['max'] : null; } public function getCappedSize() { return isset($this->info['options']['size']) ? (integer) $this->info['options']['size'] : null; } public function getName() { return (string) $this->info['name']; } public function getOptions() { return isset($this->info['options']) ? (array) $this->info['options'] : []; } public function isCapped() { return ! empty($this->info['options']['capped']); } } info = $info; } public function __debugInfo() { return $this->info; } public function getName() { return (string) $this->info['name']; } public function getSizeOnDisk() { return (integer) $this->info['sizeOnDisk']; } public function isEmpty() { return (boolean) $this->info['empty']; } } $order) { if ( ! is_int($order) && ! is_float($order) && ! is_string($order)) { throw InvalidArgumentException::invalidType(sprintf('order value for "%s" field within "key" option', $fieldName), $order, 'numeric or string'); } } if ( ! isset($index['ns'])) { throw new InvalidArgumentException('Required "ns" option is missing from index specification'); } if ( ! is_string($index['ns'])) { throw InvalidArgumentException::invalidType('"ns" option', $index['ns'], 'string'); } if ( ! isset($index['name'])) { $index['name'] = \MongoDB\generate_index_name($index['key']); } if ( ! is_string($index['name'])) { throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string'); } $this->index = $index; } public function __toString() { return $this->index['name']; } public function bsonSerialize() { return $this->index; } } info = $info; } public function __debugInfo() { return $this->info; } public function getKey() { return (array) $this->info['key']; } public function getName() { return (string) $this->info['name']; } public function getNamespace() { return (string) $this->info['ns']; } public function getVersion() { return (integer) $this->info['v']; } public function isSparse() { return ! empty($this->info['sparse']); } public function isTtl() { return array_key_exists('expireAfterSeconds', $this->info); } public function isUnique() { return ! empty($this->info['unique']); } public function offsetExists($key) { return array_key_exists($key, $this->info); } public function offsetGet($key) { return $this->info[$key]; } public function offsetSet($key, $value) { throw BadMethodCallException::classIsImmutable(__CLASS__); } public function offsetUnset($key) { throw BadMethodCallException::classIsImmutable(__CLASS__); } } exchangeArray($properties); return $document; } public function bsonSerialize() { return (object) $this->getArrayCopy(); } public function bsonUnserialize(array $data) { parent::__construct($data, ArrayObject::ARRAY_AS_PROPS); } } 'MongoDB\Model\BSONArray', 'document' => 'MongoDB\Model\BSONDocument', 'root' => 'MongoDB\Model\BSONDocument', ]; private $manager; private $uri; private $typeMap; public function __construct($uri = 'mongodb://localhost:27017', array $uriOptions = [], array $driverOptions = []) { $driverOptions += ['typeMap' => self::$defaultTypeMap]; if (isset($driverOptions['typeMap']) && ! is_array($driverOptions['typeMap'])) { throw InvalidArgumentException::invalidType('"typeMap" driver option', $driverOptions['typeMap'], 'array'); } $this->uri = (string) $uri; $this->typeMap = isset($driverOptions['typeMap']) ? $driverOptions['typeMap'] : null; unset($driverOptions['typeMap']); $this->manager = new Manager($uri, $uriOptions, $driverOptions); } public function __debugInfo() { return [ 'manager' => $this->manager, 'uri' => $this->uri, 'typeMap' => $this->typeMap, ]; } public function __get($databaseName) { return $this->selectDatabase($databaseName); } public function __toString() { return $this->uri; } public function dropDatabase($databaseName, array $options = []) { if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DropDatabase($databaseName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function listDatabases(array $options = []) { $operation = new ListDatabases($options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function selectCollection($databaseName, $collectionName, array $options = []) { $options += ['typeMap' => $this->typeMap]; return new Collection($this->manager, $databaseName, $collectionName, $options); } public function selectDatabase($databaseName, array $options = []) { $options += ['typeMap' => $this->typeMap]; return new Database($this->manager, $databaseName, $options); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->indexName = $indexName; $this->options = $options; } public function execute(Server $server) { $cmd = [ 'dropIndexes' => $this->collectionName, 'index' => $this->indexName, ]; $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } return current($cursor->toArray()); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->options = $options; } public function execute(Server $server) { return \MongoDB\server_supports_feature($server, self::$wireVersionForCommand) ? $this->executeCommand($server) : $this->executeLegacy($server); } private function executeCommand(Server $server) { $cmd = ['listIndexes' => $this->collectionName]; if (isset($this->options['maxTimeMS'])) { $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } try { $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); } catch (RuntimeException $e) { if ($e->getCode() === self::$errorCodeNamespaceNotFound || $e->getCode() === self::$errorCodeDatabaseNotFound) { return new IndexInfoIteratorIterator(new EmptyIterator); } throw $e; } $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); return new IndexInfoIteratorIterator($cursor); } private function executeLegacy(Server $server) { $filter = ['ns' => $this->databaseName . '.' . $this->collectionName]; $options = isset($this->options['maxTimeMS']) ? ['modifiers' => ['$maxTimeMS' => $this->options['maxTimeMS']]] : []; $cursor = $server->executeQuery($this->databaseName . '.system.indexes', new Query($filter, $options)); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); return new IndexInfoIteratorIterator($cursor); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->options = $options; } public function execute(Server $server) { try { $cursor = $server->executeCommand($this->databaseName, new Command(['drop' => $this->collectionName])); } catch (RuntimeException $e) { if ($e->getMessage() === self::$errorMessageNamespaceNotFound) { return (object) ['ok' => 0, 'errmsg' => self::$errorMessageNamespaceNotFound]; } throw $e; } if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } return current($cursor->toArray()); } } delete = new Delete($databaseName, $collectionName, $filter, 1, $options); } public function execute(Server $server) { return $this->delete->execute($server); } } false, 'remove' => false, 'upsert' => false, ]; if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } if (isset($options['fields']) && ! is_array($options['fields']) && ! is_object($options['fields'])) { throw InvalidArgumentException::invalidType('"fields" option', $options['fields'], 'array or object'); } if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); } if ( ! is_bool($options['new'])) { throw InvalidArgumentException::invalidType('"new" option', $options['new'], 'boolean'); } if (isset($options['query']) && ! is_array($options['query']) && ! is_object($options['query'])) { throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object'); } if ( ! is_bool($options['remove'])) { throw InvalidArgumentException::invalidType('"remove" option', $options['remove'], 'boolean'); } if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object'); } if (isset($options['update']) && ! is_array($options['update']) && ! is_object($options['update'])) { throw InvalidArgumentException::invalidType('"update" option', $options['update'], 'array or object'); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); } if ( ! is_bool($options['upsert'])) { throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); } if ( ! (isset($options['update']) xor $options['remove'])) { throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both'); } $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->options = $options; } public function execute(Server $server) { $cursor = $server->executeCommand($this->databaseName, $this->createCommand($server)); $result = current($cursor->toArray()); if ( ! isset($result->value)) { return null; } if ($this->options['upsert'] && ! $this->options['new'] && isset($result->lastErrorObject->updatedExisting) && ! $result->lastErrorObject->updatedExisting) { return null; } if ( ! is_object($result->value)) { throw new UnexpectedValueException('findAndModify command did not return a "value" document'); } return $result->value; } private function createCommand(Server $server) { $cmd = ['findAndModify' => $this->collectionName]; if ($this->options['remove']) { $cmd['remove'] = true; } else { $cmd['new'] = $this->options['new']; $cmd['upsert'] = $this->options['upsert']; } foreach (['fields', 'query', 'sort', 'update'] as $option) { if (isset($this->options[$option])) { $cmd[$option] = (object) $this->options[$option]; } } if (isset($this->options['maxTimeMS'])) { $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } if (isset($this->options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { $cmd['writeConcern'] = \MongoDB\write_concern_as_document($this->options['writeConcern']); } return new Command($cmd); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->filter = $filter; $this->options = $options; } public function execute(Server $server) { $readPreference = isset($this->options['readPreference']) ? $this->options['readPreference'] : null; $cursor = $server->executeCommand($this->databaseName, $this->createCommand($server), $readPreference); $result = current($cursor->toArray()); if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) { throw new UnexpectedValueException('count command did not return a numeric "n" value'); } return (integer) $result->n; } private function createCommand(Server $server) { $cmd = ['count' => $this->collectionName]; if ( ! empty($this->filter)) { $cmd['query'] = (object) $this->filter; } foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) { if (isset($this->options[$option])) { $cmd[$option] = $this->options[$option]; } } if (isset($this->options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']); } return new Command($cmd); } } self::RETURN_DOCUMENT_BEFORE, 'upsert' => false, ]; if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) { throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object'); } if ( ! is_integer($options['returnDocument'])) { throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer'); } if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER && $options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) { throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']); } if (isset($options['projection'])) { $options['fields'] = $options['projection']; } $options['new'] = $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER; unset($options['projection'], $options['returnDocument']); $this->findAndModify = new FindAndModify( $databaseName, $collectionName, ['query' => $filter, 'update' => $replacement] + $options ); } public function execute(Server $server) { return $this->findAndModify->execute($server); } } $index) { if ($i !== $expectedIndex) { throw new InvalidArgumentException(sprintf('$indexes is not a list (unexpected index: "%s")', $i)); } if ( ! is_array($index)) { throw InvalidArgumentException::invalidType(sprintf('$index[%d]', $i), $index, 'array'); } if ( ! isset($index['ns'])) { $index['ns'] = $databaseName . '.' . $collectionName; } $this->indexes[] = new IndexInput($index); $expectedIndex += 1; } $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; } public function execute(Server $server) { if (\MongoDB\server_supports_feature($server, self::$wireVersionForCommand)) { $this->executeCommand($server); } else { $this->executeLegacy($server); } return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes); } private function executeCommand(Server $server) { $command = new Command([ 'createIndexes' => $this->collectionName, 'indexes' => $this->indexes, ]); $server->executeCommand($this->databaseName, $command); } private function executeLegacy(Server $server) { $bulk = new Bulk(['ordered' => true]); foreach ($this->indexes as $index) { $bulk->insert($index); } $server->executeBulkWrite($this->databaseName . '.system.indexes', $bulk, new WriteConcern(1)); } } findAndModify = new FindAndModify( $databaseName, $collectionName, ['query' => $filter, 'remove' => true] + $options ); } public function execute(Server $server) { return $this->findAndModify->execute($server); } } find = new Find( $databaseName, $collectionName, $filter, ['limit' => 1] + $options ); $this->options = $options; } public function execute(Server $server) { $cursor = $this->find->execute($server); $document = current($cursor->toArray()); return ($document === false) ? null : $document; } } false, 'upsert' => false, ]; if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } if ( ! is_bool($options['multi'])) { throw InvalidArgumentException::invalidType('"multi" option', $options['multi'], 'boolean'); } if ($options['multi'] && ! \MongoDB\is_first_key_operator($update)) { throw new InvalidArgumentException('"multi" option cannot be true if $update is a replacement document'); } if ( ! is_bool($options['upsert'])) { throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); } $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->filter = $filter; $this->update = $update; $this->options = $options; } public function execute(Server $server) { $updateOptions = [ 'multi' => $this->options['multi'], 'upsert' => $this->options['upsert'], ]; $bulkOptions = []; if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { $bulkOptions['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } $bulk = new Bulk($bulkOptions); $bulk->update($this->filter, $this->update, $updateOptions); $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); return new UpdateResult($writeResult); } } options = $options; } public function execute(Server $server) { $cmd = ['listDatabases' => 1]; if (isset($this->options['maxTimeMS'])) { $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } $cursor = $server->executeCommand('admin', new Command($cmd)); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); $result = current($cursor->toArray()); if ( ! isset($result['databases']) || ! is_array($result['databases'])) { throw new UnexpectedValueException('listDatabases command did not return a "databases" array'); } return new DatabaseInfoLegacyIterator($result['databases']); } } databaseName = (string) $databaseName; $this->options = $options; } public function execute(Server $server) { return \MongoDB\server_supports_feature($server, self::$wireVersionForCommand) ? $this->executeCommand($server) : $this->executeLegacy($server); } private function executeCommand(Server $server) { $cmd = ['listCollections' => 1]; if ( ! empty($this->options['filter'])) { $cmd['filter'] = (object) $this->options['filter']; } if (isset($this->options['maxTimeMS'])) { $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); return new CollectionInfoCommandIterator($cursor); } private function executeLegacy(Server $server) { $filter = empty($this->options['filter']) ? [] : (array) $this->options['filter']; if (array_key_exists('name', $filter)) { if ( ! is_string($filter['name'])) { throw InvalidArgumentException::invalidType('filter name for MongoDB <3.0', $filter['name'], 'string'); } $filter['name'] = $this->databaseName . '.' . $filter['name']; } $options = isset($this->options['maxTimeMS']) ? ['modifiers' => ['$maxTimeMS' => $this->options['maxTimeMS']]] : []; $cursor = $server->executeQuery($this->databaseName . '.system.namespaces', new Query($filter, $options)); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); return new CollectionInfoLegacyIterator($cursor); } } $document) { if ($i !== $expectedIndex) { throw new InvalidArgumentException(sprintf('$documents is not a list (unexpected index: "%s")', $i)); } if ( ! is_array($document) && ! is_object($document)) { throw InvalidArgumentException::invalidType(sprintf('$documents[%d]', $i), $document, 'array or object'); } $expectedIndex += 1; } $options += ['ordered' => true]; if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } if ( ! is_bool($options['ordered'])) { throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean'); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); } $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->documents = $documents; $this->options = $options; } public function execute(Server $server) { $options = ['ordered' => $this->options['ordered']]; if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { $options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } $bulk = new Bulk($options); $insertedIds = []; foreach ($this->documents as $i => $document) { $insertedId = $bulk->insert($document); if ($insertedId !== null) { $insertedIds[$i] = $insertedId; } else { $insertedIds[$i] = \MongoDB\extract_id_from_inserted_document($document); } } $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); return new InsertManyResult($writeResult, $insertedIds); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->options = $options; } public function execute(Server $server) { $cursor = $server->executeCommand($this->databaseName, $this->createCommand()); if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } return current($cursor->toArray()); } private function createCommand() { $cmd = ['create' => $this->collectionName]; foreach (['autoIndexId', 'capped', 'flags', 'max', 'maxTimeMS', 'size', 'validationAction', 'validationLevel'] as $option) { if (isset($this->options[$option])) { $cmd[$option] = $this->options[$option]; } } if (isset($this->options['indexOptionDefaults'])) { $cmd['indexOptionDefaults'] = (object) $this->options['indexOptionDefaults']; } if (isset($this->options['storageEngine'])) { $cmd['storageEngine'] = (object) $this->options['storageEngine']; } if (isset($this->options['validator'])) { $cmd['validator'] = (object) $this->options['validator']; } return new Command($cmd); } } databaseName = (string) $databaseName; $this->options = $options; } public function execute(Server $server) { $cursor = $server->executeCommand($this->databaseName, new Command(['dropDatabase' => 1])); if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } return current($cursor->toArray()); } } self::RETURN_DOCUMENT_BEFORE, 'upsert' => false, ]; if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) { throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object'); } if ( ! is_integer($options['returnDocument'])) { throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer'); } if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER && $options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) { throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']); } if (isset($options['projection'])) { $options['fields'] = $options['projection']; } $options['new'] = $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER; unset($options['projection'], $options['returnDocument']); $this->findAndModify = new FindAndModify( $databaseName, $collectionName, ['query' => $filter, 'update' => $update] + $options ); } public function execute(Server $server) { return $this->findAndModify->execute($server); } } delete = new Delete($databaseName, $collectionName, $filter, 0, $options); } public function execute(Server $server) { return $this->delete->execute($server); } } $operation) { if ($i !== $expectedIndex) { throw new InvalidArgumentException(sprintf('$operations is not a list (unexpected index: "%s")', $i)); } if ( ! is_array($operation)) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array'); } if (count($operation) !== 1) { throw new InvalidArgumentException(sprintf('Expected one element in $operation[%d], actually: %d', $i, count($operation))); } $type = key($operation); $args = current($operation); if ( ! isset($args[0]) && ! array_key_exists(0, $args)) { throw new InvalidArgumentException(sprintf('Missing first argument for $operations[%d]["%s"]', $i, $type)); } if ( ! is_array($args[0]) && ! is_object($args[0])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][0]', $i, $type), $args[0], 'array or object'); } switch ($type) { case self::INSERT_ONE: break; case self::DELETE_MANY: case self::DELETE_ONE: $operations[$i][$type][1] = ['limit' => ($type === self::DELETE_ONE ? 1 : 0)]; break; case self::REPLACE_ONE: if ( ! isset($args[1]) && ! array_key_exists(1, $args)) { throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)); } if ( ! is_array($args[1]) && ! is_object($args[1])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object'); } if (\MongoDB\is_first_key_operator($args[1])) { throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is an update operator', $i, $type)); } if ( ! isset($args[2])) { $args[2] = []; } if ( ! is_array($args[2])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array'); } $args[2]['multi'] = false; $args[2] += ['upsert' => false]; if ( ! is_bool($args[2]['upsert'])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); } $operations[$i][$type][2] = $args[2]; break; case self::UPDATE_MANY: case self::UPDATE_ONE: if ( ! isset($args[1]) && ! array_key_exists(1, $args)) { throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)); } if ( ! is_array($args[1]) && ! is_object($args[1])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object'); } if ( ! \MongoDB\is_first_key_operator($args[1])) { throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is not an update operator', $i, $type)); } if ( ! isset($args[2])) { $args[2] = []; } if ( ! is_array($args[2])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array'); } $args[2]['multi'] = ($type === self::UPDATE_MANY); $args[2] += ['upsert' => false]; if ( ! is_bool($args[2]['upsert'])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); } $operations[$i][$type][2] = $args[2]; break; default: throw new InvalidArgumentException(sprintf('Unknown operation type "%s" in $operations[%d]', $type, $i)); } $expectedIndex += 1; } $options += ['ordered' => true]; if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } if ( ! is_bool($options['ordered'])) { throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean'); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); } $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->operations = $operations; $this->options = $options; } public function execute(Server $server) { $options = ['ordered' => $this->options['ordered']]; if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { $options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } $bulk = new Bulk($options); $insertedIds = []; foreach ($this->operations as $i => $operation) { $type = key($operation); $args = current($operation); switch ($type) { case self::DELETE_MANY: case self::DELETE_ONE: $bulk->delete($args[0], $args[1]); break; case self::INSERT_ONE: $insertedId = $bulk->insert($args[0]); if ($insertedId !== null) { $insertedIds[$i] = $insertedId; } else { $insertedIds[$i] = \MongoDB\extract_id_from_inserted_document($args[0]); } break; case self::REPLACE_ONE: case self::UPDATE_MANY: case self::UPDATE_ONE: $bulk->update($args[0], $args[1], $args[2]); } } $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); return new BulkWriteResult($writeResult, $insertedIds); } } update = new Update( $databaseName, $collectionName, $filter, $update, ['multi' => true] + $options ); } public function execute(Server $server) { return $this->update->execute($server); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->filter = $filter; $this->options = $options; } public function execute(Server $server) { $readPreference = isset($this->options['readPreference']) ? $this->options['readPreference'] : null; $cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, $this->createQuery(), $readPreference); if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } return $cursor; } private function createQuery() { $options = []; if ( ! empty($this->options['allowPartialResults'])) { $options['partial'] = true; } if (isset($this->options['cursorType'])) { if ($this->options['cursorType'] === self::TAILABLE) { $options['tailable'] = true; } if ($this->options['cursorType'] === self::TAILABLE_AWAIT) { $options['tailable'] = true; $options['awaitData'] = true; } } foreach (['batchSize', 'limit', 'skip', 'sort', 'noCursorTimeout', 'oplogReplay', 'projection', 'readConcern'] as $option) { if (isset($this->options[$option])) { $options[$option] = $this->options[$option]; } } $modifiers = empty($this->options['modifiers']) ? [] : (array) $this->options['modifiers']; if (isset($this->options['comment'])) { $modifiers['$comment'] = $this->options['comment']; } if (isset($this->options['maxTimeMS'])) { $modifiers['$maxTimeMS'] = $this->options['maxTimeMS']; } if ( ! empty($modifiers)) { $options['modifiers'] = $modifiers; } return new Query($this->filter, $options); } } $operation) { if ($i !== $expectedIndex) { throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i)); } if ( ! is_array($operation) && ! is_object($operation)) { throw InvalidArgumentException::invalidType(sprintf('$pipeline[%d]', $i), $operation, 'array or object'); } $expectedIndex += 1; } $options += [ 'allowDiskUse' => false, 'useCursor' => true, ]; if ( ! is_bool($options['allowDiskUse'])) { throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean'); } if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) { throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer'); } if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); } if ( ! is_bool($options['useCursor'])) { throw InvalidArgumentException::invalidType('"useCursor" option', $options['useCursor'], 'boolean'); } if (isset($options['batchSize']) && ! $options['useCursor']) { throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false'); } if (isset($options['typeMap']) && ! $options['useCursor']) { throw new InvalidArgumentException('"typeMap" option should not be used if "useCursor" is false'); } $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->pipeline = $pipeline; $this->options = $options; } public function execute(Server $server) { $isCursorSupported = \MongoDB\server_supports_feature($server, self::$wireVersionForCursor); $readPreference = isset($this->options['readPreference']) ? $this->options['readPreference'] : null; $command = $this->createCommand($server, $isCursorSupported); $cursor = $server->executeCommand($this->databaseName, $command, $readPreference); if ($isCursorSupported && $this->options['useCursor']) { if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } return $cursor; } $result = current($cursor->toArray()); if ( ! isset($result->result) || ! is_array($result->result)) { throw new UnexpectedValueException('aggregate command did not return a "result" array'); } return new ArrayIterator($result->result); } private function createCommand(Server $server, $isCursorSupported) { $cmd = [ 'aggregate' => $this->collectionName, 'pipeline' => $this->pipeline, ]; if ( ! $isCursorSupported) { return new Command($cmd); } $cmd['allowDiskUse'] = $this->options['allowDiskUse']; if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } if (isset($this->options['maxTimeMS'])) { $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } if (isset($this->options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']); } if ($this->options['useCursor']) { $cmd['cursor'] = isset($this->options["batchSize"]) ? ['batchSize' => $this->options["batchSize"]] : new stdClass; } return new Command($cmd); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->document = $document; $this->options = $options; } public function execute(Server $server) { $options = []; if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { $options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } $bulk = new Bulk($options); $insertedId = $bulk->insert($this->document); if ($insertedId === null) { $insertedId = \MongoDB\extract_id_from_inserted_document($this->document); } $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); return new InsertOneResult($writeResult, $insertedId); } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->filter = $filter; $this->limit = $limit; $this->options = $options; } public function execute(Server $server) { $bulk = new Bulk(); $bulk->delete($this->filter, ['limit' => $this->limit]); $writeConcern = isset($this->options['writeConcern']) ? $this->options['writeConcern'] : null; $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $writeConcern); return new DeleteResult($writeResult); } } update = new Update( $databaseName, $collectionName, $filter, $update, ['multi' => false] + $options ); } public function execute(Server $server) { return $this->update->execute($server); } } databaseName = (string) $databaseName; $this->command = ($command instanceof Command) ? $command : new Command($command); $this->options = $options; } public function execute(Server $server) { $readPreference = isset($this->options['readPreference']) ? $this->options['readPreference'] : null; $cursor = $server->executeCommand($this->databaseName, $this->command, $readPreference); if (isset($this->options['typeMap'])) { $cursor->setTypeMap($this->options['typeMap']); } return $cursor; } } databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->fieldName = (string) $fieldName; $this->filter = $filter; $this->options = $options; } public function execute(Server $server) { $readPreference = isset($this->options['readPreference']) ? $this->options['readPreference'] : null; $cursor = $server->executeCommand($this->databaseName, $this->createCommand($server), $readPreference); $result = current($cursor->toArray()); if ( ! isset($result->values) || ! is_array($result->values)) { throw new UnexpectedValueException('distinct command did not return a "values" array'); } return $result->values; } private function createCommand(Server $server) { $cmd = [ 'distinct' => $this->collectionName, 'key' => $this->fieldName, ]; if ( ! empty($this->filter)) { $cmd['query'] = (object) $this->filter; } if (isset($this->options['maxTimeMS'])) { $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } if (isset($this->options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $cmd['readConcern'] = \MongoDB\read_concern_as_document($this->options['readConcern']); } return new Command($cmd); } } update = new Update( $databaseName, $collectionName, $filter, $replacement, ['multi' => false] + $options ); } public function execute(Server $server) { return $this->update->execute($server); } } 'MongoDB\Model\BSONArray', 'document' => 'MongoDB\Model\BSONDocument', 'root' => 'MongoDB\Model\BSONDocument', ]; private static $wireVersionForFindAndModifyWriteConcern = 4; private static $wireVersionForReadConcern = 4; private $collectionName; private $databaseName; private $manager; private $readConcern; private $readPreference; private $typeMap; private $writeConcern; public function __construct(Manager $manager, $databaseName, $collectionName, array $options = []) { if (strlen($databaseName) < 1) { throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName); } if (strlen($collectionName) < 1) { throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName); } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); } $this->manager = $manager; $this->databaseName = (string) $databaseName; $this->collectionName = (string) $collectionName; $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern(); $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference(); $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap; $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern(); } public function __debugInfo() { return [ 'collectionName' => $this->collectionName, 'databaseName' => $this->databaseName, 'manager' => $this->manager, 'readConcern' => $this->readConcern, 'readPreference' => $this->readPreference, 'typeMap' => $this->typeMap, 'writeConcern' => $this->writeConcern, ]; } public function __toString() { return $this->databaseName . '.' . $this->collectionName; } public function aggregate(array $pipeline, array $options = []) { $hasOutStage = \MongoDB\is_last_pipeline_operator_out($pipeline); if ( ! isset($options['readPreference'])) { $options['readPreference'] = $this->readPreference; } if ($hasOutStage) { $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY); } $server = $this->manager->selectServer($options['readPreference']); if ( ! isset($options['readConcern']) && ! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $options['readConcern'] = $this->readConcern; } if ( ! isset($options['typeMap']) && ( ! isset($options['useCursor']) || $options['useCursor'])) { $options['typeMap'] = $this->typeMap; } $operation = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options); return $operation->execute($server); } public function bulkWrite(array $operations, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function count($filter = [], array $options = []) { if ( ! isset($options['readPreference'])) { $options['readPreference'] = $this->readPreference; } $server = $this->manager->selectServer($options['readPreference']); if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $options['readConcern'] = $this->readConcern; } $operation = new Count($this->databaseName, $this->collectionName, $filter, $options); return $operation->execute($server); } public function createIndex($key, array $options = []) { return current($this->createIndexes([['key' => $key] + $options])); } public function createIndexes(array $indexes) { $operation = new CreateIndexes($this->databaseName, $this->collectionName, $indexes); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function deleteMany($filter, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function deleteOne($filter, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function distinct($fieldName, $filter = [], array $options = []) { if ( ! isset($options['readPreference'])) { $options['readPreference'] = $this->readPreference; } $server = $this->manager->selectServer($options['readPreference']); if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $options['readConcern'] = $this->readConcern; } $operation = new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options); return $operation->execute($server); } public function drop(array $options = []) { if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DropCollection($this->databaseName, $this->collectionName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function dropIndex($indexName, array $options = []) { $indexName = (string) $indexName; if ($indexName === '*') { throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes'); } if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function dropIndexes(array $options = []) { if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function find($filter = [], array $options = []) { if ( ! isset($options['readPreference'])) { $options['readPreference'] = $this->readPreference; } $server = $this->manager->selectServer($options['readPreference']); if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $options['readConcern'] = $this->readConcern; } if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new Find($this->databaseName, $this->collectionName, $filter, $options); return $operation->execute($server); } public function findOne($filter = [], array $options = []) { if ( ! isset($options['readPreference'])) { $options['readPreference'] = $this->readPreference; } $server = $this->manager->selectServer($options['readPreference']); if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { $options['readConcern'] = $this->readConcern; } if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options); return $operation->execute($server); } public function findOneAndDelete($filter, array $options = []) { $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { $options['writeConcern'] = $this->writeConcern; } $operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options); return $operation->execute($server); } public function findOneAndReplace($filter, $replacement, array $options = []) { $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { $options['writeConcern'] = $this->writeConcern; } $operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options); return $operation->execute($server); } public function findOneAndUpdate($filter, $update, array $options = []) { $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { $options['writeConcern'] = $this->writeConcern; } $operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options); return $operation->execute($server); } public function getCollectionName() { return $this->collectionName; } public function getDatabaseName() { return $this->databaseName; } public function getNamespace() { return $this->databaseName . '.' . $this->collectionName; } public function insertMany(array $documents, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function insertOne($document, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function listIndexes(array $options = []) { $operation = new ListIndexes($this->databaseName, $this->collectionName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function replaceOne($filter, $replacement, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function updateMany($filter, $update, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function updateOne($filter, $update, array $options = []) { if ( ! isset($options['writeConcern'])) { $options['writeConcern'] = $this->writeConcern; } $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function withOptions(array $options = []) { $options += [ 'readConcern' => $this->readConcern, 'readPreference' => $this->readPreference, 'typeMap' => $this->typeMap, 'writeConcern' => $this->writeConcern, ]; return new Collection($this->manager, $this->databaseName, $this->collectionName, $options); } } writeResult = $writeResult; $this->insertedIds = $insertedIds; $this->isAcknowledged = $writeResult->isAcknowledged(); } public function getInsertedCount() { if ($this->isAcknowledged) { return $this->writeResult->getInsertedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getInsertedIds() { return $this->insertedIds; } public function isAcknowledged() { return $this->writeResult->isAcknowledged(); } } bsonSerialize()); } return is_array($document) ? $document['_id'] : $document->_id; } function generate_index_name($document) { if (is_object($document)) { $document = get_object_vars($document); } if ( ! is_array($document)) { throw InvalidArgumentException::invalidType('$document', $document, 'array or object'); } $name = ''; foreach ($document as $field => $type) { $name .= ($name != '' ? '_' : '') . $field . '_' . $type; } return $name; } function is_first_key_operator($document) { if (is_object($document)) { $document = get_object_vars($document); } if ( ! is_array($document)) { throw InvalidArgumentException::invalidType('$document', $document, 'array or object'); } $firstKey = (string) key($document); return (isset($firstKey[0]) && $firstKey[0] == '$'); } function is_last_pipeline_operator_out(array $pipeline) { $lastOp = end($pipeline); if ($lastOp === false) { return false; } $lastOp = (array) $lastOp; return key($lastOp) === '$out'; } function read_concern_as_document(ReadConcern $readConcern) { $document = []; if ($readConcern->getLevel() !== null) { $document['level'] = $readConcern->getLevel(); } return (object) $document; } function server_supports_feature(Server $server, $feature) { $info = $server->getInfo(); $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0; $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0; return ($minWireVersion <= $feature && $maxWireVersion >= $feature); } function write_concern_as_document(WriteConcern $writeConcern) { $document = []; if ($writeConcern->getW() !== null) { $document['w'] = $writeConcern->getW(); } if ($writeConcern->getJournal() !== null) { $document['j'] = $writeConcern->getJournal(); } if ($writeConcern->getWtimeout() !== 0) { $document['wtimeout'] = $writeConcern->getWtimeout(); } return (object) $document; } 'MongoDB\Model\BSONArray', 'document' => 'MongoDB\Model\BSONDocument', 'root' => 'MongoDB\Model\BSONDocument', ]; private $databaseName; private $manager; private $readConcern; private $readPreference; private $typeMap; private $writeConcern; public function __construct(Manager $manager, $databaseName, array $options = []) { if (strlen($databaseName) < 1) { throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName); } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); } $this->manager = $manager; $this->databaseName = (string) $databaseName; $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern(); $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference(); $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap; $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern(); } public function __debugInfo() { return [ 'databaseName' => $this->databaseName, 'manager' => $this->manager, 'readConcern' => $this->readConcern, 'readPreference' => $this->readPreference, 'typeMap' => $this->typeMap, 'writeConcern' => $this->writeConcern, ]; } public function __get($collectionName) { return $this->selectCollection($collectionName); } public function __toString() { return $this->databaseName; } public function command($command, array $options = []) { if ( ! isset($options['readPreference'])) { $options['readPreference'] = $this->readPreference; } if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DatabaseCommand($this->databaseName, $command, $options); $server = $this->manager->selectServer($options['readPreference']); return $operation->execute($server); } public function createCollection($collectionName, array $options = []) { if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new CreateCollection($this->databaseName, $collectionName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function drop(array $options = []) { if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DropDatabase($this->databaseName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function dropCollection($collectionName, array $options = []) { if ( ! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DropCollection($this->databaseName, $collectionName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function getDatabaseName() { return $this->databaseName; } public function listCollections(array $options = []) { $operation = new ListCollections($this->databaseName, $options); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); return $operation->execute($server); } public function selectCollection($collectionName, array $options = []) { $options += [ 'readConcern' => $this->readConcern, 'readPreference' => $this->readPreference, 'typeMap' => $this->typeMap, 'writeConcern' => $this->writeConcern, ]; return new Collection($this->manager, $this->databaseName, $collectionName, $options); } public function withOptions(array $options = []) { $options += [ 'readConcern' => $this->readConcern, 'readPreference' => $this->readPreference, 'typeMap' => $this->typeMap, 'writeConcern' => $this->writeConcern, ]; return new Database($this->manager, $this->databaseName, $options); } } writeResult = $writeResult; $this->isAcknowledged = $writeResult->isAcknowledged(); } public function getMatchedCount() { if ($this->isAcknowledged) { return $this->writeResult->getMatchedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getModifiedCount() { if ($this->isAcknowledged) { return $this->writeResult->getModifiedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getUpsertedCount() { if ($this->isAcknowledged) { return $this->writeResult->getUpsertedCount(); } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function getUpsertedId() { if ($this->isAcknowledged) { foreach ($this->writeResult->getUpsertedIds() as $id) { return $id; } return null; } throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); } public function isAcknowledged() { return $this->isAcknowledged; } } $(document).ready(function() { $('pre code').parent().addClass('prettyprint well'); prettyPrint(); }); PNG  IHDR=bKGDC pHYs  tIME 8V%tEXtCommentCreated with GIMPW IDATxwt>%$л4 "\zA"z, "EAI 5SɍF!V̜93ٙcED04pGUE.*O 3ZWQDDD$GuEOۛ5F#8)C( =$DE[9S*(, qP:kB"*_?֜#o²EPX='{ӪztM_b²3 +#>=IUC; #qfbȟxD䏃1UpwRP3vkoǦj(,._ fsH&[3d$;"DDDDEDrPNLo_Ͽ/eQ-2DDDD\*\ kE܎x3p-BOp|{>Dr\3G*Aejw;Ărg߻#ztm[>-O3x ֺ Hh7&=^쀵p#kCs|CSʬcѿsx@i/!V螉T%.kMegCIOjewH|ы8~W8\V\tfYD<^E)Zec+f$mCJoS` U|zl .8س41ηjwEAֿ.i)U(Zs4 c}N$lEh+zmm93FnuY_ 鎧l'(Z}" X5m~>?ExRhJ5/#MAְi_zE6]I,ȕETTՀe~O~JG _fkzFpEؖ\Ez5<w8-e̾Tk[T#̗D|KYd{tl;׾|׻Q4~x;M㪖6L¶9(Z{uF"""" ""W"Wo쾏bp + rx}YN(Od'Aa>՜O̊֬RFw=rНTj}36%4u' ͚K (MdT;×Qgll7'HKPYAvC:"ʥp"=6e2Rwp' {i TJ)&0 _Fy0iCw>>@ؿWJ8}%) 73L4OΗ5@:["zr8'Ξ$Foƙ/#\²\Z`~ڐ{rr4}UԺ}8uڇ+ϧ}rz8 ^kneYd$ףtG;86P g} x<*ZZBlI:b@q(+8DgkqZ]df}q/ͺk(1~`tZ<bnn-ۋ+8l¨dMuf;}q8f説a6IHf趬}vwJ7>H;]4}Wt gf<ȆY(Π#[qO76Z`3)5ؽPcn1bVLY9 86Y+QtoIvCy]g<:mE9Kxn7 Bz1wI]CTϩ=КLpFslmhK:#(\%$>zoefvC H8|5g[5'qhftxb撖X; :F{>Nמ/ʞ8}qΠ_h|im`ꦵ@DDDDtfYDDHkI+o_gq}4LA=$?+;`٦Yr>d^&zw|؟cߪxRDGi3__j1i H9zs+)}d$Wڱ6dg~_Sa<8݋1hYMWi}= 9pQZJִNwȮ^EDDDpZ*ȹ{^H?(>&QEiυ~}YxsZ68:,"rEUHP)m~e :-}!""","{4 3[_=%{P':2u%"+8\s[V J """""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","y1ȈR!$OK;ጳzT QXOB*iU!DDDDaYDr18} !yZb`6""","9]EUӎmg !""" "#_I.N|L!yVɪg*(,HO'{'3km!""" "c3I<Β\jH>P2&""","9}޻8]UCoVa[!"""֪ "ra +hԏU ɵ>6^OKCDDDE4Π{a"-x5~?MPeZW#'h]Ϭ #""" "rqc gq&NU`1GjWX?ڈ'm0Z/oDDDDaYDrExji€΄5)4ADDDrK%Zd1&֞R5DDDDr-"""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""","""""",""""""" """"""" """"""" """"""" """"""" """"""" """"""" """"""" """"""" """"""" """""""K%1W@1v#""" ׬?fgꈈ\z [Dx Hk,"""" ""Wπ|(,\q߼tZSQXM \!"""{/K*HGG\Z+ """",\֣JgYD҅ýk1*""""tfYD䢇PqCJW!#ɋ/cXfO"""","r4AA}TI"5$f`i盤j(,\ލ15pVx>xG]$}ZVQXZ9y2 7['9!u&""","ry4ƴGon҂4#qdP\ΧlF{²H^ ɥp'(*'N&Dߞw "d|aڝ²HiTGp-hȏ9:ިĎCzҟTEDDDEDrwcjM8\,.S?G?rjwOa²Hn 8pDsɔkv|P~d&Ǵ6DDDDED.}r:5h̪K2'f`k}WVDDDDED.MiLmkmیrL}Nވ֮ZQXx:z窙9(Ɠ ~Ct1e42EjI _]8)xWkODDDDaYD$CrYk &鵉晙ǽ3ZMkSDDDDaYD7NWg* 'bôll0>jZQXk 11q,NSg] 5wpv{Z;WkYDDDDaYDBCrD pCpjO f!d$3|Dk]DDDDaYD9(}$n}~o7Nk_DDDDaYD${#hLg&oیwwNh>d]ADDDDaYDtRZwN"*^'H泙Se(,ȕGi"UX8&9²²\9!B3Os̉8\jI>Q)[UQX;s$Tn5nSEb㛟"CoĶ{ZG""","B8JQ(Q7QUNH9>dEEel!8(d͟^CVɮϬ """" ""q2/oPEt6-UADDDDaYDχd7˸]E~rط:UcMyZ²ȅ4HAA=TI@^y: {Se5DTGhCt5U I$*(,r?ΦTo;Fv"Wf!xҶXDDDDlX6 HeZ*\f1GJ/U+o桙f\vzobXk*caSh3I/Ep-q6|*L=p.3h2Yȕ!9s240mbU`7Fl2S2ͬr~y'1UUCrc[tEυen9\[Ki$h}gU\ԸR&rrUDn̾Ճhܴ;/gNU5$8f֝[!"=ui.wO;7*TOXtMUDr'zx!>C*%+ y>_ W5$YlccT 9GpUPƃ`qhc(cB1!"qzY~!yV{~,c#Ϙk<1q]5 Ѡ_zόvo2٦Wf0gp\NWX}LmO]Ell;(YƱ2ܟcR11Nc]y,7l$*Ti.wBD8,G!yZD@jc*yKW'B緻n:OPX WK (]$Xσ:Rn{q1#垗S_ڝ1}^8^dZAT91ƅqVגEvO5dYbS·qbI49/+S?ʛ$I]YZ5h2^ow"Ied/]1ƘڃƘ2`R^](u*y[QƘhse(NAB~X.|LH`0Y˘MZ`1&k+,~w S/-9 .3D;`gN_FiS/66L%_AOnoǓz5ƤVh)@Ĭ,W? w~ WA>C?D4`jMTe%d1ft@zbcI%_;DŽHmE Gɺopy~=5':{~Cگ"Yk}Ƙ>FuM7$~ss/P8 `3{4D@xZ_AgpQB nHE, nBtnn7tA{ 0 Vc^&!9̸$84}Q:MخH&(l#iu^v j^KkȞEcv8E'T>*ig\zyғPhy,+_{yw +q]y[ְGzR GB'yp cL 03Z{/Yk?| gѼjEDDO^/Px<| mMf7Gcj{ aoWdPb:-o2F[T' .Kig\= 2|91+ q?=oe_ ɘF}VdKV(yE/uu^D-kƘh?o_?3Ƹ@Ӭ/O1cGƘ@Qk#z.әe ;`<1\I^,y@<І6z_7 .'%'Sn"dHpHof}2XRoO-9e4o?184wkЏ|ip/ IDAT8;:s[t}}~i}}[k3XsXcM͠BvFw{cQX:76Lo3i7/cͫy|1+).ކ$g$u{W61۽{NNZ56o6i3[:Jˑ,2 ˍ1c@]냲(.Tu__XH>xg/^1ocAPˋ{Z{IjR(AX]\KFbdUE:#QX߃Ƙ)%pk6$ O 4uVœ8Ou;x.XJC],yz6 dwcDo#X=q'ȉ0cD{{,wϹ{*u Y`N(ALj*76޹"w{3q8wa<1(B= <I 8َdp#?F}C>؝e/.gWO]SӶ#OuX "+T>}ܫrH~Je~z=-U#pWLO*r:? !-х/AkQ!)wR?3_¬~ X˝?k7w=Mdu? *i'$w C)pn~;noǓz5ƤVh)/.Ewd׸u|7:?}~ָAa5?Ğ=p*=o5n cIkC@`v2 )f*wC&C::w?}1 Q3N ^1m(t۔A!ٿz<ߎ5pm'8J~w>s,:Eݾ{ sON,77kDZhdli|=r4GPTh>B_#=[f {gWA \N;R{6 )XnM^#9B)#D3{1~x@w UDR\'>1;wMjۅ#jEa\)!٘z8LUih}ݿqz֮Ra. D:ޗ:kߌ3 ]} WwYL Z2.zߒY;W(ZЂǨ}a􁾄C{Z>Ɖ9o&Uۜ壯œRҍ} d$oKslk}`+t`tW5P>TKa7ؘuF 5˳Gӎ Oh=ῷ!cVh:gC"4过3piO0k(Y%Z|Jۅeޢ2Z_CG;ƩyAj14@ݻmw[ \;(,He{,ᵩ}u{蹪""{ 6U9~?w[25 OJu"JLYgÎ/15wz.krj_4 |~z9?g:`)TeIt5YʼnݏgI9yקb>\ZP6^p?1? 6?Vr|[Sv+hQR={W>P'?ݡZֿkʷ*8~ wdJ (,#$|aDDaYD~H6+15&᪈*ʭbț8'r=gwTHh#$-~P T_C>;09қ{~oF4+˖Y#Hnqw\ o'1D}(yӝM||8;i/| 7 ×QL]@~2֯IVDqJMĘ |r5-#C;(,HN 8Š掙 U+|cq{`ì[UVO]My&ᘑFﯺsxx@p(95<@"N~9*Q,W;Ai_Ýq$Q;g2悦qFy}UԺ}8uڇ+ϧ}rzݿ^p \E$72:{M#nȅes$\z|h_>Bd*H*AxIKzOŗ^r;2 q"-* ݖS~v/፵سRDo`tokyT@uX>`eMuYYvdsM^Hv}طDžee7b&d?Wc~6؍5U7E~{q>'9Q %[*i @ʛHng?ߗ{t-TDX/wƛ\.Lqftz_z zϿkctˍï?+_us7C1`\(tt2H {.zν[ i+tP0im_09œSP]mxZ^d=~HlLH"MQ$| 3:Xy؟zIuRqL#pG<$ljʙ#{YУ;1Q7zq1 WE8>3]s~n~[fvOŠ/r?)N[.;X4nL̒7{I8PZxQ."5vpG?{ǟ.i!oZduWԾ0g/}N7Hߟ4Gm"]gO<'o#:RZ[!qKHn=S>SUi+%ݤd\cԆUR V/(bX_8j>CWj*40{C?ÙHk?Aa>0p&o8vΟ'7 $l33(Tq<'obwGoqĽD=wt? +aGS  u}=Aa?xG6&(G19bg':O{۠yOjyvΟOuv^7BWF҉ٿz Sˍgߺe!Ef=zBљgfH)GZ?2[?g, YkO^6;9Sdn~;5iӕ|fBJ9۪ٳh0~,tٴ-֞:pWq)^i}'U w%nWJ#şQ8+L^J5oX+vƛQ#oi<%pnpYgebma[lcMegCIOjQ~X> oXLTW&5&+ MMdՄ$EtbV1,|>b[b}Q2z-U}\E雰MPu4тEOO͓џRԹ!fXAkSOwpw遶{s ٳu32h$jҧ__H[)Y0WTcL^U@E`1f11&D+[DDuQ14׼}:pdc`')ՀNKx9t̩i=fRM M} 7p'Рs_^1Z{8 G&Ien#wo_ˬs4/?w;c!}3!$5Ζ[@k8up=ˆp{X7 ZǏc0m T#_ BwekԼ ou:r̺w$%9K tR(ڌ953mt{|o36^CG^>~<'2w1ݴJ:/jߊ/<̺Zcעͬe8?*IRl y+k|1(:4C͝>u˼ї#c8lb?Nmu/gQ4U˚8tyu>eϴد/>_;(NٛNQ@v1Kn 2 )B 71o QGK8{Nz'g}Dx7[l0,v\U{4j@`sg P/) IG+%4>4w/)?4gd{TkXN;So2@^B;HKPYAvCxw_ߑ= ۝53 7'c~YmDZmM9h٧;.N!߷ h ]`_Չ$nu^) %y2m oc'oU6z?7L6<)5(PfJLG* <d_;r.N:حIxRQb:Î/1Òsm ضp:W/ytIn˲, -"" 9!XFşBF}.$Pv//WٝP;Eut87|{sdYGyG|`9 d7?@kj`1& T ~k<~iϏiڇj':5W Eٿ(Q7c2es|{`p˙/K/#ϳE3Gz3}ӌ n%g̹q;o~36U.'W²(,_Kq`0?Lm=9QLLP}`PK2G"ΰ Tu9S^O:zoZH) & 8\x=g]~\88޲1$j?w[Z| |l)t;vP8ݏNio>9[Or-_p<gc>O$mOYeAkpS|2`dOG(#IP3m߻5T`"JΠl}GGU.\) Z轉# 6@Hiby{RT(U(U^$В3_4J Z̙r̙3;)_(Aot6Y\4{y$s=g̘h ӛ,""ߗjVLl>Ei4w-UZ+UZ²<$ǔ{3y<6~[֟ +?DNB3u.$aEd2?vV\p|Cb#In_`z)[hZ_PU9>y '.q pTP_ )Iy2{o9/|}X\-cIcƘgnx53:?GIDxɟ y J;#{`[`nC- 3~d1fqGțoKgܾW=$sԸ[3-sh3Z2}luǝxo4/ƼeyeGInr8'3|D z=PAKN%> 떇છI)FԁXs|7EHyO3FvnK}֚e%-齭H>%+]oN|8w6Nt4M:)KGpzw70@ǦfD;~%erzg?~h2UOf9П-lCݱlWzW3.H#j@ IDAT>¹5 3vI]ٙUzPrw8~,?Ai;j+nV~S]~N ͞c}"w6Ÿ OU4yi'B/aE'\QD‹I~e8m:gwUGz8sF {NLaX])[bsNm)k`n'b#t~:U:MeӲ5o%4mx(Ū'^:rw?KV_cy1Gqdsc[D`K$ED.uw;,>!by_ƦLE*#I:sa?#r\G㗞ǃD2 g bW<͟&8_!l0ɕ)UEڌ}Z~w>+y뿓8Fݮq78m,{ =ԛ{@Q=iNl{_Ǟ(w xb{%y%## ]S[B|Ll=-*+ 7}{y &y m'oe<#T/gv$'QD:}B4-_K*IĆa#4Nl{b7p8~ﯥRA<17͞@ge=֕|F~(Ŕ [xTp%U%wJ`E?9m'0p/ (W-^:Ϸԛ!"דTwm[}_qꕫeY5;FP<.ӾC$ۋs 1l3ThmfO2+{q7@#iD}v_ޓ>Z{e?h?+/~]Vtv6OHK|#/"rqE rs1ZZތoe凿(+|m+l07:_S~mPcM匢HI=;мf#[bpQ:d 9Y<ձ9([Jܞ5ß$)~|Rɴ/{Xc|q Lhn)17[O2Sxx?[Y?};~wS1 oqĆ?/` x7i$ ̙]`sFSlڏqz68p#[!*7/mevlj ^P)wJlθ>s p|L9\)p,Ö)(swZ(Dz)4{\1r SڽoӦ"*ax]J6# niomk? J qΚauHNupTl<g7`m\ƲRt~12>ɒWǒp! UNٻ/>NxO{ēfcKIjCPq |H֍yYmAZ- &-:뿘F|=zxUZǕT_>qz?IW(%bC^CMICNnX6~J|;z/@޶;!}fqSO2?T?rA8qj4$> ˖JRT^4,r3fcRe˺#3g5kK2'bujYdN}J"^+Ot[x%ݸ%/BNV{F93}"> rmaOlT5| R+gux4>h;V*7&k!1:-oǝ\ p%ma#e9rx)Q;8©mH}{{Mb8's> C`|bOr,έmҏ`/dlbkBSjl6-2#wr]JOΘGOVf=͖u4#OZ-"rhdY$w㾃c'r"?TD.bL|Kb]CETIoו;{"5ޑ/j XX_E{+,Wc(+v0@v|_к^]q_^iw|Q>q&(؝OXP?E-4^^?~NcJpP^M;އ?m ]].FdTD&eTeҌP$7`4V 7l욋7PQ "_p\V3g/Hzyu%ufO OfOȕ}`3WBvޞ_/Dzg6QxVڋݒӻ FmAwTɱN"ԇ^DTE${ 1eaWF Zoy3@B9͙eKfJЋ^ow]ts_;WⅧ8L<|4ϛW=ӹ> >_RI y L?fɐWqEjDfY%j'hv(M9$[&DvYs CZQ"4/4^-r2l{M3s tfۉϕo_M˹W¸g7&&<3;ˮJ]?z5֦ONQm>b)|Daa]] ߊ;[uy܈+.mΟq݊e\*+>#}heKF(F[a<eK\:cqUi S>Ӽ{<6~[֟K_Q.ҢoCc4a7K+%o}"~}O^BD,N{ò5k8ᫎ*T+tǟr$ǴbӘƸSzSGK_cF26DcKTC‰:x=C1[3"sZtf#;])qa ,}sGk4ySw; >uodop''@L,7zgNp.k#>ЌfWNR.vm=_x%)v(9OMb\r*O]<WLrL×~~dQ_>~7h6|F70A%s6:iR~i 0si\)InSR ^\- ","ׯ0hm9g|9c4>Zdn2+_YMcg뻉 }q36%:u/۾Q&|@`Cqp|Ϳ# ]}I '僁Ą"pAl~)Xa,m>M$ws>6Y_~Jh擬9Op`N0J}6#{Y`b=řyq9@ՖpǓj2d%㜋/' US;޻o,+x)X)&5nz.lߗq"9>}~SnòR({^oGDcϐ}?>wq,[ ~[u8u`Be,?ʤm1NoA}Kis }3 pAYգ?#{}{P(>Z1+T{L>k j@Ν79NKU|O8gR YiH6OHK|Iaȟie2 >`èݧGTj %뿆8{_gͰ:eu7񦔧Q`J,$1 @ɺyfDHcG:I:۔%GV|ЀK²P(^c$IX;?! P7Tj:"UKZFΆ=?={R鳸k囗4R!G>-}>;M(b0y(VcU{3K1髢,"""Ba_ ]o0ZLb/s(Rx%ݸ%/BNT4}=c_]6H9S}/m!ӜH߯u`ATnccgW8}3_d3Yns+.4/>w~JA~<Ff(ʹ=3KKi#_ cցyğcas,ɩp3&x|3^Pﺑ59d>J _ı9}w% Rc t1ƄkI+)㔢bV Zce=!c)zr &dv^ IJlnj*4]ΩY3z$%1+ P"d/u{\EZ#NlUTy-+-,κhO//<˿>C[qz3sƘ^+V ?rQvDFA_8\)xg 3Zږb|IGs˿Fs9rf~Ԏg)Pn ;6Ln5eLwGP}-:X&^AR _:"ڏ@a8}IΆd{Q{ls@7}Jyvq0>Oy-ךϐ[H9HIU"We,6+O-^ErwBƄ)l(^;e[wGgq3:˖#θ <ie ;gja'ʔ v.Vs?'v8 |IRpjKg<;'`aY).sLI9WXV ,~KnØ7{{9aX#=@KecBrm+`^cFEyzu_X 4{ҦcQ)m?b9ⅷ6R8wN')Vm:E}'"6>jVeKAdzﯥRA<17͞@.:R PÔ&X4o 353m9syzذ x!EG= vRs ޅ.Ĕop׺< 5VKbWJQ\IvE!9։>",cҏi`8˦)qw :~X#(Pn &LR"=ՅV:h$ǘfiIc6(Ų`'o|(5HP*r#o`c?WBj8L⧕䜢<-Q*K.V YGnx '_&.¸aLAshJD.قA1fAJ9.]ӉdC[uIK|Iaee| > ã3P"r#)Mw)۝Cq!Ǔp >SHBrl ҒI8 c6QوU?-T1d/["υ} 04wfNxpJD8&(|[@#gy1BJ2|Tf߬0+%8 D䚔eɲƘ%JCrͯ&?%;DrKxR"cXx3,+@AdZ>aw ]td]= jY@DTEDn$:7bFe)c '2 }Rɭ4-'ph(rYXisMzLk *Ll?3:?ᥭ{B6þ[VY` FdidDvi1"~+m jY@D{Pa\gY>YUI5cru&&4~JeBe&I̡筢,"""9Fuu3ƼtD.i1S 1>""""Ȳ`9mwcu}wPDDDi3lK)ƸAeźQ#7&L^䜧;qHD IDAT|Ƙ%zDDDDFED.4o0>]ą1dV}຿_TuTQzY,[IĶf<(LX13zcBΈ,\lL4ݲV,2Ufy䋟oy3һ!"""՗柌]Pä_[ٓQm>),"""rmhdYD$J;˲fp`Dط ՗zc)qkG#""YY9d c*Ƙh#"*"""]%َ3x4ڏ/`BJ\,ː 5 $)EayOiW&lq# |mmO3Ć`u*u*"r4,"""7}/||s%j"࿙dWV~TS;> 4HZIqz?IW(%bC^C2=OfxҊRGe>!wf!_Buk# niomk? J qΚauHNupTl<g7`e&W`,(sǫ8b3SQ4\))^{e'nd ϯ>t~6ٓJMŝX-߼ PWTo۝>HVȲ@קKX]ΆÓgܴxg+sğэA=ڇtG'S9shN6X8hTj|6wL?G'_UowSZiLS*#tױ%/BNV{:mF.:Lm_\~[- Hp3D:E{m4U[QzJYdq ~6Bb(vq01՟_yxX, Ɵ"ՖWU(_6g|xl8Ą{UTf5^W,,~L_ƌ ⽕X+rj(˦r܉IZr߫2 kQ7:M|Y˖~2'\΀qE#"""DzR.~J%wrL(r@rt~=WJNlι=8w |BqL\ 8nS]iE3X#>“Vk.zߘCE -/s9 6gWx saKqx 6{B>W/ѵ\9̻ee~l4#"*"""r;cpm OP̟/rRt1 Fm\NA̡Y?z~źd+W9Ԉ.9؜ XDj{WizqxҊ\pS5OӤ%JJ)؝ q% fL5ٽ=`l*mEDZH/wr "clqy{e|6vz4Y3w<˦ݵTilTi S;mY+?a4RV/<^Wo9/_Wp#89u+>4A[q vV~(cW}#K- wrLϟrUt/,-\"4,"""玞q?wrcD3t$GIHoDzGD.Zcdf(^c!^S;{bQ5H`sɩGN`뤦,}gE0Tn"\N6/Npjw[T} =Eo9~m_J;_Zu}D<ڏr ҋ#GQHGψߟ| *|4|B94ˊ]t rDsd kj_R~^d E&&GW"d; VKnmIG`ss|D|5ȴrHciN|ƲpC]8}<@,%SoZQ|;<@!Wx qq䉢x͑Wy]7]80J1F)dGЖ5HiH^9gR >֩*6Hw)yrS71FEDDDDDDTEDDDDDD%"""7z=¨#DAeeeeeeeeeeeehZAr/a9 DDeYDDD} Br*@e HMʧ$Gs%ٱ(c)!"*"""VqXS 9/!"*"""%1G1a[Xqg)QYu}́HF 11fe2ƘMx[?!!9< DDeYDDD0S^'6bS~0DDeYDDDQaNzE|{Yn\? a⃣IK1P"""rUzrfb0uwpJFCw.u?wKDDeYeYDDD=?vh(7a_ZpjǠfwODDeYeYDDD~p<@;Gݵ9E<vDrLg1&T龜ʲʲd:c$K 9r&֏oz?Ի*","""W>;?_GGQ{xOv$&QzEDTUEDDrl#(Vk m?馚[X 8ǾqSefoJ% *Sr8X@,gSY)X?:˝X94;9",*"""v]9GB;t4,~' ^0|O,ʲHnZOO}eE+?A$xc+ @DDDrmI۱#_j]vU"oG[9he/6^ EDnuFmeeilwÙ"cJ}=jIʲ,X9>_D],{gD7^׿1g,ʲH\Wr$_<2eRJ+k@Ak DDn&:ϲEnG<*YqU~,jPDYwhd9ȲX6rpyt;[\CdAxR?7Gʲ,XBLOWGQ9M~zǷxݤMS ",*"""7:nh4U\'3HjL Ƙ DDTEeYDDkbw #tNqrW4d*y&HTȵ1>ԩ$ BI%YB!_}C0PAG&6HNI@,K,BߛϣLxE9 %"9Ⱦ,;2F"dYe!B/r`͟(&LCԒ[g^nI"dYe!"g~W:9k[%"638SZ,[`rEXDFK ZD Jƽ1xHDDuřUšTk#ɲ$B!>,i2.4DdG6_o% :`A_I4D2"Z?, !Tak)z^[$_il[`ި A$N3] r+ɲB!oT]e0xAқ9,QLgRt̮_)S SH@{\d=:]+U)$B!c4b0N9wa0neX޻/Jh{M43e2ž^] p<;&wSVw0zgfh Ny\Oѩ7̖@,t[X!-B!x 8Ej\Gŧ>3o9]s/kCYL~Mj\ ZGqN˔REj|׽Rz eUsMATAiYB!ģ,h[t}8}WP^ipknǖ{q3Bt[6*qa e4.6ۗ'Ng% "sr;GrjEIBhey-B$+ZM-Oϟ. nw~لm30/70r\8eLX/7l7XG#7{ yJͦdx]Aެy=fxM U7^^͎$'`xO'% v=|QP< @Tmʩ7Ki3^y.IN 7,p˻Ik"9D0nw?Īm1ug>-=Qpf]92hgٜ/ɱ&\\=ەSwθ J*Y㢔:RJ2݃ZO~B!^QKcA @W 7!&+R;lJo-N p;?.BG22:Y) L:ڬ Z"i!$YB!RjzKI1q兡#12ǟ /( gp$yZΞ-܋8>(Zi5m9{9nG/y~p{O8;W{ryʅe5*]GӰ&` EGhgyR*j\J)00 ٭*H7l!} \QJM驔 VJ%+)eDdtb%Q kNjB%<,`:Q!/gXnXSJ0jav9GrtYr2O!ŞM?m$>:>gC)+~j5pv[:7G+~몶k R3il8P詵NH4PJRJ%)"R3R^7R+.)RQ%9ϯS͋3~f7^ϢmwCu'uŖIm1ze!%$NVVm,PJyk2uT%)OiA[g$,nܮ@rl̹;ȂzSH$y~fFxc+@5شcI*I*pϷ!Yz$%K=pɽ?}z?3oM TDQGJ֏@^˒.-u|y e|9]d2*ϹѸRle]Z'*RSZi/= **^Z[]#ssi$=I5ՕՊ_Dدl+@|Xg|JyIBSaݥ L뎽u/Z;Rj:1S&F]QSU_х};1DcrwdBq[B~ eH8mǹ͌Y`#z+B峍æw+wOC]}c8/UoEp;q7!7xk*-9 =AoC`>67Lx٪ ji>ڒ@%ϋ:oWJ%9z]8&y0YZq7qxA1t,߂, !⯽f+*z@=k)̠{ߟ~R?.򞯘 ӊr@_nz &RoNXE$l E3B!?l`?4=nNDI2W+O+hG륄E_;+?~ ]hTk~tZ}G?) J[n]}R)u: weMv_"rO.gѪ|qg%9 +Hmn[N*>͛})HZO}\>{ 2_wO*Oȶin_=geǗmyX(\y7L;F.ɲB_@/T?u ϕ>$Y(S >%~oސb/t-GlO|R,3njӞO~{n` 8Mu0_)L܁Y瑧lV#Q/ٴvfjbr=C7[O|z'9ZC3>n}ߧ 3s|gs%cHS2@NWԩ9K T=CˮGl5)3:'\^HM 7,(4Ioj ^yZ$B!DNUJhq^\1v;`:)%͓p(Qﶔb&`67LR+yǏzIq}1Gfut-c74vKq;d?O{?oš\.{g?18==q֕ cFL7p8RGz[=qu)}833Di4/?=E,h}Iʧ$B!ģI)TCK<@WSPI1Q #%'?__Xy2u;G?+~VtP g=螽*w-Lgk/ya(ZK74|g܉W,IPƨ{noØ$E5pձt؃_XVƍ$8|<6C1Pxʷs4 ~dq/ˣjn}g,=$B!UB_3ע7<5J9 ӂ.RV% L+˹ѸNl?Qxo/޷Wy_c9[N,{j%IkNW)R;&}E`NA7as$F5ޭ)Up[|}^=-os|z!z'oKc_^]SIoJR /w0}=v Zmʶ|׾sw)V#9ܐN]|}VjKe!"SF+QD* D#mMiu?k>NGhvX|SS4%G3<j@[e4n䳓pm:atF{rs6w&y3'_&1 V׎xsXvbYܖ$)1uY39>%B8o Il.T~;vfחӸ~6h~.SMx}#oKp/|DkUxH綽7Nwʹ/T-_  KRI U^B!kTe0^!_@=~)pS=r;ZXRԸZeݙQ,r"HqLN FE>Q$Au'ʶ jv=.휀%$KI2|hԕǣ)T;}?= \=vs(N+/QwpAJLe4J]nr\=0k( _@"Nn6}>w D$t{[?v>1Պ=Rbkv{C5x%nKȯqq>sVu/@ezDC!c)26"]jU~rln ~f7ހ{ tZ@Kmv LmMF_M)O=ON>&Y-BdDhJ7J!'st0Ne@o5Bscr H4.f/ć%$v;Ή vjn}My3veطkybrD*3h<~[n ?pE(CNe":]1\PCs~s#;ޕr1xK8(Ra0&Oٴ"?}$= 5vƢ}ރ{;߼؝~[:\;KRPq΋E>< Ė NRs3l;i,C{YkG6L)R?<xRBd+8>%]n@O)֍JbxK򔜃_1\!d$vO*~N[&BfXG^@@nTUIx7 Bqt u=-Sj%"3#%*Ǿ=ƒ2D_ot nn͡yTm08ƿ{=%"ξˎ/Iq0#ɕw5Uvp$c_Ҏ }]L%!~aBv}G_(X}LWur ;'Tfoܠph U5 BTcJZRB!#e0̋H m|m,)Z^l3 }ǢMN Bl#Tuh?Z Ր]PJK3@3Ǽ-I $Y;.B<*Feb\`לl\5rΞG꽶{Qd'pfM~ʶ u{H>\õ~-NjczR{/nN;o)$K75)wZ}903Ǘq1W;vۈ 3cI,w5 ˖k{ ~b1`{v*YXH,u;BP WY?z*yK͌9> T/ 8\<CZ){@{u0lA%cv=8d)gפ{K}8ʆ1Hlf xA)~?(Lnm\܋y3;éUm h[i% gUشsu3K IBh78oM|qmjuJ9Fj/YfĊ^32{Sou*WuCAt+ƾE|XS% WA @Fv1ɕw=- #ߵtB!rRln=Xv,;%*B<#3j6kI3ŗSTe!xBsګ S)~ݹu]mſ!v[..Sk5~hՍ* 򞃉8?7G5D\Ə\Z%*5|KϢPŏY=9a2c I=&U-㦥~h?~RPh|GȮhף+<_\=;xƑqY7 !f`4GW_n )159a8;їaIp5[/ IDATCKqvF~]S*[V`0'QG|E|X{6~QVŪ'wU|P0g 2ꁱx[v a<zҵ= eY!i ֱ@TC|4#?7K%2BK st`l)6m%(Oe¯{Vv} U>p4~ })2Ęb\Ly3׳y\ Ǚe70;ZYK5DDns'z7< ,iǢNćWK3 4`8.e?( tsԷg9N._6ل]:C2-&"oߋʯ27Nc7]^s!F;9!ځoNpމb5fqhn݅ޢdSq]ofrrUr?h&Ӵ?*tF0nia Uz2$U~dWrڴaAO lK_G#:]e^ćcS)T{qzҲ,Bܤy[*s;8odW`WO~)-ô֒(?IN- gtutoh9|a'/sxK)Nˡ|6gx˒O;rMO @J\nNaI,[LXɱ&vtPǀbIqؗ(4h+J77ŞZ"3^N.-0>3,Sy.I@rtrHOOFllN[c<6l"kvڞXEv`K̚w"m0E`v}1$zZ( J`wSQX(Ӽ;§h ;u/sNiDžeD/ ocW흽MXSyu)hQOz ;/Al޻ם<1+6oP*O+qC*9e|73M _ 3J [!Lv?7y2DE,#FƧ:d q=-!tLӓ(ewjyoBv{eyn?p~HW;}--Dsw%Gat@̕^۝ h}uB^ΰ5cnW#&;I>qwppPWzbI* NRK >Q<, !fġtN fKT_Tʾt]T) чߗ)P~=-^bZ`9L~RbmrfQ4 gO-⮵e 8{spb 5̫9%ygoR&:{]))yۗ* }/T-_  KRI U@ns=Π`9Xr~歿|fp .w>%oٟ,q׺dX` m&R `5qބ&5X@׾cg`kv}9 g6n2DDJęX5HaP6n2i8fw햗HqMil]#te_;R>gܼqʽ31̖O"1x8{,50G.C8SGݵ򱜠Yg[ c vrf,xaXg H/Wv$]v띸~9Sb q፹ca)P#ZOၷ]?5/!~`0жU_l0*us\,4dEAjB @bX W02&Xa, OWZneS{o.7v5/ r0r(ϼWOn"zkgng.VL8rA !+ Jc4˗NHʿ]OHM:ҲlvQHZ|)~Frt}.>Nv\?2mz>1%h7{DCdi?Mų [!KkWJ5`^Wg.ΖZ\?Kҳ]08] mg,ʐ}na;X!\y@<[; kdX0:[.c-߼xgã,h3Z(D|?l {:חվ7`ǧ'/yY>v iK;re !EZBI6A)r1'MPj? eqr;BG ϒ^ ?]8G#:]e^ćR:6Ω18-1nQ_qrqx~<6BX[tZyV63Yks >}=7%p`] [>j-Zu0GRo?_{8\ؚ]_LC]g\9q7{=2Xd!$B!x WJ,YZM[,'6~D-F̕ݒq>"3`nJlVE[x۟FR gizQLj1Wj`t(Պs7w˚b`ׄݨVG=Sqy7;DJ\4vsZ U VIBoU*e4gnɔoD>DueØQ$E6Gk3 \czc0ݾ3HG/`I,dz-_7:uz\UO lɹDIBhfeR<ǖL"x:.:Ć1IjHc Zحٍ?g?!npF,1:A1\HM(s{.x(^q,K+p~ ֕f$WFRLn Rr2B!]¬ mMmLj g RlrF #yuKRxȆ$G5 쨇\?U vkvO*w'9FRs\gKi;,{&ۜ[kr'm|*Wsk3YJ$YB!ģOoѶ \,[}ȹuYbn'Iau<ʐ+b5bhU{Ϙ+㰦~35xfsy"? i?C=]o+܉fX35jyUk"WG e6$6k$B!x\$mK@JvM}_;]{zQZ9W^칃Jat6Ո P[D^DΊLv=gu ϝ`Hk%HKBs9cn/u[ۭ~Zr>%6R!sPZk B!;e4`A?sA]ZDx_>WM$yyιOuٸ,ew<6pa2nrZB7Sk _\vթ)%YB!ēD+Ud\r[i:!<@L}LOҵ'(?,Glf5pt3PmOr2Kx;$mK)WW}0wn n:Ve!B<+Gkn2~|GQwSݟ#sXIןAY%vvO566+"]ޘ~vnq!(3KLadY!OBm4\g[Q VщL* s13S(Ҫm 5:ã, !iW0:M"WKt~DDe2}Ydkhɩ Q+\xG'K|nnIJ7Cf螋K,BLQqr>5xSi5f!CHrI%(*'leAEc˜2/e85IkyVe!Bd 3s fW? |0JzpsOEGh 'I,BL4?2]l7f/ A,%= Be!B?WT!`3 ?7H6[?ʤu>&BH,BqʊRs3{hQy&HT A(Ζ!$YB!x9oŴR" ߜSaM K3Y~BH,B*/fq`|r-R,ꇾ] mZ!$YB!`4*B}^K[c/6 Be!BG]qv1E%"XgDr3605 Be!BUQʄ4k)j³nJT2ՃZ~gڜɫ$ BIB!\\ ew=_" 6$W ^mIy_"dY!iUn(Q?<%KABX9OJ@, !B< R&nA//S=)F7ʾAkk\ Be!B̗43Ki;]f~}_ƚt%md Be!B\1;26t xF"ۍzجôֻ% BIB!JGRM 27j2|mK$BH,BU+?]X?p }Q"Du1IQis&t]BIB!|H)Ces*^&Qy@"l7Z5!$B!/i< h\_?4VDvhKI@, !Bd P4`DOw@|X1uD!ɲB!DN)9ܹh8f !W`P[SY"dY!"&MPI\A? ^]bMfN3C!ɲB!@ctnISx~Pzwwb<:BkG!$B!"cEI($Fyєl}[>9ږ:EJ_!ɲ$B!4wB&RZL%[clpqִ9oI !$B!^q2'bv@ŮAT|%Ъmu#>BkNJX!$YB!g'Lx>JsAVskal68)U!dY!T78bx,_H⭂جõ$Be!BG[R+mnfO^xg c2ή}7 `|_[SJ !$B!;in2OzYڹc qd lZFhmRbB!ɲB!ēXԆ/OO}VIi]I !$B!OrTq)+\O|'|\K;3-uBH,BY+FͶ2\k!%!, !Bdʖ%FT2*]CۆV*ζCۇk7HBe!B]R gthfٟb6Gm!dY!"kU`_ }_pq$F uA",, !BdʗŔry𩔨wW\?2mzDU!$YB!.I(S >x㋋rh@'%uDQ!$Y۷cV $e&eSv)E2*(Ob0YJnWrսϳ390emtvov]=~ 0ޅŔN73sپ 'No error', JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', JSON_ERROR_SYNTAX => 'Syntax error', JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded' ); $error = json_last_error(); return isset($errors[$error]) ? $errors[$error] : 'Unknown error'; } } // function not autoloaded in PHP, thus its a good place for them if (!function_exists('codecept_debug')) { function codecept_debug($data) { \Codeception\Util\Debug::debug($data); } } if (!function_exists('codecept_root_dir')) { function codecept_root_dir($appendPath = '') { return \Codeception\Configuration::projectDir() . $appendPath; } } if (!function_exists('codecept_output_dir')) { function codecept_output_dir($appendPath = '') { return \Codeception\Configuration::outputDir() . $appendPath; } } if (!function_exists('codecept_log_dir')) { function codecept_log_dir($appendPath = '') { return \Codeception\Configuration::outputDir() . $appendPath; } } if (!function_exists('codecept_data_dir')) { function codecept_data_dir($appendPath = '') { return \Codeception\Configuration::dataDir() . $appendPath; } } if (!function_exists('codecept_relative_path')) { function codecept_relative_path($path) { return \Codeception\Util\PathResolver::getRelativeDir( $path, \Codeception\Configuration::projectDir(), DIRECTORY_SEPARATOR ); } } add(new Codeception\Command\Build('build')); $app->add(new Codeception\Command\Run('run')); $app->add(new Codeception\Command\Console('console')); $app->add(new Codeception\Command\Bootstrap('bootstrap')); $app->add(new Codeception\Command\GenerateCept('generate:cept')); $app->add(new Codeception\Command\GenerateCest('generate:cest')); $app->add(new Codeception\Command\GenerateTest('generate:test')); $app->add(new Codeception\Command\GenerateSuite('generate:suite')); $app->add(new Codeception\Command\GenerateHelper('generate:helper')); $app->add(new Codeception\Command\GenerateScenarios('generate:scenarios')); $app->add(new Codeception\Command\Clean('clean')); $app->add(new Codeception\Command\GenerateGroup('generate:groupobject')); $app->add(new Codeception\Command\GeneratePageObject('generate:pageobject')); $app->add(new Codeception\Command\GenerateStepObject('generate:stepobject')); $app->add(new Codeception\Command\GenerateEnvironment('generate:environment')); $app->add(new Codeception\Command\GenerateFeature('generate:feature')); $app->add(new Codeception\Command\GherkinSnippets('gherkin:snippets')); $app->add(new Codeception\Command\GherkinSteps('gherkin:steps')); $app->add(new Codeception\Command\DryRun('dry-run')); $app->add(new Codeception\Command\ConfigValidate('config:validate')); $app->registerCustomCommands(); // add only if within a phar archive. if ('phar:' === substr(__FILE__, 0, 5)) { $app->add(new Codeception\Command\SelfUpdate('self-update')); } $app->run(); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!class_exists('PHPUnit_Util_String')) { /** * String helpers. */ class PHPUnit_Util_String { /** * Converts a string to UTF-8 encoding. * * @param string $string * * @return string */ public static function convertToUtf8($string) { return mb_convert_encoding($string, 'UTF-8'); } /** * Checks a string for UTF-8 encoding. * * @param string $string * * @return bool */ protected static function isUtf8($string) { $length = strlen($string); for ($i = 0; $i < $length; $i++) { if (ord($string[$i]) < 0x80) { $n = 0; } elseif ((ord($string[$i]) & 0xE0) == 0xC0) { $n = 1; } elseif ((ord($string[$i]) & 0xF0) == 0xE0) { $n = 2; } elseif ((ord($string[$i]) & 0xF0) == 0xF0) { $n = 3; } else { return false; } for ($j = 0; $j < $n; $j++) { if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) { return false; } } } return true; } } } /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestListener that generates JSON messages. */ if (!class_exists('PHPUnit_Util_Log_JSON')) { class PHPUnit_Util_Log_JSON extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { /** * @var string */ protected $currentTestSuiteName = ''; /** * @var string */ protected $currentTestName = ''; /** * @var bool */ protected $currentTestPass = true; /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), PHPUnit_Framework_TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * A warning occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_Warning $e * @param float $time */ public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time) { $this->writeCase( 'warning', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), PHPUnit_Framework_TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $this->writeCase( 'fail', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), PHPUnit_Framework_TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), 'Incomplete Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), 'Risky Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), 'Skipped Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->currentTestSuiteName = $suite->getName(); $this->currentTestName = ''; $this->write( [ 'event' => 'suiteStart', 'suite' => $this->currentTestSuiteName, 'tests' => count($suite) ] ); } /** * A testsuite ended. * * @param PHPUnit_Framework_TestSuite $suite */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->currentTestSuiteName = ''; $this->currentTestName = ''; } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { $this->currentTestName = PHPUnit_Util_Test::describe($test); $this->currentTestPass = true; $this->write( [ 'event' => 'testStart', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName ] ); } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if ($this->currentTestPass) { $this->writeCase('pass', $time, [], '', $test); } } /** * @param string $status * @param float $time * @param array $trace * @param string $message * @param PHPUnit_Framework_TestCase|null $test */ protected function writeCase($status, $time, array $trace = [], $message = '', $test = null) { $output = ''; // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) { $output = $test->getActualOutput(); } $this->write( [ 'event' => 'test', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName, 'status' => $status, 'time' => $time, 'trace' => $trace, 'message' => PHPUnit_Util_String::convertToUtf8($message), 'output' => $output, ] ); } /** * @param string $buffer */ public function write($buffer) { array_walk_recursive( $buffer, function (&$input) { if (is_string($input)) { $input = PHPUnit_Util_String::convertToUtf8($input); } } ); parent::write(json_encode($buffer, JSON_PRETTY_PRINT)); } } } /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!class_exists('PHPUnit_Util_Log_TAP')) { /** * A TestListener that generates a logfile of the * test execution using the Test Anything Protocol (TAP). */ class PHPUnit_Util_Log_TAP extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { /** * @var int */ protected $testNumber = 0; /** * @var int */ protected $testSuiteLevel = 0; /** * @var bool */ protected $testSuccessful = true; /** * Constructor. * * @param mixed $out * * @throws PHPUnit_Framework_Exception */ public function __construct($out = null) { parent::__construct($out); $this->write("TAP version 13\n"); } /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeNotOk($test, 'Error'); } /** * A warning occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_Warning $e * @param float $time */ public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time) { $this->writeNotOk($test, 'Warning'); } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $this->writeNotOk($test, 'Failure'); $message = explode( "\n", PHPUnit_Framework_TestFailure::exceptionToString($e) ); $diagnostic = [ 'message' => $message[0], 'severity' => 'fail' ]; if ($e instanceof PHPUnit_Framework_ExpectationFailedException) { $cf = $e->getComparisonFailure(); if ($cf !== null) { $diagnostic['data'] = [ 'got' => $cf->getActual(), 'expected' => $cf->getExpected() ]; } } $yaml = new Symfony\Component\Yaml\Dumper; $this->write( sprintf( " ---\n%s ...\n", $yaml->dump($diagnostic, 2, 2) ) ); } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeNotOk($test, '', 'TODO Incomplete Test'); } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->write( sprintf( "ok %d - # RISKY%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->write( sprintf( "ok %d - # SKIP%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->testSuiteLevel++; } /** * A testsuite ended. * * @param PHPUnit_Framework_TestSuite $suite */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->testSuiteLevel--; if ($this->testSuiteLevel == 0) { $this->write(sprintf("1..%d\n", $this->testNumber)); } } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { $this->testNumber++; $this->testSuccessful = true; } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if ($this->testSuccessful === true) { $this->write( sprintf( "ok %d - %s\n", $this->testNumber, PHPUnit_Util_Test::describe($test) ) ); } $this->writeDiagnostics($test); } /** * @param PHPUnit_Framework_Test $test * @param string $prefix * @param string $directive */ protected function writeNotOk(PHPUnit_Framework_Test $test, $prefix = '', $directive = '') { $this->write( sprintf( "not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', PHPUnit_Util_Test::describe($test), $directive != '' ? ' # ' . $directive : '' ) ); $this->testSuccessful = false; } /** * @param PHPUnit_Framework_Test $test */ private function writeDiagnostics(PHPUnit_Framework_Test $test) { if (!$test instanceof PHPUnit_Framework_TestCase) { return; } if (!$test->hasOutput()) { return; } foreach (explode("\n", trim($test->getActualOutput())) as $line) { $this->write( sprintf( "# %s\n", $line ) ); } } } } } // @codingStandardsIgnoreEnd 4"Oч#̒|UˏGBMB