#!/usr/bin/env php  & wp-cli.phar php/utils.php) ZJrɶphp/class-wp-cli.php) Z4Xphp/WP_CLI/Fetchers/Base.php) Zy9php/WP_CLI/NoOp.php) ZD1php/WP_CLI/Runner.php[) Z[ kphp/WP_CLI/Formatter.php$) Z$dphp/WP_CLI/UpgraderSkin.phpO) ZO޶"php/WP_CLI/Iterators/Transform.php) ZĿphp/WP_CLI/Iterators/Table.php[ ) Z[ -Gphp/WP_CLI/Iterators/Query.php ) Z ˶"php/WP_CLI/Iterators/Exception.phpR) ZRY7php/WP_CLI/Iterators/CSV.php) Zphp/WP_CLI/Autoloader.phpG) ZG,php/WP_CLI/SynopsisParser.php) ZXk%php/WP_CLI/Dispatcher/RootCommand.php) Zeζ(php/WP_CLI/Dispatcher/CommandFactory.php) Z6'q)php/WP_CLI/Dispatcher/CommandAddition.php") Z"ܨ$php/WP_CLI/Dispatcher/Subcommand.php-) Z-O~*php/WP_CLI/Dispatcher/CompositeCommand.php%) Z%);pvphp/WP_CLI/FileCache.php) Zzphp/WP_CLI/ExitException.phpD) ZDs/߶php/WP_CLI/Configurator.php$) Z$php/WP_CLI/Loggers/Base.php+) Z+-Gphp/WP_CLI/Loggers/Quiet.php) Zkphp/WP_CLI/Loggers/Regular.php) Z php/WP_CLI/Loggers/Execution.php) Z0php/WP_CLI/Process.php2 ) Z2 5,php/WP_CLI/PackageManagerEventSubscriber.php) Z[&!php/WP_CLI/WpHttpCacheManager.phpV ) ZV 5څphp/WP_CLI/ProcessRun.php) Z(~%php/WP_CLI/Bootstrap/LaunchRunner.php() Z(z\0php/WP_CLI/Bootstrap/DefineProtectedCommands.php) ZٶXw(php/WP_CLI/Bootstrap/ConfigureRunner.php5) Z5߶-)php/WP_CLI/Bootstrap/DeclareMainClass.php) ZTj1php/WP_CLI/Bootstrap/IncludePackageAutoloader.php) ZΜ2php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php ) Z %ʚ-php/WP_CLI/Bootstrap/LoadUtilityFunctions.php)) Z)TYMݶ1php/WP_CLI/Bootstrap/IncludeBundledAutoloader.phpO) ZOWZ3php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.phpM) ZMM6 )php/WP_CLI/Bootstrap/InitializeLogger.php) Z(mJ/php/WP_CLI/Bootstrap/InitializeColorization.phpL) ZL'='php/WP_CLI/Bootstrap/LoadDispatcher.php5) Z5U4&php/WP_CLI/Bootstrap/BootstrapStep.php) ZA7/ 2php/WP_CLI/Bootstrap/RegisterFrameworkCommands.php) Zö1php/WP_CLI/Bootstrap/RegisterDeferredCommands.php) ZQ+뷶'php/WP_CLI/Bootstrap/RunnerInstance.php) Z{,php/WP_CLI/Bootstrap/LoadRequiredCommand.php) Z7n'php/WP_CLI/Bootstrap/AutoloaderStep.php) Z_ܴ3php/WP_CLI/Bootstrap/DeclareAbstractBaseCommand.phpA) ZA@<@'php/WP_CLI/Bootstrap/BootstrapState.php) Z(5php/WP_CLI/Extractor.phpE) ZE(׶php/WP_CLI/Completions.php) Z php/WP_CLI/SynopsisValidator.php) Zӄbphp/WP_CLI/AutoloadSplitter.phpY) ZYߓphp/WP_CLI/DocParser.php) Zdphp/WP_CLI/ComposerIO.php) Zphp/bootstrap.phpA) ZA]php/utils-wp.php*) Z*jXhphp/wp-settings-cli.php>) Z>bophp/commands/cli.php4) Z4@Pphp/commands/help.php) ZVE php/commands/src/CLI_Command.phpJ) ZJWphp/config-spec.phpj ) Zj Xɡphp/boot-fs.php) Zphp/boot-phar.php9) Z9Ysphp/dispatcher.phpQ) ZQ!php/class-wp-cli-command.php) Z*php/wp-cli.phpX) ZX 0vendor/mustache/mustache/bin/build_bootstrap.php;) Z;:vendor/mustache/mustache/src/Mustache/HelperCollection.php) ZӶ0vendor/mustache/mustache/src/Mustache/Source.php) Zb׶m4vendor/mustache/mustache/src/Mustache/Autoloader.php) Z=cֶ0vendor/mustache/mustache/src/Mustache/Engine.phpc) ZcR/vendor/mustache/mustache/src/Mustache/Cache.php) Z% H ?vendor/mustache/mustache/src/Mustache/Cache/FilesystemCache.phph) Zh(}49vendor/mustache/mustache/src/Mustache/Cache/NoopCache.phpA) ZAٶ=vendor/mustache/mustache/src/Mustache/Cache/AbstractCache.php) ZV߀۶1vendor/mustache/mustache/src/Mustache/Context.php) ZD@vendor/mustache/mustache/src/Mustache/Loader/CascadingLoader.php) Z<vendor/mustache/mustache/src/Mustache/Loader/ArrayLoader.php) Z!Kvendor/mustache/mustache/src/Mustache/Loader/ProductionFilesystemLoader.phpf ) Zf =vendor/mustache/mustache/src/Mustache/Loader/StringLoader.php) Z<%=vendor/mustache/mustache/src/Mustache/Loader/InlineLoader.php) Z\ >vendor/mustache/mustache/src/Mustache/Loader/MutableLoader.php) ZgAvendor/mustache/mustache/src/Mustache/Loader/FilesystemLoader.php) ZL$y6vendor/mustache/mustache/src/Mustache/LambdaHelper.php) Zpն2vendor/mustache/mustache/src/Mustache/Compiler.php|X) Z|XVs3vendor/mustache/mustache/src/Mustache/Tokenizer.php)) Z) 0vendor/mustache/mustache/src/Mustache/Loader.phpB) ZBQy3vendor/mustache/mustache/src/Mustache/Exception.phpR) ZR]uAvendor/mustache/mustache/src/Mustache/Source/FilesystemSource.phpj) Zj@0vendor/mustache/mustache/src/Mustache/Parser.phpx*) Zx*{0vendor/mustache/mustache/src/Mustache/Logger.php$ ) Z$ @Y9=vendor/mustache/mustache/src/Mustache/Logger/StreamLogger.php) ZJ?vendor/mustache/mustache/src/Mustache/Logger/AbstractLogger.phpv ) Zv nY62vendor/mustache/mustache/src/Mustache/Template.php) Z8Jvendor/mustache/mustache/src/Mustache/Exception/UnknownHelperException.php) ZHjJvendor/mustache/mustache/src/Mustache/Exception/UnknownFilterException.php) Zf޶Lvendor/mustache/mustache/src/Mustache/Exception/UnknownTemplateException.php) Z-[Bvendor/mustache/mustache/src/Mustache/Exception/LogicException.php) Z}ADvendor/mustache/mustache/src/Mustache/Exception/RuntimeException.php) Z1]ӶLvendor/mustache/mustache/src/Mustache/Exception/InvalidArgumentException.php) ZQCvendor/mustache/mustache/src/Mustache/Exception/SyntaxException.php) ZN1vendor/rmccue/requests/library/Requests/Proxy.php-) Z-3vendor/rmccue/requests/library/Requests/Session.php) Zx¶1vendor/rmccue/requests/library/Requests/Hooks.phpw) ZwTֶ/vendor/rmccue/requests/library/Requests/IRI.php3o) Z3oD :vendor/rmccue/requests/library/Requests/Transport/cURL.php;) Z;8L߶?vendor/rmccue/requests/library/Requests/Transport/fsockopen.php0) Z06vendor/rmccue/requests/library/Requests/Cookie/Jar.php) Z! Wg2vendor/rmccue/requests/library/Requests/Cookie.php62) Z62VJ6x/vendor/rmccue/requests/library/Requests/SSL.php) Zvendor/rmccue/requests/library/Requests/Exception/HTTP/418.php) Z?/l>vendor/rmccue/requests/library/Requests/Exception/HTTP/411.php) ZuI>vendor/rmccue/requests/library/Requests/Exception/HTTP/306.php) ZMo>vendor/rmccue/requests/library/Requests/Exception/HTTP/412.php) ZRUJ>vendor/rmccue/requests/library/Requests/Exception/HTTP/415.php) Z9>vendor/rmccue/requests/library/Requests/Exception/HTTP/428.php) Zْ/>vendor/rmccue/requests/library/Requests/Exception/HTTP/431.php) Z3JѶ>vendor/rmccue/requests/library/Requests/Exception/HTTP/408.php) Z&M>vendor/rmccue/requests/library/Requests/Exception/HTTP/405.php) Z]>vendor/rmccue/requests/library/Requests/Exception/HTTP/403.php}) Z}^%>vendor/rmccue/requests/library/Requests/Exception/HTTP/504.php) Zm͍>vendor/rmccue/requests/library/Requests/Exception/HTTP/400.php) Zv>vendor/rmccue/requests/library/Requests/Exception/HTTP/503.php) Z*>vendor/rmccue/requests/library/Requests/Exception/HTTP/416.php) Z1>vendor/rmccue/requests/library/Requests/Exception/HTTP/409.phpz) Zz>vendor/rmccue/requests/library/Requests/Exception/HTTP/404.php}) Z}k>vendor/rmccue/requests/library/Requests/Exception/HTTP/413.php) Z6^%>vendor/rmccue/requests/library/Requests/Exception/HTTP/304.php) ZH9>vendor/rmccue/requests/library/Requests/Exception/HTTP/410.phpn) Zn|Z>vendor/rmccue/requests/library/Requests/Exception/HTTP/402.php) ZU>vendor/rmccue/requests/library/Requests/Exception/HTTP/401.php) Z >vendor/rmccue/requests/library/Requests/Exception/HTTP/417.php) Z;>vendor/rmccue/requests/library/Requests/Exception/HTTP/406.php) Z*>vendor/rmccue/requests/library/Requests/Exception/HTTP/414.php) Zy>vendor/rmccue/requests/library/Requests/Exception/HTTP/500.php) Z}l\Bvendor/rmccue/requests/library/Requests/Exception/HTTP/Unknown.phpc) Zc"v>vendor/rmccue/requests/library/Requests/Exception/HTTP/505.php) Z">vendor/rmccue/requests/library/Requests/Exception/HTTP/407.php) Zbiֶ>vendor/rmccue/requests/library/Requests/Exception/HTTP/502.php) Zb>vendor/rmccue/requests/library/Requests/Exception/HTTP/429.php%) Z%]fn>vendor/rmccue/requests/library/Requests/Exception/HTTP/511.php) Z D>vendor/rmccue/requests/library/Requests/Exception/HTTP/305.php~) Z~4-O>vendor/rmccue/requests/library/Requests/Exception/HTTP/501.php) Zc/?vendor/rmccue/requests/library/Requests/Exception/Transport.phpJ) ZJ9[:vendor/rmccue/requests/library/Requests/Exception/HTTP.php) ZS冶+vendor/rmccue/requests/library/Requests.php^t) Z^tD ?2vendor/rmccue/requests/bin/create_pear_package.php#) Z# !쯶*vendor/composer/ca-bundle/src/CaBundle.php5) Z5ps!vendor/composer/include_paths.phpe) Ze\5J.vendor/composer/autoload_commands_classmap.php,!) Z,!ݽ #vendor/composer/autoload_static.phpN() ZN(t !vendor/composer/autoload_psr4.php) Z61vendor/composer/composer/src/Composer/Factory.phpd_) Zd_%<vendor/composer/composer/src/Composer/Json/JsonFormatter.php) Zu[Fvendor/composer/composer/src/Composer/Json/JsonValidationException.php) ZJȶ7vendor/composer/composer/src/Composer/Json/JsonFile.phpS%) ZS%| >vendor/composer/composer/src/Composer/Json/JsonManipulator.phpJ) ZJcʶRvendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php) Z\!'ƶ?vendor/composer/composer/src/Composer/EventDispatcher/Event.phpf) ZfKxնRvendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php) Z#gIvendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.phpP) ZP̰Զ9vendor/composer/composer/src/Composer/SelfUpdate/Keys.phpj) ZjA=vendor/composer/composer/src/Composer/SelfUpdate/Versions.php\) Z\l=vendor/composer/composer/src/Composer/Command/ShowCommand.php) ZlƶGvendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php&) Z&G#.@vendor/composer/composer/src/Composer/Command/RequireCommand.php') Z'K?vendor/composer/composer/src/Composer/Command/RemoveCommand.php) Z7;Avendor/composer/composer/src/Composer/Command/DiagnoseCommand.phpd) ZdFvendor/composer/composer/src/Composer/Command/CreateProjectCommand.php=C) Z=Cc=vendor/composer/composer/src/Composer/Command/ExecCommand.php ) Z 2ٶAvendor/composer/composer/src/Composer/Command/OutdatedCommand.php ) Z !fCvendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php?) Z?J.˶Dvendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php ) Z 4؞?vendor/composer/composer/src/Composer/Command/StatusCommand.php) ZFAvendor/composer/composer/src/Composer/Command/ValidateCommand.php) Zݥ@vendor/composer/composer/src/Composer/Command/ArchiveCommand.php) ZoBvendor/composer/composer/src/Composer/Command/ProhibitsCommand.php<) Z<Avendor/composer/composer/src/Composer/Command/SuggestsCommand.phpj) ZjG?vendor/composer/composer/src/Composer/Command/GlobalCommand.phpq ) Zq e@vendor/composer/composer/src/Composer/Command/DependsCommand.php() Z(G1ABvendor/composer/composer/src/Composer/Command/RunScriptCommand.php) ZϜH?vendor/composer/composer/src/Composer/Command/SearchCommand.phps ) Zs JCvendor/composer/composer/src/Composer/Command/ClearCacheCommand.php) Znx?vendor/composer/composer/src/Composer/Command/ConfigCommand.php=h) Z=hs =vendor/composer/composer/src/Composer/Command/BaseCommand.php ) Z ΰEvendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.phpT ) ZT 5e?vendor/composer/composer/src/Composer/Command/UpdateCommand.php)) Z)=vendor/composer/composer/src/Composer/Command/InitCommand.php\) Z\?ol=vendor/composer/composer/src/Composer/Command/HomeCommand.phpP) ZP2>vendor/composer/composer/src/Composer/Command/AboutCommand.phpc) ZcJAvendor/composer/composer/src/Composer/Command/LicensesCommand.phpC) ZCh@vendor/composer/composer/src/Composer/Command/InstallCommand.php) Z}.6vendor/composer/composer/src/Composer/IO/ConsoleIO.php#) Z# 3vendor/composer/composer/src/Composer/IO/BaseIO.php) ZwT#3vendor/composer/composer/src/Composer/IO/NullIO.php ) Z 5vendor/composer/composer/src/Composer/IO/BufferIO.php) Zە8vendor/composer/composer/src/Composer/IO/IOInterface.phpb) Zb@8GMvendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.phpk ) Zk |F>vendor/composer/composer/src/Composer/Autoload/ClassLoader.php4) Z4QDvendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php) Zm2 Dvendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php ) Z nJ72vendor/composer/composer/src/Composer/Composer.phpo) ZowAvendor/composer/composer/src/Composer/Package/CompletePackage.php:) Z: "w6vendor/composer/composer/src/Composer/Package/Link.php ) Z 0V=vendor/composer/composer/src/Composer/Package/RootPackage.phpD ) ZD -/Fvendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php) ZēLvendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php) ZH^Gvendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php5 ) Z5 ZIIIvendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php) Z*=Kvendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php#) Z# ꚶPvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php) ZԴ=Pvendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpZ) ZZHInwJvendor/composer/composer/src/Composer/Package/Archiver/HgExcludeFilter.php ) Z YLvendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php) ZlةPvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.phpK ) ZK I|/Bvendor/composer/composer/src/Composer/Package/PackageInterface.php#) Z#}'u=vendor/composer/composer/src/Composer/Package/BasePackage.php ) Z  Dvendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.phpH-) ZH-./+Pvendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php) ZIQضNvendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php+H) Z+Hk8BJvendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php") Z""hCvendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php\) Z\pjHvendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phpB) ZB*JFvendor/composer/composer/src/Composer/Package/RootPackageInterface.php ) Z ' f8vendor/composer/composer/src/Composer/Package/Locker.phpZ9) ZZ9hӶDvendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php) ZG>vendor/composer/composer/src/Composer/Package/AliasPackage.php#) Z#ض9vendor/composer/composer/src/Composer/Package/Package.php1) Z1p(Ivendor/composer/composer/src/Composer/Package/Version/VersionSelector.php) ZFBo'Gvendor/composer/composer/src/Composer/Package/Version/VersionParser.php) ZR*̶Hvendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php-) Z-VBvendor/composer/composer/src/Composer/Package/RootAliasPackage.php=) Z=6Jvendor/composer/composer/src/Composer/Package/CompletePackageInterface.php) ZrPvendor/composer/composer/src/Composer/Package/LinkConstraint/EmptyConstraint.php) Z3Pvendor/composer/composer/src/Composer/Package/LinkConstraint/MultiConstraint.php) Z[5Svendor/composer/composer/src/Composer/Package/LinkConstraint/SpecificConstraint.php) Z<Xvendor/composer/composer/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php) Z䑞Rvendor/composer/composer/src/Composer/Package/LinkConstraint/VersionConstraint.php) Z4O/vendor/composer/composer/src/Composer/Cache.php) Z˃X7vendor/composer/composer/src/Composer/XdebugHandler.phpr!) Zr!c=vendor/composer/composer/src/Composer/Script/CommandEvent.php) ZǶ6vendor/composer/composer/src/Composer/Script/Event.php) ZLD=vendor/composer/composer/src/Composer/Script/PackageEvent.php) Z=vendor/composer/composer/src/Composer/Script/ScriptEvents.php) ZNׯ=vendor/composer/composer/src/Composer/Util/NoProxyPattern.phpF) ZFM7vendor/composer/composer/src/Composer/Util/Silencer.php) Z ^8vendor/composer/composer/src/Composer/Util/TlsHelper.php) Zq8vendor/composer/composer/src/Composer/Util/IniHelper.php) Zy5vendor/composer/composer/src/Composer/Util/GitHub.php) Z)8vendor/composer/composer/src/Composer/Util/Bitbucket.php!) Z!W5vendor/composer/composer/src/Composer/Util/GitLab.php) Z~'öCvendor/composer/composer/src/Composer/Util/StreamContextFactory.php9) Z9^ # 9vendor/composer/composer/src/Composer/Util/Filesystem.phpS) ZSĴQ=vendor/composer/composer/src/Composer/Util/ComposerMirror.php) ZQ>vendor/composer/composer/src/Composer/Util/ProcessExecutor.php) Zu7vendor/composer/composer/src/Composer/Util/Platform.php( ) Z( R>vendor/composer/composer/src/Composer/Util/ConfigValidator.phpe) ZeQݶ2vendor/composer/composer/src/Composer/Util/Svn.phps!) Zs!)5߹:vendor/composer/composer/src/Composer/Util/SpdxLicense.php:) Z:R2vendor/composer/composer/src/Composer/Util/Git.php7) Z7\rǶ9vendor/composer/composer/src/Composer/Util/AuthHelper.php) ZF7vendor/composer/composer/src/Composer/Util/Perforce.phpC) ZC0 *;vendor/composer/composer/src/Composer/Util/ErrorHandler.php) Z s?vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php4) Z4R Fvendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php) ZQ-Avendor/composer/composer/src/Composer/Config/JsonConfigSource.php) Zi Cvendor/composer/composer/src/Composer/Installer/InstallerEvents.php) Z.!Kvendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php) ZCvendor/composer/composer/src/Composer/Installer/BinaryInstaller.phpl) Zlm5ʶBvendor/composer/composer/src/Composer/Installer/InstallerEvent.php ) Z Cvendor/composer/composer/src/Composer/Installer/PluginInstaller.php4 ) Z4  YAvendor/composer/composer/src/Composer/Installer/PearInstaller.php6 ) Z6 x Mvendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php) ZYrAvendor/composer/composer/src/Composer/Installer/PackageEvents.php) ZZVGvendor/composer/composer/src/Composer/Installer/PearBinaryInstaller.php>) Z>qvAvendor/composer/composer/src/Composer/Installer/NoopInstaller.phpn) Zn1aDvendor/composer/composer/src/Composer/Installer/ProjectInstaller.php/ ) Z/ #D@vendor/composer/composer/src/Composer/Installer/PackageEvent.phpT) ZT[ڶFvendor/composer/composer/src/Composer/Installer/InstallerInterface.php ) Z {`Hvendor/composer/composer/src/Composer/Installer/MetapackageInstaller.phpy) ZyW}Gvendor/composer/composer/src/Composer/Installer/InstallationManager.php*) Z*nwDDvendor/composer/composer/src/Composer/Installer/LibraryInstaller.php) Z~ Evendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php ) Z Rܶ=vendor/composer/composer/src/Composer/Console/Application.phpI) ZI3w2vendor/composer/composer/src/Composer/Compiler.php') Z'׳Qvendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.phpC) ZC#Bvendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php) ZGq?Hvendor/composer/composer/src/Composer/Repository/Vcs/BitbucketDriver.php2) Z23AKvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php~ ) Z~ Bvendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php-) Z-ֶBvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php^) Z^tlGvendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php) ZwIEvendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.phpB) ZB /" Evendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.phpO) ZO_9Kvendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.phpL ) ZL t4Jvendor/composer/composer/src/Composer/Repository/Vcs/HgBitbucketDriver.phpF ) ZF 3Avendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php|) Z|曈WEvendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php8) Z8ڣRHvendor/composer/composer/src/Composer/Repository/CompositeRepository.phpW) ZW2Dvendor/composer/composer/src/Composer/Repository/ArrayRepository.phpV) ZVaNTvendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php) Z6{Bvendor/composer/composer/src/Composer/Repository/VcsRepository.php,) Z,ֶFvendor/composer/composer/src/Composer/Repository/PackageRepository.php\) Z\其Fvendor/composer/composer/src/Composer/Repository/RepositoryFactory.php-) Z-aRvendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php) ZYLvendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php) ZdIvendor/composer/composer/src/Composer/Repository/FilesystemRepository.php) ZQ Gvendor/composer/composer/src/Composer/Repository/ArtifactRepository.php) Z[o8bCvendor/composer/composer/src/Composer/Repository/PathRepository.php) Z"UGvendor/composer/composer/src/Composer/Repository/ComposerRepository.php\|) Z\|dOvendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php) Z$Mvendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.phpH) ZH +;IFvendor/composer/composer/src/Composer/Repository/RepositoryManager.php) ZhZGvendor/composer/composer/src/Composer/Repository/PlatformRepository.phpG*) ZG*Pvendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php) Z<9Pvendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php) ZugbHvendor/composer/composer/src/Composer/Repository/RepositoryInterface.php) ZOCvendor/composer/composer/src/Composer/Repository/PearRepository.php'") Z'"뛅ǶCvendor/composer/composer/src/Composer/Repository/BaseRepository.phps) ZsMGvendor/composer/composer/src/Composer/Repository/Pear/ChannelReader.php ) Z ŅNvendor/composer/composer/src/Composer/Repository/Pear/DependencyConstraint.php) Zs2Kvendor/composer/composer/src/Composer/Repository/Pear/BaseChannelReader.php ) Z 6܅նMvendor/composer/composer/src/Composer/Repository/Pear/ChannelRest11Reader.php) Z`V׶Hvendor/composer/composer/src/Composer/Repository/Pear/DependencyInfo.php) Z-mEvendor/composer/composer/src/Composer/Repository/Pear/PackageInfo.phpJ) ZJrMvendor/composer/composer/src/Composer/Repository/Pear/ChannelRest10Reader.php ) Z M-0Qvendor/composer/composer/src/Composer/Repository/Pear/PackageDependencyParser.php*) Z*DDжEvendor/composer/composer/src/Composer/Repository/Pear/ReleaseInfo.php) Z3&Evendor/composer/composer/src/Composer/Repository/Pear/ChannelInfo.php) Zs3vendor/composer/composer/src/Composer/Installer.php ) Z }wHvendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php|) Z|y5`GKvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php) Zz!nDvendor/composer/composer/src/Composer/DependencyResolver/Request.php) Z~6Cvendor/composer/composer/src/Composer/DependencyResolver/Solver.phph) ZhyJvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php ) Z 7Avendor/composer/composer/src/Composer/DependencyResolver/Pool.php7) Z7HAvendor/composer/composer/src/Composer/DependencyResolver/Rule.php#) Z#~6Lvendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php) ZJvendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.phpy$) Zy$FhCHvendor/composer/composer/src/Composer/DependencyResolver/Transaction.phpJ) ZJ+>Jvendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php ) Z }#Kvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.phpz) Zzd]_CLvendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php ) Z FMvendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php1) Z1\'Dvendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php<) Z<ZDvendor/composer/composer/src/Composer/DependencyResolver/Problem.php") Z"d1Tvendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.phpu ) Zu cȶFvendor/composer/composer/src/Composer/DependencyResolver/Decisions.phpg) ZgB:ֶVvendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php.) Z. %pgbvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php) ZDVvendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php) Z݊Yvendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.phpS) ZSBTYvendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php#) Z#uWvendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.phpK) ZKQdvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php) Zx@Ovendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php) Zv ?Jvendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php) Z)ŸCvendor/composer/composer/src/Composer/Downloader/FileDownloader.php&) Z&ϔBvendor/composer/composer/src/Composer/Downloader/GitDownloader.php'L) Z'LcEvendor/composer/composer/src/Composer/Downloader/FossilDownloader.php) Z;v\Avendor/composer/composer/src/Composer/Downloader/HgDownloader.php ) Z 'TXRvendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php) Z Bvendor/composer/composer/src/Composer/Downloader/TarDownloader.php) Z fFvendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php) ZCvendor/composer/composer/src/Composer/Downloader/GzipDownloader.php* ) Z* rwBvendor/composer/composer/src/Composer/Downloader/ZipDownloader.php) ZhSGvendor/composer/composer/src/Composer/Downloader/TransportException.php) Zy#eBvendor/composer/composer/src/Composer/Downloader/VcsDownloader.php )) Z )smCvendor/composer/composer/src/Composer/Downloader/PharDownloader.php) Zx6Cvendor/composer/composer/src/Composer/Downloader/PathDownloader.php) Zp|FIvendor/composer/composer/src/Composer/Downloader/PearPackageExtractor.php-) Z-Dl~Bvendor/composer/composer/src/Composer/Downloader/RarDownloader.php& ) Z& ¶Gvendor/composer/composer/src/Composer/Downloader/PerforceDownloader.phpH ) ZH J Bvendor/composer/composer/src/Composer/Downloader/SvnDownloader.php<) Z<*.Hvendor/composer/composer/src/Composer/Downloader/FilesystemException.php) ZDvendor/composer/composer/src/Composer/Downloader/DownloadManager.php)) Z)Avendor/composer/composer/src/Composer/Downloader/XzDownloader.php) Z-DJLvendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php) Zv(Hvendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php) ZRz^0vendor/composer/composer/src/Composer/Config.php<) Z<tʌKvendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php_) Z_'Fvendor/composer/composer/src/Composer/Plugin/Capability/Capability.php) ZY93=vendor/composer/composer/src/Composer/Plugin/CommandEvent.php) Z# Evendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php7) Z7JD=vendor/composer/composer/src/Composer/Plugin/PluginEvents.phpr) Zr|IB@vendor/composer/composer/src/Composer/Plugin/PluginInterface.php*) Z*u*ֶ>vendor/composer/composer/src/Composer/Plugin/PluginManager.php'>) Z'>8vendor/composer/composer/src/Composer/Plugin/Capable.php) Z'}Bvendor/composer/composer/src/Composer/Exception/NoSslException.php) Zg*vendor/composer/composer/src/bootstrap.php) ZcAl!vendor/composer/autoload_real.php( ) Z( KoSJ9vendor/composer/semver/src/Constraint/EmptyConstraint.php8) Z8)Ͷ9vendor/composer/semver/src/Constraint/MultiConstraint.php& ) Z& (4vendor/composer/semver/src/Constraint/Constraint.phpV) ZVzn<vendor/composer/semver/src/Constraint/AbstractConstraint.phpW) ZWP=vendor/composer/semver/src/Constraint/ConstraintInterface.php^) Z^)X,vendor/composer/semver/src/VersionParser.phpAM) ZAM7Ǐ1)vendor/composer/semver/src/Comparator.php ) Z g3 %vendor/composer/semver/src/Semver.php ) Z =nQAvendor/composer/ClassLoader.php4) Z4Q'vendor/composer/autoload_namespaces.php1) Z1(2ȶ2vendor/composer/spdx-licenses/src/SpdxLicenses.php) Z%g9vendor/composer/spdx-licenses/src/SpdxLicensesUpdater.php) ZK%/vendor/composer/autoload_framework_classmap.phpM) ZM&v<*vendor/composer/autoload_commands_real.php) Zn+"vendor/composer/autoload_files.php ) Z 8%vendor/composer/autoload_classmap.php) Zb+vendor/composer/autoload_framework_real.php) Z /vendor/ramsey/array_column/src/array_column.php ) Z 0vendor/symfony/finder/Adapter/GnuFindAdapter.php ) Z Bm1vendor/symfony/finder/Adapter/AbstractAdapter.php) Z Ѷ2vendor/symfony/finder/Adapter/AdapterInterface.php/ ) Z/ .k,vendor/symfony/finder/Adapter/PhpAdapter.phpq ) Zq 5vendor/symfony/finder/Adapter/AbstractFindAdapter.php]*) Z]*%0vendor/symfony/finder/Adapter/BsdFindAdapter.php ) Z OH vendor/symfony/finder/Finder.php_) Z_ M9vendor/symfony/finder/Iterator/FilenameFilterIterator.php) Z p1vendor/symfony/finder/Iterator/FilterIterator.php) Z8=/:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php) Z:~ж7vendor/symfony/finder/Iterator/CustomFilterIterator.php) ZcˀS<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php) Zr~=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php ) Z 5Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php ) Z ,9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php>) Z>pY5vendor/symfony/finder/Iterator/PathFilterIterator.php) Z;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php) Z4vendor/symfony/finder/Iterator/FilePathsIterator.phpP ) ZP =vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php) ZZ@ 3vendor/symfony/finder/Iterator/SortableIterator.php ) Z !ư:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php) Z@*vendor/symfony/finder/Expression/Regex.php~) Z~93vendor/symfony/finder/Expression/ValueInterface.php) Z1GE/vendor/symfony/finder/Expression/Expression.php ) Z c)vendor/symfony/finder/Expression/Glob.phpD) ZD^bvendor/symfony/finder/Glob.php ) Z R`5vendor/symfony/finder/Comparator/NumberComparator.php ) Z 3vendor/symfony/finder/Comparator/DateComparator.php) ZGö/vendor/symfony/finder/Comparator/Comparator.php) Z'vendor/symfony/finder/Shell/Command.phph) ZhF%vendor/symfony/finder/Shell/Shell.phpK) ZK5%vendor/symfony/finder/SplFileInfo.phpZ) ZZiqpAvendor/symfony/finder/Exception/OperationNotPermitedException.phpr) Zrc 6vendor/symfony/finder/Exception/ExceptionInterface.php) Z7@vendor/symfony/finder/Exception/ShellCommandFailureException.php) Z~˶9vendor/symfony/finder/Exception/AccessDeniedException.php) ZcW޶;vendor/symfony/finder/Exception/AdapterFailureException.phpK) ZK|^@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php9J) Z9J|Q@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpI) ZI҈-vendor/symfony/polyfill-mbstring/Mbstring.phpjV) ZjVv.vendor/symfony/polyfill-mbstring/bootstrap.php') Z'p#Di+vendor/wp-cli/eval-command/eval-command.php) ZGB3vendor/wp-cli/eval-command/src/EvalFile_Command.php) Zs/vendor/wp-cli/eval-command/src/Eval_Command.phpQ) ZQ-1vendor/wp-cli/php-cli-tools/lib/cli/Arguments.php/) Z/o6vendor/wp-cli/php-cli-tools/lib/cli/table/Renderer.phpv) Zv5vendor/wp-cli/php-cli-tools/lib/cli/table/Tabular.php) Zl3vendor/wp-cli/php-cli-tools/lib/cli/table/Ascii.php) Z+Sȶ/vendor/wp-cli/php-cli-tools/lib/cli/Memoize.php) Z/vendor/wp-cli/php-cli-tools/lib/cli/Streams.php") Z"öBvendor/wp-cli/php-cli-tools/lib/cli/arguments/InvalidArguments.php) ZR<vendor/wp-cli/php-cli-tools/lib/cli/arguments/HelpScreen.php ) Z +B%7vendor/wp-cli/php-cli-tools/lib/cli/arguments/Lexer.php^ ) Z^ G:vendor/wp-cli/php-cli-tools/lib/cli/arguments/Argument.php ) Z =+vendor/wp-cli/php-cli-tools/lib/cli/cli.php@) Z@~z׶4vendor/wp-cli/php-cli-tools/lib/cli/progress/Bar.php) Z6vendor/wp-cli/php-cli-tools/lib/cli/notify/Spinner.php) Z8u3vendor/wp-cli/php-cli-tools/lib/cli/notify/Dots.phpd) Zdwz.vendor/wp-cli/php-cli-tools/lib/cli/Notify.php) Z,-vendor/wp-cli/php-cli-tools/lib/cli/Table.php) Z7),vendor/wp-cli/php-cli-tools/lib/cli/Tree.php) Z@0vendor/wp-cli/php-cli-tools/lib/cli/Progress.php ) Z a-vendor/wp-cli/php-cli-tools/lib/cli/Shell.php ) Z mŻ5vendor/wp-cli/php-cli-tools/lib/cli/tree/Renderer.phpO) ZOc"Ӷ5vendor/wp-cli/php-cli-tools/lib/cli/tree/Markdown.php) ZT魶2vendor/wp-cli/php-cli-tools/lib/cli/tree/Ascii.phpx) Zx95vendor/wp-cli/php-cli-tools/lib/cli/unicode/regex.phpb) Zbɶ9Ͷ.vendor/wp-cli/php-cli-tools/lib/cli/Colors.phpk!) Zk!y6)$vendor/wp-cli/php-cli-tools/test.phpi) Zi3M,vendor/wp-cli/php-cli-tools/http-console.php) Z>1vendor/wp-cli/rewrite-command/rewrite-command.php) Z5vendor/wp-cli/rewrite-command/src/Rewrite_Command.php,) Z,k5vendor/wp-cli/extension-command/extension-command.phpL) ZLݶ5vendor/wp-cli/extension-command/src/Theme_Command.phpZ) ZZZ =vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Theme.php) Z¸>vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Plugin.php) Z%JHvendor/wp-cli/extension-command/src/WP_CLI/DestructivePluginUpgrader.php) ZSGvendor/wp-cli/extension-command/src/WP_CLI/DestructiveThemeUpgrader.php) ŹFAvendor/wp-cli/extension-command/src/WP_CLI/CommandWithUpgrade.phpM) ZMrVo9vendor/wp-cli/extension-command/src/Theme_Mod_Command.phpX) ZX뺶6vendor/wp-cli/extension-command/src/Plugin_Command.php) ZE3vendor/wp-cli/checksum-command/checksum-command.php) Zo7vendor/wp-cli/checksum-command/src/Checksum_Command.php) Zp7vendor/wp-cli/mustangostang-spyc/includes/functions.php) Z:)vendor/wp-cli/mustangostang-spyc/Spyc.php) Zx.vendor/wp-cli/mustangostang-spyc/php4/5to4.php) ZuS])-vendor/wp-cli/mustangostang-spyc/src/Spyc.php˅) Z˅~6vendor/wp-cli/autoload-splitter/src/ComposerPlugin.php ) Z M9vendor/wp-cli/autoload-splitter/src/AutoloadGenerator.phpd3) Zd3-vendor/wp-cli/media-command/media-command.php) Z$1vendor/wp-cli/media-command/src/Media_Command.php p) Z pTQ+vendor/wp-cli/role-command/role-command.php) Z/vendor/wp-cli/role-command/src/Role_Command.php&) Z&47vendor/wp-cli/role-command/src/Capabilities_Command.phpu) ZuiY˶/vendor/wp-cli/import-command/import-command.php) Z03vendor/wp-cli/import-command/src/Import_Command.php6) Z6%F3vendor/wp-cli/language-command/language-command.php) Z᭸Dvendor/wp-cli/language-command/src/WP_CLI/CommandWithTranslation.phpJ3) ZJ37SBvendor/wp-cli/language-command/src/WP_CLI/LanguagePackUpgrader.php ) Z >-<vendor/wp-cli/language-command/src/Core_Language_Command.php) Z&'vendor/wp-cli/db-command/db-command.php) Zf3+vendor/wp-cli/db-command/src/DB_Command.php) Z 2c1vendor/wp-cli/package-command/package-command.php ) Z  f5vendor/wp-cli/package-command/src/Package_Command.php z) Z zLz3vendor/wp-cli/scaffold-command/scaffold-command.php) Z7vendor/wp-cli/scaffold-command/src/WP_CLI/Inflector.phpqB) ZqBr787vendor/wp-cli/scaffold-command/src/Scaffold_Command.phpz) Zz>̶/vendor/wp-cli/entity-command/entity-command.phpY) ZYFC95vendor/wp-cli/entity-command/src/Taxonomy_Command.php) ZB9vendor/wp-cli/entity-command/src/Comment_Meta_Command.php) Z4BW9vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/User.phpT) ZTXk9vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/Post.php) ZS<vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/Comment.php#) Z#9vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/Site.php) Z$ ;vendor/wp-cli/entity-command/src/WP_CLI/CommandWithMeta.phpI)) ZI)֨G?vendor/wp-cli/entity-command/src/WP_CLI/CommandWithDBObject.phph) ZhW <vendor/wp-cli/entity-command/src/WP_CLI/CommandWithTerms.phpU) ZU˶Jvendor/wp-cli/entity-command/src/WP_CLI/Entity/NonExistentKeyException.php) Zn8vendor/wp-cli/entity-command/src/WP_CLI/Entity/Utils.php}) Z}lRvendor/wp-cli/entity-command/src/WP_CLI/Entity/RecursiveDataStructureTraverser.php) ZKȶ3vendor/wp-cli/entity-command/src/Option_Command.php;) Z;6U6vendor/wp-cli/entity-command/src/Post_Type_Command.php_) Z_]r1vendor/wp-cli/entity-command/src/Term_Command.php6@) Z6@81vendor/wp-cli/entity-command/src/Menu_Command.php) Zx6vendor/wp-cli/entity-command/src/Post_Term_Command.php) Z4vendor/wp-cli/entity-command/src/Comment_Command.phpZ>) ZZ>:gf:vendor/wp-cli/entity-command/src/Menu_Location_Command.phpP) ZP^ׂ6vendor/wp-cli/entity-command/src/Menu_Item_Command.php2) Z2R6vendor/wp-cli/entity-command/src/Term_Meta_Command.php) ZPC6vendor/wp-cli/entity-command/src/Post_Meta_Command.phpp) Zp71vendor/wp-cli/entity-command/src/Site_Command.phpwQ) ZwQq9vendor/wp-cli/entity-command/src/Network_Meta_Command.php') Z')6vendor/wp-cli/entity-command/src/User_Meta_Command.php) Z99vendor/wp-cli/entity-command/src/User_Session_Command.php)) Z)}8vendor/wp-cli/entity-command/src/Site_Option_Command.php%) Z%2z6vendor/wp-cli/entity-command/src/User_Term_Command.php) ZGov1vendor/wp-cli/entity-command/src/User_Command.php x) Z x|)81vendor/wp-cli/entity-command/src/Post_Command.php=) Z=s7)-vendor/wp-cli/shell-command/shell-command.php) ZCC/vendor/wp-cli/shell-command/src/WP_CLI/REPL.php ) Z /`0B1vendor/wp-cli/shell-command/src/Shell_Command.php) Z⍗+vendor/wp-cli/cron-command/cron-command.phpZ) ZZMC_L5vendor/wp-cli/cron-command/src/Cron_Event_Command.php 3) Z 3[U/vendor/wp-cli/cron-command/src/Cron_Command.php ) Z H\ж8vendor/wp-cli/cron-command/src/Cron_Schedule_Command.phpi ) Zi r+vendor/wp-cli/core-command/core-command.php) ZǶ/vendor/wp-cli/core-command/src/Core_Command.phpޞ) Zޞ@Dvendor/wp-cli/core-command/src/WP_CLI/NonDestructiveCoreUpgrader.php3) Z3?׶6vendor/wp-cli/core-command/src/WP_CLI/CoreUpgrader.php ) Z gX/vendor/wp-cli/widget-command/widget-command.php) Z/4vendor/wp-cli/widget-command/src/Sidebar_Command.php) Z.œ3vendor/wp-cli/widget-command/src/Widget_Command.phpD) ZDSW</vendor/wp-cli/config-command/config-command.php) Z^3vendor/wp-cli/config-command/src/Config_Command.php.) Z.$D?vendor/wp-cli/search-replace-command/search-replace-command.php) Z!>EȶBvendor/wp-cli/search-replace-command/src/WP_CLI/SearchReplacer.php) Z-Cvendor/wp-cli/search-replace-command/src/Search_Replace_Command.phpT) ZTH-vendor/wp-cli/cache-command/cache-command.php) Zգ5vendor/wp-cli/cache-command/src/Transient_Command.php) Z@)}1vendor/wp-cli/cache-command/src/Cache_Command.php) Z# 9vendor/wp-cli/super-admin-command/super-admin-command.phpr) ZrCH=vendor/wp-cli/super-admin-command/src/Super_Admin_Command.php) Z$3w*vendor/wp-cli/export-command/functions.phpd) Zd==/vendor/wp-cli/export-command/export-command.php) ZN/78vendor/wp-cli/export-command/src/WP_Export_Exception.php>) Z>M5vendor/wp-cli/export-command/src/WP_Export_Oxymel.php) ZC3vendor/wp-cli/export-command/src/Export_Command.php.) Z.-8ж4vendor/wp-cli/export-command/src/WP_Export_Query.php,) Z,5N7vendor/wp-cli/export-command/src/WP_Export_Returner.php) Zq9vendor/wp-cli/export-command/src/WP_Post_IDs_Iterator.php) Zm˫<vendor/wp-cli/export-command/src/WP_Export_Stdout_Writer.php) Z<vendor/wp-cli/export-command/src/WP_Export_WXR_Formatter.php#) Z#W6]:vendor/wp-cli/export-command/src/WP_Iterator_Exception.php9) Z9w4vendor/wp-cli/export-command/src/WP_Map_Iterator.php2) Z2i:vendor/wp-cli/export-command/src/WP_Export_Base_Writer.php) Zaz<vendor/wp-cli/export-command/src/WP_Export_XML_Over_HTTP.php) Zq):vendor/wp-cli/export-command/src/WP_Export_File_Writer.php() Z(ζ=vendor/wp-cli/export-command/src/WP_Export_Term_Exception.phpC) ZCǶAvendor/wp-cli/export-command/src/WP_Export_Split_Files_Writer.phpA ) ZA dwʶ/vendor/wp-cli/server-command/server-command.php) Zպܶ3vendor/wp-cli/server-command/src/Server_Command.php ) Z jrh'vendor/wp-cli/server-command/router.php ) Z Jfeatures/bootstrap/support.php{) Z{~%features/bootstrap/FeatureContext.phpt}) Zt}Ӷfeatures/steps/when.phpb) Zb9{Mfeatures/steps/then.php) ZGgtafeatures/steps/given.php[) Z[hg)features/extra/no-mail.php|) Z|[(vendor/nb/oxymel/OxymelTest.php) ZBLVvendor/nb/oxymel/Oxymel.php)) Z)(rն%vendor/psr/log/Psr/Log/NullLogger.php) ZZf+vendor/psr/log/Psr/Log/LoggerAwareTrait.php) Zz%*vendor/psr/log/Psr/Log/LoggerInterface.php ) Z ?}/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)) Z)j 3vendor/psr/log/Psr/Log/InvalidArgumentException.php`) Z` X1)vendor/psr/log/Psr/Log/AbstractLogger.php ) Z Gl#vendor/psr/log/Psr/Log/LogLevel.phpP) ZP&vendor/psr/log/Psr/Log/LoggerTrait.php ) Z ý;vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php0) Z0K粶0vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.phpe) Ze)$4vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php) Zg5vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.phpP) ZPM 7@vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php8) Z8 )vendor/seld/phar-utils/src/Timestamps.php) Zׁ6&vendor/seld/cli-prompt/res/example.php') Z'I(vendor/seld/cli-prompt/src/CliPrompt.php ) Z -+*.vendor/symfony/console/Command/HelpCommand.php ) Z '*vendor/symfony/console/Command/Command.phpcP) ZcP~.vendor/symfony/console/Command/ListCommand.phpo ) Zo qX(vendor/symfony/console/ConsoleEvents.php7) Z7E2vendor/symfony/console/Question/ChoiceQuestion.php) Zֶ,vendor/symfony/console/Question/Question.phpS) ZSR'"8vendor/symfony/console/Question/ConfirmationQuestion.php) Z,vendor/symfony/console/Input/StringInput.php ) Z (2.vendor/symfony/console/Input/InputArgument.php ) Z  *vendor/symfony/console/Input/ArgvInput.phpt') Zt'퐶0vendor/symfony/console/Input/InputDefinition.php&3) Z&3+/vendor/symfony/console/Input/InputInterface.phpN) ZN(Γ4vendor/symfony/console/Input/InputAwareInterface.php^) Z^9Kh&vendor/symfony/console/Input/Input.php) Z݂Y+vendor/symfony/console/Input/ArrayInput.php ) Z s,vendor/symfony/console/Input/InputOption.php\) Z\k[&vendor/symfony/console/Application.php`) Z`d>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php ) Z 1 +C4vendor/symfony/console/Formatter/OutputFormatter.php) Z9vendor/symfony/console/Formatter/OutputFormatterStyle.php) ZD1=vendor/symfony/console/Formatter/OutputFormatterInterface.php) Z#7uBvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php[) Z[f@-vendor/symfony/console/Style/SymfonyStyle.php/) Z/]\,vendor/symfony/console/Style/OutputStyle.php0 ) Z0 B޿/vendor/symfony/console/Style/StyleInterface.php ) Z 3Z6vendor/symfony/console/Event/ConsoleTerminateEvent.php) Z{e4vendor/symfony/console/Event/ConsoleCommandEvent.php=) Z=%mf-vendor/symfony/console/Event/ConsoleEvent.php) Z 6vendor/symfony/console/Event/ConsoleExceptionEvent.php=) Z=Ŷ/vendor/symfony/console/Tester/CommandTester.php) Z2ə3vendor/symfony/console/Tester/ApplicationTester.php? ) Z? "W vendor/symfony/console/Shell.php) Z<vendor/symfony/console/Descriptor/ApplicationDescription.php) Z!g3vendor/symfony/console/Descriptor/XmlDescriptor.phpS%) ZS%Ja4vendor/symfony/console/Descriptor/TextDescriptor.phpk*) Zk* n8vendor/symfony/console/Descriptor/MarkdownDescriptor.php) Z_50vendor/symfony/console/Descriptor/Descriptor.php ) Z ɠ9vendor/symfony/console/Descriptor/DescriptorInterface.php) ZJZ0<4vendor/symfony/console/Descriptor/JsonDescriptor.php) Zwt/vendor/symfony/console/Logger/ConsoleLogger.php) ZCv-vendor/symfony/console/Helper/ProgressBar.phpF) ZF0vendor/symfony/console/Helper/QuestionHelper.php5) Z5{,ζ3vendor/symfony/console/Helper/ProgressIndicator.php!) Z!"1vendor/symfony/console/Helper/FormatterHelper.php ) Z "*.vendor/symfony/console/Helper/DialogHelper.phpD) ZD~o+vendor/symfony/console/Helper/HelperSet.php ) Z 4-vendor/symfony/console/Helper/TableHelper.php3) Z3I"2vendor/symfony/console/Helper/InputAwareHelper.php) Z˶0vendor/symfony/console/Helper/ProgressHelper.phpO1) ZO1Ҟ6/vendor/symfony/console/Helper/ProcessHelper.php) Zt'vendor/symfony/console/Helper/Table.phpI) ZIvbڶ2vendor/symfony/console/Helper/DescriptorHelper.php1 ) Z1 i0vendor/symfony/console/Helper/TableSeparator.phpE) ZEp|ն+vendor/symfony/console/Helper/TableCell.php) ZI(vendor/symfony/console/Helper/Helper.phpU ) ZU s7vendor/symfony/console/Helper/SymfonyQuestionHelper.php) Z歶,vendor/symfony/console/Helper/TableStyle.php) ZwU\1vendor/symfony/console/Helper/HelperInterface.php) Z6vendor/symfony/console/Helper/DebugFormatterHelper.phpV) ZVݔ.vendor/symfony/console/Output/StreamOutput.php ) Z 8vendor/symfony/console/Output/ConsoleOutputInterface.phpK) ZK0(vendor/symfony/console/Output/Output.php{) Z{mn1vendor/symfony/console/Output/OutputInterface.phpu ) Zu ʶ/vendor/symfony/console/Output/ConsoleOutput.php) ZB!ɶ,vendor/symfony/console/Output/NullOutput.phpn) ZntD0vendor/symfony/console/Output/BufferedOutput.phpk) Zk3;vendor/symfony/console/Exception/InvalidOptionException.php) Z;7vendor/symfony/console/Exception/ExceptionInterface.php) ZU3vendor/symfony/console/Exception/LogicException.php) ZSML5vendor/symfony/console/Exception/RuntimeException.php) Z*b=vendor/symfony/console/Exception/InvalidArgumentException.php) Zu i=vendor/symfony/console/Exception/CommandNotFoundException.php) Z(vendor/symfony/filesystem/Filesystem.php4c) Z4cb2)vendor/symfony/filesystem/LockHandler.php ) Z |73vendor/symfony/filesystem/Exception/IOException.php) ZёA:vendor/symfony/filesystem/Exception/ExceptionInterface.php) Zh<vendor/symfony/filesystem/Exception/IOExceptionInterface.php) Zk/=vendor/symfony/filesystem/Exception/FileNotFoundException.php) Z0z¶)vendor/symfony/process/ProcessBuilder.phps) Zs JS"vendor/symfony/process/Process.php3) Z3v@㮶.vendor/symfony/process/PhpExecutableFinder.php) Zl(.vendor/symfony/process/Pipes/AbstractPipes.phpe) Ze;̶-vendor/symfony/process/Pipes/WindowsPipes.php) Z0.*vendor/symfony/process/Pipes/UnixPipes.php) Z/vendor/symfony/process/Pipes/PipesInterface.phpt) Zt?_'vendor/symfony/process/ProcessUtils.php) Z }ڶ+vendor/symfony/process/ExecutableFinder.php' ) Z' 6l%vendor/symfony/process/PhpProcess.phpb ) Zb c77vendor/symfony/process/Exception/ExceptionInterface.php) Z<=vendor/symfony/process/Exception/ProcessTimedOutException.php{) Z{4;vendor/symfony/process/Exception/ProcessFailedException.php) ZP53vendor/symfony/process/Exception/LogicException.php) ZW5vendor/symfony/process/Exception/RuntimeException.php) Z>H=vendor/symfony/process/Exception/InvalidArgumentException.php) Z˅.vendor/justinrainbow/json-schema/demo/demo.php) ZbAvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php*) Z*[Kvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php ) Z H;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.phpv) Zvx$Cvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php;) Z;Tvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.phpr) ZrMRvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php) Z0Xvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php) Z?r@Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.phpj ) Zj [}x#Gvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.phpo) ZonDvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php") Z"v')Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php) ZIѶIvendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php) Z5|[=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.phpY ) ZY !޽nJvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php") Z"x&7Gvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php) ZDֶSvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php<) Z<p"0öNvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php%) Z%2Jvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php) ZnzTvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php) Z|sNvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php&) Z&ٌ]Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php) Z~GNvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.phpq) Zq` Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php) ZtePvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php) ZB2Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php") Z"ͥ#KPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php) ZLoPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php) ZFgcXvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php) Z Yvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php) Z~ն\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php) Zc>϶Fvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php> ) Z> W^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php) Z.miPvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpI) ZI%|]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpW) ZW%*)ֶTvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phpN) ZNݠNvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.phpa) Za`Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php\) Z\iRQvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php) ZEB*Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpJ) ZJ-Vvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.phpy) ZyWΖWvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpT) ZT:Svendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php) Z٣Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpQ) ZQJtemplates/man.mustache) ZI< templates/plugin-status.mustache) Zo[5templates/theme-status.mustachex) ZxǁgѶtemplates/versions.mustache) ZSU"templates/man-params.mustache) ZC9vendor/wp-cli/config-command/templates/wp-config.mustacheO ) ZO o9vendor/wp-cli/scaffold-command/templates/phpunit.xml.distA) ZAdA ?vendor/wp-cli/scaffold-command/templates/plugin-readme.mustache) Zd@B`Cvendor/wp-cli/scaffold-command/templates/plugin-distignore.mustache) Z \֥?vendor/wp-cli/scaffold-command/templates/plugin-travis.mustache) ZHeֶCvendor/wp-cli/scaffold-command/templates/taxonomy_extended.mustachef) ZfYDvendor/wp-cli/scaffold-command/templates/plugin-test-sample.mustache ) Z tU:vendor/wp-cli/scaffold-command/templates/taxonomy.mustache) ZLQbCvendor/wp-cli/scaffold-command/templates/theme-test-sample.mustache) Z<vendor/wp-cli/scaffold-command/templates/install-wp-tests.sh") Z"J];vendor/wp-cli/scaffold-command/templates/post_type.mustache) Z Avendor/wp-cli/scaffold-command/templates/plugin-packages.mustache) ZUBvendor/wp-cli/scaffold-command/templates/plugin-bootstrap.mustache.) Z.hŶ6vendor/wp-cli/scaffold-command/templates/.editorconfig) ZhѶ7vendor/wp-cli/scaffold-command/templates/phpcs.xml.dist ) Z :ɶ=vendor/wp-cli/scaffold-command/templates/child_theme.mustache) Z>vendor/wp-cli/scaffold-command/templates/theme-status.mustachex) ZxǁgѶDvendor/wp-cli/scaffold-command/templates/post_type_extended.mustache/) Z/IGehGvendor/wp-cli/scaffold-command/templates/child_theme_functions.mustache) ZR?vendor/wp-cli/scaffold-command/templates/plugin-circle.mustacheU) ZUBuAvendor/wp-cli/scaffold-command/templates/theme-bootstrap.mustache) ZV?vendor/wp-cli/scaffold-command/templates/plugin-gitlab.mustache) Zh;KuBvendor/wp-cli/scaffold-command/templates/plugin-gruntfile.mustachen) Zn.ƶBvendor/wp-cli/scaffold-command/templates/plugin-gitignore.mustacheH) ZHx48vendor/wp-cli/scaffold-command/templates/plugin.mustacheV) ZV8vendor/autoload.php) Z^ Įvendor/autoload_commands.php) Z(/vendor/autoload_framework.php) Z>(ci/behat-tags.php) Z& vendor/composer/composer/LICENSE,) Z,Vg1vendor/composer/composer/res/composer-schema.json_) Z_H<vendor/rmccue/requests/library/Requests/Transport/cacert.pem~) Z~a#^VERSION) Z|5cconfig ) && ! empty( $composer->config->{'vendor-dir'} ) ) { array_unshift( $vendor_paths, WP_CLI_ROOT . '/../../../' . $composer->config->{'vendor-dir'} ); } } return $vendor_paths; } // Using require() directly inside a class grants access to private methods to the loaded code function load_file( $path ) { require_once $path; } function load_command( $name ) { $path = WP_CLI_ROOT . "/php/commands/$name.php"; if ( is_readable( $path ) ) { include_once $path; } } /** * Like array_map(), except it returns a new iterator, instead of a modified array. * * Example: * * $arr = array('Football', 'Socker'); * * $it = iterator_map($arr, 'strtolower', function($val) { * return str_replace('foo', 'bar', $val); * }); * * foreach ( $it as $val ) { * var_dump($val); * } * * @param array|object Either a plain array or another iterator * @param callback The function to apply to an element * @return object An iterator that applies the given callback(s) */ function iterator_map( $it, $fn ) { if ( is_array( $it ) ) { $it = new \ArrayIterator( $it ); } if ( ! method_exists( $it, 'add_transform' ) ) { $it = new Transform( $it ); } foreach ( array_slice( func_get_args(), 1 ) as $fn ) { $it->add_transform( $fn ); } return $it; } /** * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true * @param string|array The files (or file) to search for * @param string|null The directory to start searching from; defaults to CWD * @param callable Function which is passed the current dir each time a directory level is traversed * @return null|string Null if the file was not found */ function find_file_upward( $files, $dir = null, $stop_check = null ) { $files = (array) $files; if ( is_null( $dir ) ) { $dir = getcwd(); } while ( is_readable( $dir ) ) { // Stop walking up when the supplied callable returns true being passed the $dir if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) { return null; } foreach ( $files as $file ) { $path = $dir . DIRECTORY_SEPARATOR . $file; if ( file_exists( $path ) ) { return $path; } } $parent_dir = dirname( $dir ); if ( empty( $parent_dir ) || $parent_dir === $dir ) { break; } $dir = $parent_dir; } return null; } function is_path_absolute( $path ) { // Windows if ( isset( $path[1] ) && ':' === $path[1] ) { return true; } return '/' === $path[0]; } /** * Composes positional arguments into a command string. * * @param array * @return string */ function args_to_str( $args ) { return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) ); } /** * Composes associative arguments into a command string. * * @param array * @return string */ function assoc_args_to_str( $assoc_args ) { $str = ''; foreach ( $assoc_args as $key => $value ) { if ( true === $value ) { $str .= " --$key"; } elseif ( is_array( $value ) ) { foreach ( $value as $_ => $v ) { $str .= assoc_args_to_str( array( $key => $v, ) ); } } else { $str .= " --$key=" . escapeshellarg( $value ); } } return $str; } /** * Given a template string and an arbitrary number of arguments, * returns the final command, with the parameters escaped. */ function esc_cmd( $cmd ) { if ( func_num_args() < 2 ) { trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING ); } $args = func_get_args(); $cmd = array_shift( $args ); return vsprintf( $cmd, array_map( 'escapeshellarg', $args ) ); } function locate_wp_config() { static $path; if ( null === $path ) { $path = false; if ( file_exists( ABSPATH . 'wp-config.php' ) ) { $path = ABSPATH . 'wp-config.php'; } elseif ( file_exists( ABSPATH . '../wp-config.php' ) && ! file_exists( ABSPATH . '/../wp-settings.php' ) ) { $path = ABSPATH . '../wp-config.php'; } if ( $path ) { $path = realpath( $path ); } } return $path; } function wp_version_compare( $since, $operator ) { $wp_version = str_replace( '-src', '', $GLOBALS['wp_version'] ); $since = str_replace( '-src', '', $since ); return version_compare( $wp_version, $since, $operator ); } /** * Render a collection of items as an ASCII table, JSON, CSV, YAML, list of ids, or count. * * Given a collection of items with a consistent data structure: * * ``` * $items = array( * array( * 'key' => 'foo', * 'value' => 'bar', * ) * ); * ``` * * Render `$items` as an ASCII table: * * ``` * WP_CLI\Utils\format_items( 'table', $items, array( 'key', 'value' ) ); * * # +-----+-------+ * # | key | value | * # +-----+-------+ * # | foo | bar | * # +-----+-------+ * ``` * * Or render `$items` as YAML: * * ``` * WP_CLI\Utils\format_items( 'yaml', $items, array( 'key', 'value' ) ); * * # --- * # - * # key: foo * # value: bar * ``` * * @access public * @category Output * * @param string $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count' * @param array $items An array of items to output. * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list. * @return null */ function format_items( $format, $items, $fields ) { $assoc_args = compact( 'format', 'fields' ); $formatter = new \WP_CLI\Formatter( $assoc_args ); $formatter->display_items( $items ); } /** * Write data as CSV to a given file. * * @access public * * @param resource $fd File descriptor * @param array $rows Array of rows to output * @param array $headers List of CSV columns (optional) */ function write_csv( $fd, $rows, $headers = array() ) { if ( ! empty( $headers ) ) { fputcsv( $fd, $headers ); } foreach ( $rows as $row ) { if ( ! empty( $headers ) ) { $row = pick_fields( $row, $headers ); } fputcsv( $fd, array_values( $row ) ); } } /** * Pick fields from an associative array or object. * * @param array|object Associative array or object to pick fields from * @param array List of fields to pick * @return array */ function pick_fields( $item, $fields ) { $item = (object) $item; $values = array(); foreach ( $fields as $field ) { $values[ $field ] = isset( $item->$field ) ? $item->$field : null; } return $values; } /** * Launch system's $EDITOR for the user to edit some text. * * @access public * @category Input * * @param string $content Some form of text to edit (e.g. post content) * @return string|bool Edited text, if file is saved from editor; false, if no change to file. */ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { check_proc_available( 'launch_editor_for_input' ); $tmpdir = get_temp_dir(); do { $tmpfile = basename( $filename ); $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); $tmpfile .= '-' . substr( md5( mt_rand() ), 0, 6 ); $tmpfile = $tmpdir . $tmpfile . '.tmp'; $fp = fopen( $tmpfile, 'xb' ); if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { $tmpfile = ''; continue; } if ( $fp ) { fclose( $fp ); } } while ( ! $tmpfile ); if ( ! $tmpfile ) { \WP_CLI::error( 'Error creating temporary file.' ); } $output = ''; file_put_contents( $tmpfile, $input ); $editor = getenv( 'EDITOR' ); if ( ! $editor ) { $editor = 'vi'; if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) ) { $editor = 'notepad'; } } $descriptorspec = array( STDIN, STDOUT, STDERR ); $process = proc_open( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes ); $r = proc_close( $process ); if ( $r ) { exit( $r ); } $output = file_get_contents( $tmpfile ); unlink( $tmpfile ); if ( $output === $input ) { return false; } return $output; } /** * @param string MySQL host string, as defined in wp-config.php * @return array */ function mysql_host_to_cli_args( $raw_host ) { $assoc_args = array(); $host_parts = explode( ':', $raw_host ); if ( count( $host_parts ) == 2 ) { list( $assoc_args['host'], $extra ) = $host_parts; $extra = trim( $extra ); if ( is_numeric( $extra ) ) { $assoc_args['port'] = (int) $extra; $assoc_args['protocol'] = 'tcp'; } elseif ( '' !== $extra ) { $assoc_args['socket'] = $extra; } } else { $assoc_args['host'] = $raw_host; } return $assoc_args; } function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { check_proc_available( 'run_mysql_command' ); if ( ! $descriptors ) { $descriptors = array( STDIN, STDOUT, STDERR ); } if ( isset( $assoc_args['host'] ) ) { //@codingStandardsIgnoreStart $assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); //@codingStandardsIgnoreEnd } $pass = $assoc_args['pass']; unset( $assoc_args['pass'] ); $old_pass = getenv( 'MYSQL_PWD' ); putenv( 'MYSQL_PWD=' . $pass ); $final_cmd = force_env_on_nix_systems( $cmd ) . assoc_args_to_str( $assoc_args ); $proc = proc_open( $final_cmd, $descriptors, $pipes ); if ( ! $proc ) { exit( 1 ); } $r = proc_close( $proc ); putenv( 'MYSQL_PWD=' . $old_pass ); if ( $r ) { exit( $r ); } } /** * Render PHP or other types of files using Mustache templates. * * IMPORTANT: Automatic HTML escaping is disabled! */ function mustache_render( $template_name, $data = array() ) { if ( ! file_exists( $template_name ) ) { $template_name = WP_CLI_ROOT . "/templates/$template_name"; } $template = file_get_contents( $template_name ); $m = new \Mustache_Engine( array( 'escape' => function ( $val ) { return $val; }, ) ); return $m->render( $template, $data ); } /** * Create a progress bar to display percent completion of a given operation. * * Progress bar is written to STDOUT, and disabled when command is piped. Progress * advances with `$progress->tick()`, and completes with `$progress->finish()`. * Process bar also indicates elapsed time and expected total time. * * ``` * # `wp user generate` ticks progress bar each time a new user is created. * # * # $ wp user generate --count=500 * # Generating users 22 % [=======> ] 0:05 / 0:23 * * $progress = \WP_CLI\Utils\make_progress_bar( 'Generating users', $count ); * for ( $i = 0; $i < $count; $i++ ) { * // uses wp_insert_user() to insert the user * $progress->tick(); * } * $progress->finish(); * ``` * * @access public * @category Output * * @param string $message Text to display before the progress bar. * @param integer $count Total number of ticks to be performed. * @return cli\progress\Bar|WP_CLI\NoOp */ function make_progress_bar( $message, $count ) { if ( \cli\Shell::isPiped() ) { return new \WP_CLI\NoOp; } return new \cli\progress\Bar( $message, $count ); } function parse_url( $url ) { $url_parts = \parse_url( $url ); if ( ! isset( $url_parts['scheme'] ) ) { $url_parts = parse_url( 'http://' . $url ); } return $url_parts; } /** * Check if we're running in a Windows environment (cmd.exe). * * @return bool */ function is_windows() { return false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN'; } /** * Replace magic constants in some PHP source code. * * @param string $source The PHP code to manipulate. * @param string $path The path to use instead of the magic constants */ function replace_path_consts( $source, $path ) { $replacements = array( '__FILE__' => "'$path'", '__DIR__' => "'" . dirname( $path ) . "'", ); $old = array_keys( $replacements ); $new = array_values( $replacements ); return str_replace( $old, $new, $source ); } /** * Make a HTTP request to a remote URL. * * Wraps the Requests HTTP library to ensure every request includes a cert. * * ``` * # `wp core download` verifies the hash for a downloaded WordPress archive * * $md5_response = Utils\http_request( 'GET', $download_url . '.md5' ); * if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { * WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$response->status_code})" ); * } * ``` * * @access public * * @param string $method HTTP method (GET, POST, DELETE, etc.) * @param string $url URL to make the HTTP request to. * @param array $headers Add specific headers to the request. * @param array $options * @return object */ function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { $cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; if ( inside_phar() ) { // cURL can't read Phar archives $options['verify'] = extract_from_phar( WP_CLI_VENDOR_DIR . $cert_path ); } else { foreach ( get_vendor_paths() as $vendor_path ) { if ( file_exists( $vendor_path . $cert_path ) ) { $options['verify'] = $vendor_path . $cert_path; break; } } if ( empty( $options['verify'] ) ) { $error_msg = 'Cannot find SSL certificate.'; if ( $halt_on_error ) { WP_CLI::error( $error_msg ); } throw new \RuntimeException( $error_msg ); } } try { return \Requests::request( $url, $headers, $data, $method, $options ); } catch ( \Requests_Exception $ex ) { // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. if ( 'curlerror' !== $ex->getType() || ! in_array( curl_errno( $ex->getData() ), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ), true ) ) { $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ); if ( $halt_on_error ) { WP_CLI::error( $error_msg ); } throw new \RuntimeException( $error_msg, null, $ex ); } // Handle SSL certificate issues gracefully \WP_CLI::warning( sprintf( "Re-trying without verify after failing to get verified url '%s' %s.", $url, $ex->getMessage() ) ); $options['verify'] = false; try { return \Requests::request( $url, $headers, $data, $method, $options ); } catch ( \Requests_Exception $ex ) { $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ); if ( $halt_on_error ) { WP_CLI::error( $error_msg ); } throw new \RuntimeException( $error_msg, null, $ex ); } } } /** * Increments a version string using the "x.y.z-pre" format * * Can increment the major, minor or patch number by one * If $new_version == "same" the version string is not changed * If $new_version is not a known keyword, it will be used as the new version string directly * * @param string $current_version * @param string $new_version * @return string */ function increment_version( $current_version, $new_version ) { // split version assuming the format is x.y.z-pre $current_version = explode( '-', $current_version, 2 ); $current_version[0] = explode( '.', $current_version[0] ); switch ( $new_version ) { case 'same': // do nothing break; case 'patch': $current_version[0][2]++; $current_version = array( $current_version[0] ); // drop possible pre-release info break; case 'minor': $current_version[0][1]++; $current_version[0][2] = 0; $current_version = array( $current_version[0] ); // drop possible pre-release info break; case 'major': $current_version[0][0]++; $current_version[0][1] = 0; $current_version[0][2] = 0; $current_version = array( $current_version[0] ); // drop possible pre-release info break; default: // not a keyword $current_version = array( array( $new_version ) ); break; } // reconstruct version string $current_version[0] = implode( '.', $current_version[0] ); $current_version = implode( '-', $current_version ); return $current_version; } /** * Compare two version strings to get the named semantic version. * * @access public * * @param string $new_version * @param string $original_version * @return string $name 'major', 'minor', 'patch' */ function get_named_sem_ver( $new_version, $original_version ) { if ( ! Comparator::greaterThan( $new_version, $original_version ) ) { return ''; } $parts = explode( '-', $original_version ); $bits = explode( '.', $parts[0] ); $major = $bits[0]; if ( isset( $bits[1] ) ) { $minor = $bits[1]; } if ( isset( $bits[2] ) ) { $patch = $bits[2]; } if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { return 'patch'; } if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { return 'minor'; } return 'major'; } /** * Return the flag value or, if it's not set, the $default value. * * Because flags can be negated (e.g. --no-quiet to negate --quiet), this * function provides a safer alternative to using * `isset( $assoc_args['quiet'] )` or similar. * * @access public * @category Input * * @param array $assoc_args Arguments array. * @param string $flag Flag to get the value. * @param mixed $default Default value for the flag. Default: NULL * @return mixed */ function get_flag_value( $assoc_args, $flag, $default = null ) { return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; } /** * Get the home directory. * * @access public * @category System * * @return string */ function get_home_dir() { $home = getenv( 'HOME' ); if ( ! $home ) { // In Windows $HOME may not be defined $home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); } return rtrim( $home, '/\\' ); } /** * Appends a trailing slash. * * @access public * @category System * * @param string $string What to add the trailing slash to. * @return string String with trailing slash added. */ function trailingslashit( $string ) { return rtrim( $string, '/\\' ) . '/'; } /** * Get the system's temp directory. Warns user if it isn't writable. * * @access public * @category System * * @return string */ function get_temp_dir() { static $temp = ''; if ( $temp ) { return $temp; } $temp = '/tmp/'; // `sys_get_temp_dir()` introduced PHP 5.2.1. if ( $try = sys_get_temp_dir() ) { $temp = trailingslashit( $try ); } elseif ( $try = ini_get( 'upload_tmp_dir' ) ) { $temp = trailingslashit( $try ); } if ( ! is_writable( $temp ) ) { \WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); } return $temp; } /** * Parse a SSH url for its host, port, and path. * * Similar to parse_url(), but adds support for defined SSH aliases. * * ``` * host OR host/path/to/wordpress OR host:port/path/to/wordpress * ``` * * @access public * * @return mixed */ function parse_ssh_url( $url, $component = -1 ) { preg_match( '#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches ); $bits = array(); foreach ( array( 2 => 'scheme', 4 => 'user', 5 => 'host', 7 => 'port', 8 => 'path', ) as $i => $key ) { if ( ! empty( $matches[ $i ] ) ) { $bits[ $key ] = $matches[ $i ]; } } switch ( $component ) { case PHP_URL_SCHEME: return isset( $bits['scheme'] ) ? $bits['scheme'] : null; case PHP_URL_USER: return isset( $bits['user'] ) ? $bits['user'] : null; case PHP_URL_HOST: return isset( $bits['host'] ) ? $bits['host'] : null; case PHP_URL_PATH: return isset( $bits['path'] ) ? $bits['path'] : null; case PHP_URL_PORT: return isset( $bits['port'] ) ? $bits['port'] : null; default: return $bits; } } /** * Report the results of the same operation against multiple resources. * * @access public * @category Input * * @param string $noun Resource being affected (e.g. plugin) * @param string $verb Type of action happening to the noun (e.g. activate) * @param integer $total Total number of resource being affected. * @param integer $successes Number of successful operations. * @param integer $failures Number of failures. * @param null|integer $skips Optional. Number of skipped operations. Default null (don't show skips). */ function report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips = null ) { $plural_noun = $noun . 's'; $past_tense_verb = past_tense_verb( $verb ); $past_tense_verb_upper = ucfirst( $past_tense_verb ); if ( $failures ) { $failed_skipped_message = null === $skips ? '' : " ({$failures} failed" . ( $skips ? ", {$skips} skipped" : '' ) . ')'; if ( $successes ) { WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}{$failed_skipped_message}." ); } else { WP_CLI::error( "No {$plural_noun} {$past_tense_verb}{$failed_skipped_message}." ); } } else { $skipped_message = $skips ? " ({$skips} skipped)" : ''; if ( $successes || $skips ) { WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}{$skipped_message}." ); } else { $message = $total > 1 ? ucfirst( $plural_noun ) : ucfirst( $noun ); WP_CLI::success( "{$message} already {$past_tense_verb}." ); } } } /** * Parse a string of command line arguments into an $argv-esqe variable. * * @access public * @category Input * * @param string $arguments * @return array */ function parse_str_to_argv( $arguments ) { preg_match_all( '/(?<=^|\s)([\'"]?)(.+?)(? 'check-update', 'clear' => 'flush', 'decrement' => 'decr', 'del' => 'delete', 'directory' => 'dir', 'exec' => 'eval', 'exec-file' => 'eval-file', 'increment' => 'incr', 'language' => 'locale', 'lang' => 'locale', 'new' => 'create', 'number' => 'count', 'remove' => 'delete', 'regen' => 'regenerate', 'rep' => 'replace', 'repl' => 'replace', 'v' => 'version', ); if ( array_key_exists( $target, $suggestion_map ) ) { return $suggestion_map[ $target ]; } if ( empty( $options ) ) { return ''; } foreach ( $options as $option ) { $distance = levenshtein( $option, $target ); $levenshtein[ $option ] = $distance; } // Sort known command strings by distance to user entry. asort( $levenshtein ); // Fetch the closest command string. reset( $levenshtein ); $suggestion = key( $levenshtein ); // Only return a suggestion if below a given threshold. return $levenshtein[ $suggestion ] <= $threshold && $suggestion !== $target ? (string) $suggestion : ''; } /** * Get a Phar-safe version of a path. * * For paths inside a Phar, this strips the outer filesystem's location to * reduce the path to what it needs to be within the Phar archive. * * Use the __FILE__ or __DIR__ constants as a starting point. * * @param string $path An absolute path that might be within a Phar. * * @return string A Phar-safe version of the path. */ function phar_safe_path( $path ) { if ( ! inside_phar() ) { return $path; } return str_replace( PHAR_STREAM_PREFIX . WP_CLI_PHAR_PATH . '/', PHAR_STREAM_PREFIX, $path ); } /** * Check whether a given Command object is part of the bundled set of * commands. * * This function accepts both a fully qualified class name as a string as * well as an object that extends `WP_CLI\Dispatcher\CompositeCommand`. * * @param \WP_CLI\Dispatcher\CompositeCommand|string $command * * @return bool */ function is_bundled_command( $command ) { static $classes; if ( null === $classes ) { $classes = array(); $class_map = WP_CLI_VENDOR_DIR . '/composer/autoload_commands_classmap.php'; if ( file_exists( WP_CLI_VENDOR_DIR . '/composer/' ) ) { $classes = include $class_map; } } if ( is_object( $command ) ) { $command = get_class( $command ); } return is_string( $command ) ? array_key_exists( $command, $classes ) : false; } /** * Maybe prefix command string with "/usr/bin/env". * Removes (if there) if Windows, adds (if not there) if not. * * @param string $command * * @return string */ function force_env_on_nix_systems( $command ) { $env_prefix = '/usr/bin/env '; $env_prefix_len = strlen( $env_prefix ); if ( is_windows() ) { if ( 0 === strncmp( $command, $env_prefix, $env_prefix_len ) ) { $command = substr( $command, $env_prefix_len ); } } else { if ( 0 !== strncmp( $command, $env_prefix, $env_prefix_len ) ) { $command = $env_prefix . $command; } } return $command; } /** * Check that `proc_open()` and `proc_close()` haven't been disabled. * * @param string $context Optional. If set will appear in error message. Default null. * @param bool $return Optional. If set will return false rather than error out. Default false. * * @return bool */ function check_proc_available( $context = null, $return = false ) { if ( ! function_exists( 'proc_open' ) || ! function_exists( 'proc_close' ) ) { if ( $return ) { return false; } $msg = 'The PHP functions `proc_open()` and/or `proc_close()` are disabled. Please check your PHP ini directive `disable_functions` or suhosin settings.'; if ( $context ) { WP_CLI::error( sprintf( "Cannot do '%s': %s", $context, $msg ) ); } else { WP_CLI::error( $msg ); } } return true; } /** * Returns past tense of verb, with limited accuracy. Only regular verbs catered for, apart from "reset". * * @param string $verb Verb to return past tense of. * * @return string */ function past_tense_verb( $verb ) { static $irregular = array( 'reset' => 'reset', ); if ( isset( $irregular[ $verb ] ) ) { return $irregular[ $verb ]; } $last = substr( $verb, -1 ); if ( 'e' === $last ) { $verb = substr( $verb, 0, -1 ); } elseif ( 'y' === $last && ! preg_match( '/[aeiou]y$/', $verb ) ) { $verb = substr( $verb, 0, -1 ) . 'i'; } elseif ( preg_match( '/^[^aeiou]*[aeiou][^aeiouhwxy]$/', $verb ) ) { // Rule of thumb that most (all?) one-voweled regular verbs ending in vowel + consonant (excluding "h", "w", "x", "y") double their final consonant - misses many cases (eg "submit"). $verb .= $last; } return $verb . 'ed'; } clean(); } ); } } return $cache; } /** * Set the context in which WP-CLI should be run */ public static function set_url( $url ) { WP_CLI::debug( 'Set URL: ' . $url, 'bootstrap' ); $url_parts = Utils\parse_url( $url ); self::set_url_params( $url_parts ); } private static function set_url_params( $url_parts ) { $f = function( $key ) use ( $url_parts ) { return \WP_CLI\Utils\get_flag_value( $url_parts, $key, '' ); }; if ( isset( $url_parts['host'] ) ) { if ( isset( $url_parts['scheme'] ) && 'https' === strtolower( $url_parts['scheme'] ) ) { $_SERVER['HTTPS'] = 'on'; } $_SERVER['HTTP_HOST'] = $url_parts['host']; if ( isset( $url_parts['port'] ) ) { $_SERVER['HTTP_HOST'] .= ':' . $url_parts['port']; } $_SERVER['SERVER_NAME'] = $url_parts['host']; } $_SERVER['REQUEST_URI'] = $f( 'path' ) . ( isset( $url_parts['query'] ) ? '?' . $url_parts['query'] : '' ); $_SERVER['SERVER_PORT'] = \WP_CLI\Utils\get_flag_value( $url_parts, 'port', '80' ); $_SERVER['QUERY_STRING'] = $f( 'query' ); } /** * @return WpHttpCacheManager */ public static function get_http_cache_manager() { static $http_cacher; if ( ! $http_cacher ) { $http_cacher = new WpHttpCacheManager( self::get_cache() ); } return $http_cacher; } /** * Colorize a string for output. * * Yes, you too can change the color of command line text. For instance, * here's how `WP_CLI::success()` colorizes "Success: " * * ``` * WP_CLI::colorize( "%GSuccess:%n " ) * ``` * * Uses `\cli\Colors::colorize()` to transform color tokens to display * settings. Choose from the following tokens (and note 'reset'): * * * %y => ['color' => 'yellow'], * * %g => ['color' => 'green'], * * %b => ['color' => 'blue'], * * %r => ['color' => 'red'], * * %p => ['color' => 'magenta'], * * %m => ['color' => 'magenta'], * * %c => ['color' => 'cyan'], * * %w => ['color' => 'grey'], * * %k => ['color' => 'black'], * * %n => ['color' => 'reset'], * * %Y => ['color' => 'yellow', 'style' => 'bright'], * * %G => ['color' => 'green', 'style' => 'bright'], * * %B => ['color' => 'blue', 'style' => 'bright'], * * %R => ['color' => 'red', 'style' => 'bright'], * * %P => ['color' => 'magenta', 'style' => 'bright'], * * %M => ['color' => 'magenta', 'style' => 'bright'], * * %C => ['color' => 'cyan', 'style' => 'bright'], * * %W => ['color' => 'grey', 'style' => 'bright'], * * %K => ['color' => 'black', 'style' => 'bright'], * * %N => ['color' => 'reset', 'style' => 'bright'], * * %3 => ['background' => 'yellow'], * * %2 => ['background' => 'green'], * * %4 => ['background' => 'blue'], * * %1 => ['background' => 'red'], * * %5 => ['background' => 'magenta'], * * %6 => ['background' => 'cyan'], * * %7 => ['background' => 'grey'], * * %0 => ['background' => 'black'], * * %F => ['style' => 'blink'], * * %U => ['style' => 'underline'], * * %8 => ['style' => 'inverse'], * * %9 => ['style' => 'bright'], * * %_ => ['style' => 'bright'] * * @access public * @category Output * * @param string $string String to colorize for output, with color tokens. * @return string Colorized string. */ public static function colorize( $string ) { return \cli\Colors::colorize( $string, self::get_runner()->in_color() ); } /** * Schedule a callback to be executed at a certain point. * * Hooks conceptually are very similar to WordPress actions. WP-CLI hooks * are typically called before WordPress is loaded. * * WP-CLI hooks include: * * * `before_add_command:` - Before the command is added. * * `after_add_command:` - After the command was added. * * `before_invoke:` - Just before a command is invoked. * * `after_invoke:` - Just after a command is invoked. * * `find_command_to_run_pre` - Just before WP-CLI finds the command to run. * * `before_wp_load` - Just before the WP load process begins. * * `before_wp_config_load` - After wp-config.php has been located. * * `after_wp_config_load` - After wp-config.php has been loaded into scope. * * `after_wp_load` - Just after the WP load process has completed. * * WP-CLI commands can create their own hooks with `WP_CLI::do_hook()`. * * If additional arguments are passed through the `WP_CLI::do_hook()` call, * these will be passed on to the callback provided by `WP_CLI::add_hook()`. * * ``` * # `wp network meta` confirms command is executing in multisite context. * WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array( * 'before_invoke' => function () { * if ( !is_multisite() ) { * WP_CLI::error( 'This is not a multisite install.' ); * } * } * ) ); * ``` * * @access public * @category Registration * * @param string $when Identifier for the hook. * @param mixed $callback Callback to execute when hook is called. * @return null */ public static function add_hook( $when, $callback ) { if ( array_key_exists( $when, self::$hooks_passed ) ) { call_user_func_array( $callback, (array) self::$hooks_passed[ $when ] ); } self::$hooks[ $when ][] = $callback; } /** * Execute callbacks registered to a given hook. * * See `WP_CLI::add_hook()` for details on WP-CLI's internal hook system. * Commands can provide and call their own hooks. * * @access public * @category Registration * * @param string $when Identifier for the hook. * @param mixed ... Optional. Arguments that will be passed onto the * callback provided by `WP_CLI::add_hook()`. * @return null */ public static function do_hook( $when ) { $args = func_num_args() > 1 ? array_slice( func_get_args(), 1 ) : array(); self::$hooks_passed[ $when ] = $args; if ( ! isset( self::$hooks[ $when ] ) ) { return; } foreach ( self::$hooks[ $when ] as $callback ) { call_user_func_array( $callback, $args ); } } /** * Add a callback to a WordPress action or filter. * * `add_action()` without needing access to `add_action()`. If WordPress is * already loaded though, you should use `add_action()` (and `add_filter()`) * instead. * * @access public * @category Registration * * @param string $tag Named WordPress action or filter. * @param mixed $function_to_add Callable to execute when the action or filter is evaluated. * @param integer $priority Priority to add the callback as. * @param integer $accepted_args Number of arguments to pass to callback. * @return true */ public static function add_wp_hook( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { global $wp_filter, $merged_filters; if ( function_exists( 'add_filter' ) ) { add_filter( $tag, $function_to_add, $priority, $accepted_args ); } else { $idx = self::wp_hook_build_unique_id( $tag, $function_to_add, $priority ); $wp_filter[ $tag ][ $priority ][ $idx ] = array( 'function' => $function_to_add, 'accepted_args' => $accepted_args, ); unset( $merged_filters[ $tag ] ); } return true; } /** * Build Unique ID for storage and retrieval. * * Essentially _wp_filter_build_unique_id() without needing access to _wp_filter_build_unique_id() */ private static function wp_hook_build_unique_id( $tag, $function, $priority ) { global $wp_filter; static $filter_id_count = 0; if ( is_string( $function ) ) { return $function; } if ( is_object( $function ) ) { // Closures are currently implemented as objects $function = array( $function, '' ); } else { $function = (array) $function; } if ( is_object( $function[0] ) ) { // Object Class Calling if ( function_exists( 'spl_object_hash' ) ) { return spl_object_hash( $function[0] ) . $function[1]; } $obj_idx = get_class( $function[0] ) . $function[1]; if ( ! isset( $function[0]->wp_filter_id ) ) { if ( false === $priority ) { return false; } $obj_idx .= isset( $wp_filter[ $tag ][ $priority ] ) ? count( (array) $wp_filter[ $tag ][ $priority ] ) : $filter_id_count; $function[0]->wp_filter_id = $filter_id_count; ++$filter_id_count; } else { $obj_idx .= $function[0]->wp_filter_id; } return $obj_idx; } if ( is_string( $function[0] ) ) { // Static Calling return $function[0] . '::' . $function[1]; } } /** * Register a command to WP-CLI. * * WP-CLI supports using any callable class, function, or closure as a * command. `WP_CLI::add_command()` is used for both internal and * third-party command registration. * * Command arguments are parsed from PHPDoc by default, but also can be * supplied as an optional third argument during registration. * * ``` * # Register a custom 'foo' command to output a supplied positional param. * # * # $ wp foo bar --append=qux * # Success: bar qux * * /** * * My awesome closure command * * * * * * : An awesome message to display * * * * --append= * * : An awesome message to append to the original message. * * * * @when before_wp_load * *\/ * $foo = function( $args, $assoc_args ) { * WP_CLI::success( $args[0] . ' ' . $assoc_args['append'] ); * }; * WP_CLI::add_command( 'foo', $foo ); * ``` * * @access public * @category Registration * * @param string $name Name for the command (e.g. "post list" or "site empty"). * @param callable $callable Command implementation as a class, function or closure. * @param array $args { * Optional. An associative array with additional registration parameters. * * @type callable $before_invoke Callback to execute before invoking the command. * @type callable $after_invoke Callback to execute after invoking the command. * @type string $short_desc Short description (80 char or less) for the command. * @type string $synopsis The synopsis for the command (string or array). * @type string $when Execute callback on a named WP-CLI hook (e.g. before_wp_load). * @type bool $is_deferred Whether the command addition had already been deferred. * } * @return true True on success, false if deferred, hard error if registration failed. */ public static function add_command( $name, $callable, $args = array() ) { // Bail immediately if the WP-CLI executable has not been run. if ( ! defined( 'WP_CLI' ) ) { return false; } $valid = false; if ( is_callable( $callable ) ) { $valid = true; } elseif ( is_string( $callable ) && class_exists( (string) $callable ) ) { $valid = true; } elseif ( is_object( $callable ) ) { $valid = true; } if ( ! $valid ) { if ( is_array( $callable ) ) { $callable[0] = is_object( $callable[0] ) ? get_class( $callable[0] ) : $callable[0]; $callable = array( $callable[0], $callable[1] ); } WP_CLI::error( sprintf( 'Callable %s does not exist, and cannot be registered as `wp %s`.', json_encode( $callable ), $name ) ); } $addition = new Dispatcher\CommandAddition(); self::do_hook( "before_add_command:{$name}", $addition ); if ( $addition->was_aborted() ) { WP_CLI::warning( "Aborting the addition of the command '{$name}' with reason: {$addition->get_reason()}." ); return false; } foreach ( array( 'before_invoke', 'after_invoke' ) as $when ) { if ( isset( $args[ $when ] ) ) { self::add_hook( "{$when}:{$name}", $args[ $when ] ); } } $path = preg_split( '/\s+/', $name ); $leaf_name = array_pop( $path ); $full_path = $path; $command = self::get_root_command(); while ( ! empty( $path ) ) { $subcommand_name = $path[0]; $parent = implode( ' ', $path ); $subcommand = $command->find_subcommand( $path ); // Parent not found. Defer addition or create an empty container as // needed. if ( ! $subcommand ) { if ( isset( $args['is_deferred'] ) && $args['is_deferred'] ) { $subcommand = new Dispatcher\CompositeCommand( $command, $subcommand_name, new \WP_CLI\DocParser( '' ) ); $command->add_subcommand( $subcommand_name, $subcommand ); } else { self::defer_command_addition( $name, $parent, $callable, $args ); return false; } } $command = $subcommand; } $leaf_command = Dispatcher\CommandFactory::create( $leaf_name, $callable, $command ); if ( ! $command->can_have_subcommands() ) { throw new Exception( sprintf( "'%s' can't have subcommands.", implode( ' ' , Dispatcher\get_path( $command ) ) ) ); } if ( isset( $args['shortdesc'] ) ) { $leaf_command->set_shortdesc( $args['shortdesc'] ); } if ( isset( $args['synopsis'] ) ) { if ( is_string( $args['synopsis'] ) ) { $leaf_command->set_synopsis( $args['synopsis'] ); } elseif ( is_array( $args['synopsis'] ) ) { $synopsis = \WP_CLI\SynopsisParser::render( $args['synopsis'] ); $leaf_command->set_synopsis( $synopsis ); $long_desc = ''; $bits = explode( ' ', $synopsis ); foreach ( $args['synopsis'] as $key => $arg ) { $long_desc .= $bits[ $key ] . PHP_EOL; if ( ! empty( $arg['description'] ) ) { $long_desc .= ': ' . $arg['description'] . PHP_EOL; } $yamlify = array(); foreach ( array( 'default', 'options' ) as $key ) { if ( isset( $arg[ $key ] ) ) { $yamlify[ $key ] = $arg[ $key ]; } } if ( ! empty( $yamlify ) ) { $long_desc .= Spyc::YAMLDump( $yamlify ); $long_desc .= '---' . PHP_EOL; } $long_desc .= PHP_EOL; } if ( ! empty( $long_desc ) ) { $long_desc = rtrim( $long_desc, PHP_EOL ); $long_desc = '## OPTIONS' . PHP_EOL . PHP_EOL . $long_desc; $leaf_command->set_longdesc( $long_desc ); } } } if ( isset( $args['when'] ) ) { self::get_runner()->register_early_invoke( $args['when'], $leaf_command ); } $command->add_subcommand( $leaf_name, $leaf_command ); self::do_hook( "after_add_command:{$name}" ); return true; } /** * Defer command addition for a sub-command if the parent command is not yet * registered. * * @param string $name Name for the sub-command. * @param string $parent Name for the parent command. * @param string $callable Command implementation as a class, function or closure. * @param array $args Optional. See `WP_CLI::add_command()` for details. */ private static function defer_command_addition( $name, $parent, $callable, $args = array() ) { $args['is_deferred'] = true; self::$deferred_additions[ $name ] = array( 'parent' => $parent, 'callable' => $callable, 'args' => $args, ); self::add_hook( "after_add_command:$parent", function () use ( $name ) { $deferred_additions = WP_CLI::get_deferred_additions(); if ( ! array_key_exists( $name, $deferred_additions ) ) { return; } $callable = $deferred_additions[ $name ]['callable']; $args = $deferred_additions[ $name ]['args']; WP_CLI::remove_deferred_addition( $name ); WP_CLI::add_command( $name, $callable, $args ); } ); } /** * Get the list of outstanding deferred command additions. * * @return array Array of outstanding command additions. */ public static function get_deferred_additions() { return self::$deferred_additions; } /** * Remove a command addition from the list of outstanding deferred additions. */ public static function remove_deferred_addition( $name ) { if ( ! array_key_exists( $name, self::$deferred_additions ) ) { WP_CLI::warning( "Trying to remove a non-existent command addition '{$name}'." ); } unset( self::$deferred_additions[ $name ] ); } /** * Display informational message without prefix, and ignore `--quiet`. * * Message is written to STDOUT. `WP_CLI::log()` is typically recommended; * `WP_CLI::line()` is included for historical compat. * * @access public * @category Output * * @param string $message Message to display to the end user. * @return null */ public static function line( $message = '' ) { echo $message . "\n"; } /** * Display informational message without prefix. * * Message is written to STDOUT, or discarded when `--quiet` flag is supplied. * * ``` * # `wp cli update` lets user know of each step in the update process. * WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) ); * ``` * * @access public * @category Output * * @param string $message Message to write to STDOUT. */ public static function log( $message ) { self::$logger->info( $message ); } /** * Display success message prefixed with "Success: ". * * Success message is written to STDOUT. * * Typically recommended to inform user of successful script conclusion. * * ``` * # wp rewrite flush expects 'rewrite_rules' option to be set after flush. * flush_rewrite_rules( \WP_CLI\Utils\get_flag_value( $assoc_args, 'hard' ) ); * if ( ! get_option( 'rewrite_rules' ) ) { * WP_CLI::warning( "Rewrite rules are empty." ); * } else { * WP_CLI::success( 'Rewrite rules flushed.' ); * } * ``` * * @access public * @category Output * * @param string $message Message to write to STDOUT. * @return null */ public static function success( $message ) { self::$logger->success( $message ); } /** * Display debug message prefixed with "Debug: " when `--debug` is used. * * Debug message is written to STDERR, and includes script execution time. * * Helpful for optionally showing greater detail when needed. Used throughout * WP-CLI bootstrap process for easier debugging and profiling. * * ``` * # Called in `WP_CLI\Runner::set_wp_root()`. * private static function set_wp_root( $path ) { * define( 'ABSPATH', Utils\trailingslashit( $path ) ); * WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH ); * $_SERVER['DOCUMENT_ROOT'] = realpath( $path ); * } * * # Debug details only appear when `--debug` is used. * # $ wp --debug * # [...] * # Debug: ABSPATH defined: /srv/www/wordpress-develop.dev/src/ (0.225s) * ``` * * @access public * @category Output * * @param string $message Message to write to STDERR. * @param string $group Organize debug message to a specific group. * @return null */ public static function debug( $message, $group = false ) { self::$logger->debug( self::error_to_string( $message ), $group ); } /** * Display warning message prefixed with "Warning: ". * * Warning message is written to STDERR. * * Use instead of `WP_CLI::debug()` when script execution should be permitted * to continue. * * ``` * # `wp plugin activate` skips activation when plugin is network active. * $status = $this->get_status( $plugin->file ); * // Network-active is the highest level of activation status * if ( 'active-network' === $status ) { * WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." ); * continue; * } * ``` * * @access public * @category Output * * @param string $message Message to write to STDERR. * @return null */ public static function warning( $message ) { self::$logger->warning( self::error_to_string( $message ) ); } /** * Display error message prefixed with "Error: " and exit script. * * Error message is written to STDERR. Defaults to halting script execution * with return code 1. * * Use `WP_CLI::warning()` instead when script execution should be permitted * to continue. * * ``` * # `wp cache flush` considers flush failure to be a fatal error. * if ( false === wp_cache_flush() ) { * WP_CLI::error( 'The object cache could not be flushed.' ); * } * ``` * * @access public * @category Output * * @param string|WP_Error $message Message to write to STDERR. * @param boolean|integer $exit True defaults to exit(1). * @return null */ public static function error( $message, $exit = true ) { if ( ! isset( self::get_runner()->assoc_args['completions'] ) ) { self::$logger->error( self::error_to_string( $message ) ); } $return_code = false; if ( true === $exit ) { $return_code = 1; } elseif ( is_int( $exit ) && $exit >= 1 ) { $return_code = $exit; } if ( $return_code ) { if ( self::$capture_exit ) { throw new ExitException( null, $return_code ); } exit( $return_code ); } } /** * Halt script execution with a specific return code. * * Permits script execution to be overloaded by `WP_CLI::runcommand()` * * @access public * @category Output * * @param integer $return_code */ public static function halt( $return_code ) { if ( self::$capture_exit ) { throw new ExitException( null, $return_code ); } exit( $return_code ); } /** * Display a multi-line error message in a red box. Doesn't exit script. * * Error message is written to STDERR. * * @access public * @category Output * * @param array $message Multi-line error message to be displayed. */ public static function error_multi_line( $message_lines ) { if ( ! isset( self::get_runner()->assoc_args['completions'] ) && is_array( $message_lines ) ) { self::$logger->error_multi_line( array_map( array( __CLASS__, 'error_to_string' ), $message_lines ) ); } } /** * Ask for confirmation before running a destructive operation. * * If 'y' is provided to the question, the script execution continues. If * 'n' or any other response is provided to the question, script exits. * * ``` * # `wp db drop` asks for confirmation before dropping the database. * * WP_CLI::confirm( "Are you sure you want to drop the database?", $assoc_args ); * ``` * * @access public * @category Input * * @param string $question Question to display before the prompt. * @param array $assoc_args Skips prompt if 'yes' is provided. */ public static function confirm( $question, $assoc_args = array() ) { if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'yes' ) ) { fwrite( STDOUT, $question . ' [y/n] ' ); $answer = strtolower( trim( fgets( STDIN ) ) ); if ( 'y' != $answer ) { exit; } } } /** * Read value from a positional argument or from STDIN. * * @param array $args The list of positional arguments. * @param int $index At which position to check for the value. * * @return string */ public static function get_value_from_arg_or_stdin( $args, $index ) { if ( isset( $args[ $index ] ) ) { $raw_value = $args[ $index ]; } else { // We don't use file_get_contents() here because it doesn't handle // Ctrl-D properly, when typing in the value interactively. $raw_value = ''; while ( ( $line = fgets( STDIN ) ) !== false ) { $raw_value .= $line; } } return $raw_value; } /** * Read a value, from various formats. * * @access public * @category Input * * @param mixed $value * @param array $assoc_args */ public static function read_value( $raw_value, $assoc_args = array() ) { if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { $value = json_decode( $raw_value, true ); if ( null === $value ) { WP_CLI::error( sprintf( 'Invalid JSON: %s', $raw_value ) ); } } else { $value = $raw_value; } return $value; } /** * Display a value, in various formats * * @param mixed $value Value to display. * @param array $assoc_args Arguments passed to the command, determining format. */ public static function print_value( $value, $assoc_args = array() ) { if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { $value = json_encode( $value ); } elseif ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'yaml' ) { $value = Spyc::YAMLDump( $value, 2, 0 ); } elseif ( is_array( $value ) || is_object( $value ) ) { $value = var_export( $value ); } echo $value . "\n"; } /** * Convert a wp_error into a string * * @param mixed $errors * @return string */ public static function error_to_string( $errors ) { if ( is_string( $errors ) ) { return $errors; } // Only json_encode() the data when it needs it $render_data = function( $data ) { if ( is_array( $data ) || is_object( $data ) ) { return json_encode( $data ); } return '"' . $data . '"'; }; if ( is_object( $errors ) && is_a( $errors, 'WP_Error' ) ) { foreach ( $errors->get_error_messages() as $message ) { if ( $errors->get_error_data() ) { return $message . ' ' . $render_data( $errors->get_error_data() ); } return $message; } } } /** * Launch an arbitrary external process that takes over I/O. * * ``` * # `wp core download` falls back to the `tar` binary when PharData isn't available * if ( ! class_exists( 'PharData' ) ) { * $cmd = "tar xz --strip-components=1 --directory=%s -f $tarball"; * WP_CLI::launch( Utils\esc_cmd( $cmd, $dest ) ); * return; * } * ``` * * @access public * @category Execution * * @param string $command External process to launch. * @param boolean $exit_on_error Whether to exit if the command returns an elevated return code. * @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results. * @return int|ProcessRun The command exit status, or a ProcessRun object for full details. */ public static function launch( $command, $exit_on_error = true, $return_detailed = false ) { Utils\check_proc_available( 'launch' ); $proc = Process::create( $command ); $results = $proc->run(); if ( -1 == $results->return_code ) { self::warning( "Spawned process returned exit code {$results->return_code}, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option." ); } if ( $results->return_code && $exit_on_error ) { exit( $results->return_code ); } if ( $return_detailed ) { return $results; } return $results->return_code; } /** * Run a WP-CLI command in a new process reusing the current runtime arguments. * * Use `WP_CLI::runcommand()` instead, which is easier to use and works better. * * Note: While this command does persist a limited set of runtime arguments, * it *does not* persist environment variables. Practically speaking, WP-CLI * packages won't be loaded when using WP_CLI::launch_self() because the * launched process doesn't have access to the current process $HOME. * * @access public * @category Execution * * @param string $command WP-CLI command to call. * @param array $args Positional arguments to include when calling the command. * @param array $assoc_args Associative arguments to include when calling the command. * @param bool $exit_on_error Whether to exit if the command returns an elevated return code. * @param bool $return_detailed Whether to return an exit status (default) or detailed execution results. * @param array $runtime_args Override one or more global args (path,url,user,allow-root) * @return int|ProcessRun The command exit status, or a ProcessRun instance */ public static function launch_self( $command, $args = array(), $assoc_args = array(), $exit_on_error = true, $return_detailed = false, $runtime_args = array() ) { $reused_runtime_args = array( 'path', 'url', 'user', 'allow-root', ); foreach ( $reused_runtime_args as $key ) { if ( isset( $runtime_args[ $key ] ) ) { $assoc_args[ $key ] = $runtime_args[ $key ]; } elseif ( $value = self::get_runner()->config[ $key ] ) { $assoc_args[ $key ] = $value; } } $php_bin = self::get_php_binary(); $script_path = $GLOBALS['argv'][0]; if ( getenv( 'WP_CLI_CONFIG_PATH' ) ) { $config_path = getenv( 'WP_CLI_CONFIG_PATH' ); } else { $config_path = Utils\get_home_dir() . '/.wp-cli/config.yml'; } $config_path = escapeshellarg( $config_path ); $args = implode( ' ', array_map( 'escapeshellarg', $args ) ); $assoc_args = \WP_CLI\Utils\assoc_args_to_str( $assoc_args ); $full_command = "WP_CLI_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$command} {$args} {$assoc_args}"; return self::launch( $full_command, $exit_on_error, $return_detailed ); } /** * Get the path to the PHP binary used when executing WP-CLI. * * Environment values permit specific binaries to be indicated. * * @access public * @category System * * @return string */ public static function get_php_binary() { if ( getenv( 'WP_CLI_PHP_USED' ) ) { return getenv( 'WP_CLI_PHP_USED' ); } if ( getenv( 'WP_CLI_PHP' ) ) { return getenv( 'WP_CLI_PHP' ); } if ( defined( 'PHP_BINARY' ) ) { return PHP_BINARY; } return 'php'; } /** * Get values of global configuration parameters. * * Provides access to `--path=`, `--url=`, and other values of * the [global configuration parameters](https://wp-cli.org/config/). * * ``` * WP_CLI::log( 'The --url= value is: ' . WP_CLI::get_config( 'url' ) ); * ``` * * @access public * @category Input * * @param string $key Get value for a specific global configuration parameter. * @return mixed */ public static function get_config( $key = null ) { if ( null === $key ) { return self::get_runner()->config; } if ( ! isset( self::get_runner()->config[ $key ] ) ) { self::warning( "Unknown config option '$key'." ); return null; } return self::get_runner()->config[ $key ]; } /** * Run a WP-CLI command. * * Launches a new child process to run a specified WP-CLI command. * Optionally: * * * Run the command in an existing process. * * Prevent halting script execution on error. * * Capture and return STDOUT, or full details about command execution. * * Parse JSON output if the command rendered it. * * ``` * $options = array( * 'return' => true, // Return 'STDOUT'; use 'all' for full object. * 'parse' => 'json', // Parse captured STDOUT to JSON array. * 'launch' => false, // Reuse the current process. * 'exit_error' => true, // Halt script execution on error. * ); * $plugins = WP_CLI::runcommand( 'plugin list --format=json', $options ); * ``` * * @access public * @category Execution * * @param string $command WP-CLI command to run, including arguments. * @param array $options Configuration options for command execution. * @return mixed */ public static function runcommand( $command, $options = array() ) { $defaults = array( 'launch' => true, // Launch a new process, or reuse the existing. 'exit_error' => true, // Exit on error by default. 'return' => false, // Capture and return output, or render in realtime. 'parse' => false, // Parse returned output as a particular format. ); $options = array_merge( $defaults, $options ); $launch = $options['launch']; $exit_error = $options['exit_error']; $return = $options['return']; $parse = $options['parse']; $retval = null; if ( $launch ) { Utils\check_proc_available( 'launch option' ); $descriptors = array( 0 => STDIN, 1 => STDOUT, 2 => STDERR, ); if ( $return ) { $descriptors = array( 0 => STDIN, 1 => array( 'pipe', 'w' ), 2 => array( 'pipe', 'w' ), ); } $php_bin = self::get_php_binary(); $script_path = $GLOBALS['argv'][0]; // Persist runtime arguments unless they've been specified otherwise. $configurator = \WP_CLI::get_configurator(); $argv = array_slice( $GLOBALS['argv'], 1 ); list( $_, $_, $runtime_config ) = $configurator->parse_args( $argv ); foreach ( $runtime_config as $k => $v ) { if ( preg_match( "|^--{$k}=?$|", $command ) ) { unset( $runtime_config[ $k ] ); } } $runtime_config = Utils\assoc_args_to_str( $runtime_config ); $runcommand = "{$php_bin} {$script_path} {$runtime_config} {$command}"; $proc = proc_open( $runcommand, $descriptors, $pipes, getcwd(), null ); if ( $return ) { $stdout = stream_get_contents( $pipes[1] ); fclose( $pipes[1] ); $stderr = stream_get_contents( $pipes[2] ); fclose( $pipes[2] ); } $return_code = proc_close( $proc ); if ( -1 == $return_code ) { self::warning( 'Spawned process returned exit code -1, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option.' ); } elseif ( $return_code && $exit_error ) { exit( $return_code ); } if ( true === $return || 'stdout' === $return ) { $retval = trim( $stdout ); } elseif ( 'stderr' === $return ) { $retval = trim( $stderr ); } elseif ( 'return_code' === $return ) { $retval = $return_code; } elseif ( 'all' === $return ) { $retval = (object) array( 'stdout' => trim( $stdout ), 'stderr' => trim( $stderr ), 'return_code' => $return_code, ); } } else { $configurator = self::get_configurator(); $argv = Utils\parse_str_to_argv( $command ); list( $args, $assoc_args, $runtime_config ) = $configurator->parse_args( $argv ); if ( $return ) { $existing_logger = self::$logger; self::$logger = new WP_CLI\Loggers\Execution; self::$logger->ob_start(); } if ( ! $exit_error ) { self::$capture_exit = true; } try { self::get_runner()->run_command( $args, $assoc_args, array( 'back_compat_conversions' => true, ) ); $return_code = 0; } catch ( ExitException $e ) { $return_code = $e->getCode(); } if ( $return ) { $execution_logger = self::$logger; $execution_logger->ob_end(); self::$logger = $existing_logger; $stdout = $execution_logger->stdout; $stderr = $execution_logger->stderr; if ( true === $return || 'stdout' === $return ) { $retval = trim( $stdout ); } elseif ( 'stderr' === $return ) { $retval = trim( $stderr ); } elseif ( 'return_code' === $return ) { $retval = $return_code; } elseif ( 'all' === $return ) { $retval = (object) array( 'stdout' => trim( $stdout ), 'stderr' => trim( $stderr ), 'return_code' => $return_code, ); } } if ( ! $exit_error ) { self::$capture_exit = false; } } if ( ( true === $return || 'stdout' === $return ) && 'json' === $parse ) { $retval = json_decode( $retval, true ); } return $retval; } /** * Run a given command within the current process using the same global * parameters. * * Use `WP_CLI::runcommand()` instead, which is easier to use and works better. * * To run a command using a new process with the same global parameters, * use WP_CLI::launch_self(). To run a command using a new process with * different global parameters, use WP_CLI::launch(). * * ``` * ob_start(); * WP_CLI::run_command( array( 'cli', 'cmd-dump' ) ); * $ret = ob_get_clean(); * ``` * * @access public * @category Execution * * @param array $args Positional arguments including command name. * @param array $assoc_args */ public static function run_command( $args, $assoc_args = array() ) { self::get_runner()->run_command( $args, $assoc_args ); } // DEPRECATED STUFF public static function add_man_dir() { trigger_error( 'WP_CLI::add_man_dir() is deprecated. Add docs inline.', E_USER_WARNING ); } // back-compat public static function out( $str ) { fwrite( STDOUT, $str ); } // back-compat // @codingStandardsIgnoreLine public static function addCommand( $name, $class ) { trigger_error( sprintf( 'wp %s: %s is deprecated. use WP_CLI::add_command() instead.', $name, __FUNCTION__ ), E_USER_WARNING ); self::add_command( $name, $class ); } } get( $arg ); if ( ! $item ) { \WP_CLI::error( sprintf( $this->msg, $arg ) ); } return $item; } /** * @param array The raw CLI arguments * @return array The list of found items */ public function get_many( $args ) { $items = array(); foreach ( $args as $arg ) { $item = $this->get( $arg ); if ( $item ) { $items[] = $item; } else { \WP_CLI::warning( sprintf( $this->msg, $arg ) ); } } return $items; } } $key; } /** * Register a command for early invocation, generally before WordPress loads. * * @param string $when Named execution hook * @param WP_CLI\Dispatcher\Subcommand $command */ public function register_early_invoke( $when, $command ) { $this->_early_invoke[ $when ][] = array_slice( Dispatcher\get_path( $command ), 1 ); } /** * Perform the early invocation of a command. * * @param string $when Named execution hook */ private function do_early_invoke( $when ) { if ( ! isset( $this->_early_invoke[ $when ] ) ) { return; } foreach ( $this->_early_invoke[ $when ] as $path ) { if ( $this->cmd_starts_with( $path ) ) { $this->_run_command_and_exit(); } } } /** * Get the path to the global configuration YAML file. * * @return string|false */ public function get_global_config_path() { if ( getenv( 'WP_CLI_CONFIG_PATH' ) ) { $config_path = getenv( 'WP_CLI_CONFIG_PATH' ); $this->_global_config_path_debug = 'Using global config from WP_CLI_CONFIG_PATH env var: ' . $config_path; } else { $config_path = Utils\get_home_dir() . '/.wp-cli/config.yml'; $this->_global_config_path_debug = 'Using default global config: ' . $config_path; } if ( is_readable( $config_path ) ) { return $config_path; } $this->_global_config_path_debug = 'No readable global config found'; return false; } /** * Get the path to the project-specific configuration * YAML file. * wp-cli.local.yml takes priority over wp-cli.yml. * * @return string|false */ public function get_project_config_path() { $config_files = array( 'wp-cli.local.yml', 'wp-cli.yml', ); // Stop looking upward when we find we have emerged from a subdirectory // install into a parent install $project_config_path = Utils\find_file_upward( $config_files, getcwd(), function ( $dir ) { static $wp_load_count = 0; $wp_load_path = $dir . DIRECTORY_SEPARATOR . 'wp-load.php'; if ( file_exists( $wp_load_path ) ) { ++ $wp_load_count; } return $wp_load_count > 1; } ); $this->_project_config_path_debug = 'No project config found'; if ( ! empty( $project_config_path ) ) { $this->_project_config_path_debug = 'Using project config: ' . $project_config_path; } return $project_config_path; } /** * Get the path to the packages directory * * @return string */ public function get_packages_dir_path() { if ( getenv( 'WP_CLI_PACKAGES_DIR' ) ) { $packages_dir = Utils\trailingslashit( getenv( 'WP_CLI_PACKAGES_DIR' ) ); } else { $packages_dir = Utils\get_home_dir() . '/.wp-cli/packages/'; } return $packages_dir; } /** * Attempts to find the path to the WP install inside index.php * * @param string $index_path * @return string|false */ private static function extract_subdir_path( $index_path ) { $index_code = file_get_contents( $index_path ); if ( ! preg_match( '|^\s*require\s*\(?\s*(.+?)/wp-blog-header\.php([\'"])|m', $index_code, $matches ) ) { return false; } $wp_path_src = $matches[1] . $matches[2]; $wp_path_src = Utils\replace_path_consts( $wp_path_src, $index_path ); $wp_path = eval( "return $wp_path_src;" ); if ( ! Utils\is_path_absolute( $wp_path ) ) { $wp_path = dirname( $index_path ) . "/$wp_path"; } return $wp_path; } /** * Find the directory that contains the WordPress files. * Defaults to the current working dir. * * @return string An absolute path */ private function find_wp_root() { if ( ! empty( $this->config['path'] ) ) { $path = $this->config['path']; if ( ! Utils\is_path_absolute( $path ) ) { $path = getcwd() . '/' . $path; } return $path; } if ( $this->cmd_starts_with( array( 'core', 'download' ) ) ) { return getcwd(); } $dir = getcwd(); while ( is_readable( $dir ) ) { if ( file_exists( "$dir/wp-load.php" ) ) { return $dir; } if ( file_exists( "$dir/index.php" ) ) { if ( $path = self::extract_subdir_path( "$dir/index.php" ) ) { return $path; } } $parent_dir = dirname( $dir ); if ( empty( $parent_dir ) || $parent_dir === $dir ) { break; } $dir = $parent_dir; } } /** * Set WordPress root as a given path. * * @param string $path */ private static function set_wp_root( $path ) { define( 'ABSPATH', Utils\trailingslashit( $path ) ); WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH, 'bootstrap' ); $_SERVER['DOCUMENT_ROOT'] = realpath( $path ); } /** * Guess which URL context WP-CLI has been invoked under. * * @param array $assoc_args * @return string|false */ private static function guess_url( $assoc_args ) { if ( isset( $assoc_args['blog'] ) ) { $assoc_args['url'] = $assoc_args['blog']; } if ( isset( $assoc_args['url'] ) ) { $url = $assoc_args['url']; if ( true === $url ) { WP_CLI::warning( 'The --url parameter expects a value.' ); } } if ( isset( $url ) ) { return $url; } return false; } private function cmd_starts_with( $prefix ) { return array_slice( $this->arguments, 0, count( $prefix ) ) === $prefix; } /** * Given positional arguments, find the command to execute. * * @param array $args * @return array|string Command, args, and path on success; error message on failure */ public function find_command_to_run( $args ) { $command = \WP_CLI::get_root_command(); WP_CLI::do_hook( 'find_command_to_run_pre' ); $cmd_path = array(); while ( ! empty( $args ) && $command->can_have_subcommands() ) { $cmd_path[] = $args[0]; $full_name = implode( ' ', $cmd_path ); $subcommand = $command->find_subcommand( $args ); if ( ! $subcommand ) { if ( count( $cmd_path ) > 1 ) { $child = array_pop( $cmd_path ); $parent_name = implode( ' ', $cmd_path ); $suggestion = $this->get_subcommand_suggestion( $child, $command ); return sprintf( "'%s' is not a registered subcommand of '%s'. See 'wp help %s' for available subcommands.%s", $child, $parent_name, $parent_name, ! empty( $suggestion ) ? PHP_EOL . "Did you mean '{$suggestion}'?" : '' ); } $suggestion = $this->get_subcommand_suggestion( $full_name, $command ); return sprintf( "'%s' is not a registered wp command. See 'wp help' for available commands.%s", $full_name, ! empty( $suggestion ) ? PHP_EOL . "Did you mean '{$suggestion}'?" : '' ); } if ( $this->is_command_disabled( $subcommand ) ) { return sprintf( "The '%s' command has been disabled from the config file.", $full_name ); } $command = $subcommand; } return array( $command, $args, $cmd_path ); } /** * Find the WP-CLI command to run given arguments, and invoke it. * * @param array $args Positional arguments including command name * @param array $assoc_args Associative arguments for the command. * @param array $options Configuration options for the function. */ public function run_command( $args, $assoc_args = array(), $options = array() ) { if ( ! empty( $options['back_compat_conversions'] ) ) { list( $args, $assoc_args ) = self::back_compat_conversions( $args, $assoc_args ); } $r = $this->find_command_to_run( $args ); if ( is_string( $r ) ) { WP_CLI::error( $r ); } list( $command, $final_args, $cmd_path ) = $r; $name = implode( ' ', $cmd_path ); $extra_args = array(); if ( isset( $this->extra_config[ $name ] ) ) { $extra_args = $this->extra_config[ $name ]; } WP_CLI::debug( 'Running command: ' . $name, 'bootstrap' ); try { $command->invoke( $final_args, $assoc_args, $extra_args ); } catch ( WP_CLI\Iterators\Exception $e ) { WP_CLI::error( $e->getMessage() ); } } private function _run_command_and_exit( $help_exit_warning = '' ) { $this->run_command( $this->arguments, $this->assoc_args ); if ( $this->cmd_starts_with( array( 'help' ) ) ) { // Help couldn't find the command so exit with suggestion. $suggestion_or_disabled = $this->find_command_to_run( array_slice( $this->arguments, 1 ) ); if ( is_string( $suggestion_or_disabled ) ) { if ( $help_exit_warning ) { WP_CLI::warning( $help_exit_warning ); } WP_CLI::error( $suggestion_or_disabled ); } // Should never get here. } exit; } /** * Perform a command against a remote server over SSH (or a container using * scheme of "docker" or "docker-compose"). * * @param string $connection_string Passed connection string. * @return void */ private function run_ssh_command( $connection_string ) { WP_CLI::do_hook( 'before_ssh' ); $bits = Utils\parse_ssh_url( $connection_string ); $pre_cmd = getenv( 'WP_CLI_SSH_PRE_CMD' ); if ( $pre_cmd ) { $pre_cmd = rtrim( $pre_cmd, ';' ) . '; '; } if ( ! empty( $bits['path'] ) ) { $pre_cmd .= 'cd ' . escapeshellarg( $bits['path'] ) . '; '; } $env_vars = ''; if ( getenv( 'WP_CLI_STRICT_ARGS_MODE' ) ) { $env_vars .= 'WP_CLI_STRICT_ARGS_MODE=1 '; } $wp_binary = 'wp'; $wp_args = array_slice( $GLOBALS['argv'], 1 ); if ( $this->alias && ! empty( $wp_args[0] ) && $this->alias === $wp_args[0] ) { array_shift( $wp_args ); $runtime_alias = array(); foreach ( $this->aliases[ $this->alias ] as $key => $value ) { if ( 'ssh' === $key ) { continue; } $runtime_alias[ $key ] = $value; } if ( ! empty( $runtime_alias ) ) { $encoded_alias = json_encode( array( $this->alias => $runtime_alias, ) ); $wp_binary = "WP_CLI_RUNTIME_ALIAS='{$encoded_alias}' {$wp_binary} {$this->alias}"; } } foreach ( $wp_args as $k => $v ) { if ( preg_match( '#--ssh=#', $v ) ) { unset( $wp_args[ $k ] ); } } $wp_command = $pre_cmd . $env_vars . $wp_binary . ' ' . implode( ' ', array_map( 'escapeshellarg', $wp_args ) ); $escaped_command = $this->generate_ssh_command( $bits, $wp_command ); passthru( $escaped_command, $exit_code ); if ( 255 === $exit_code ) { WP_CLI::error( 'Cannot connect over SSH using provided configuration.', 255 ); } else { exit( $exit_code ); } } /** * Generate a shell command from the parsed connection string. * * @param array $bits Parsed connection string. * @param string $wp_command WP-CLI command to run. * @return string */ private function generate_ssh_command( $bits, $wp_command ) { $escaped_command = ''; // Set default values. foreach ( array( 'scheme', 'user', 'host', 'port', 'path' ) as $bit ) { if ( ! isset( $bits[ $bit ] ) ) { $bits[ $bit ] = null; } WP_CLI::debug( 'SSH ' . $bit . ': ' . $bits[ $bit ], 'bootstrap' ); } $is_tty = function_exists( 'posix_isatty' ) && posix_isatty( STDOUT ); if ( 'docker' === $bits['scheme'] ) { $command = 'docker exec %s%s%s sh -c %s'; $escaped_command = sprintf( $command, $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $is_tty ? '-t ' : '', escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) ); } if ( 'docker-compose' === $bits['scheme'] ) { $command = 'docker-compose exec %s%s%s sh -c %s'; $escaped_command = sprintf( $command, $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $is_tty ? '' : '-T ', escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) ); } // Vagrant ssh-config. if ( 'vagrant' === $bits['scheme'] ) { $command = 'vagrant ssh -c %s %s'; $escaped_command = sprintf( $command, escapeshellarg( $wp_command ), escapeshellarg( $bits['host'] ) ); } // Default scheme is SSH. if ( 'ssh' === $bits['scheme'] || null === $bits['scheme'] ) { $command = 'ssh -q %s%s %s %s'; if ( $bits['user'] ) { $bits['host'] = $bits['user'] . '@' . $bits['host']; } $escaped_command = sprintf( $command, $bits['port'] ? '-p ' . (int) $bits['port'] . ' ' : '', escapeshellarg( $bits['host'] ), $is_tty ? '-t' : '-T', escapeshellarg( $wp_command ) ); } WP_CLI::debug( 'Running SSH command: ' . $escaped_command, 'bootstrap' ); return $escaped_command; } /** * Check whether a given command is disabled by the config * * @return bool */ public function is_command_disabled( $command ) { $path = implode( ' ', array_slice( \WP_CLI\Dispatcher\get_path( $command ), 1 ) ); return in_array( $path, $this->config['disabled_commands'] ); } /** * Returns wp-config.php code, skipping the loading of wp-settings.php * * @return string */ public function get_wp_config_code() { $wp_config_path = Utils\locate_wp_config(); $wp_config_code = explode( "\n", file_get_contents( $wp_config_path ) ); $found_wp_settings = false; $lines_to_run = array(); foreach ( $wp_config_code as $line ) { if ( preg_match( '/^\s*require.+wp-settings\.php/', $line ) ) { $found_wp_settings = true; continue; } $lines_to_run[] = $line; } if ( ! $found_wp_settings ) { WP_CLI::error( 'Strange wp-config.php file: wp-settings.php is not loaded directly.' ); } $source = implode( "\n", $lines_to_run ); $source = Utils\replace_path_consts( $source, $wp_config_path ); return preg_replace( '|^\s*\<\?php\s*|', '', $source ); } /** * Transparently convert deprecated syntaxes * * @param array $args * @param array $assoc_args * @return array */ private static function back_compat_conversions( $args, $assoc_args ) { $top_level_aliases = array( 'sql' => 'db', 'blog' => 'site', ); if ( count( $args ) > 0 ) { foreach ( $top_level_aliases as $old => $new ) { if ( $old == $args[0] ) { $args[0] = $new; break; } } } // *-meta -> * meta if ( ! empty( $args ) && preg_match( '/(post|comment|user|network)-meta/', $args[0], $matches ) ) { array_shift( $args ); array_unshift( $args, 'meta' ); array_unshift( $args, $matches[1] ); } // core (multsite-)install --admin_name= -> --admin_user= if ( count( $args ) > 0 && 'core' == $args[0] && isset( $assoc_args['admin_name'] ) ) { $assoc_args['admin_user'] = $assoc_args['admin_name']; unset( $assoc_args['admin_name'] ); } // core config -> config create if ( array( 'core', 'config' ) == array_slice( $args, 0, 2 ) ) { list( $args[0], $args[1] ) = array( 'config', 'create' ); } // core language -> language core if ( array( 'core', 'language' ) == array_slice( $args, 0, 2 ) ) { list( $args[0], $args[1] ) = array( 'language', 'core' ); } // core verify-checksums -> checksum core if ( array( 'core', 'verify-checksums' ) == array_slice( $args, 0, 2 ) ) { list( $args[0], $args[1] ) = array( 'checksum', 'core' ); } // site create --site_id= -> site create --network_id= if ( count( $args ) >= 2 && 'site' === $args[0] && 'create' === $args[1] && isset( $assoc_args['site_id'] ) ) { $assoc_args['network_id'] = $assoc_args['site_id']; unset( $assoc_args['site_id'] ); } // {plugin|theme} update-all -> {plugin|theme} update --all if ( count( $args ) > 1 && in_array( $args[0], array( 'plugin', 'theme' ) ) && 'update-all' === $args[1] ) { $args[1] = 'update'; $assoc_args['all'] = true; } // transient delete-expired -> transient delete --expired if ( count( $args ) > 1 && 'transient' === $args[0] && 'delete-expired' === $args[1] ) { $args[1] = 'delete'; $assoc_args['expired'] = true; } // transient delete-all -> transient delete --all if ( count( $args ) > 1 && 'transient' === $args[0] && 'delete-all' === $args[1] ) { $args[1] = 'delete'; $assoc_args['all'] = true; } // plugin scaffold -> scaffold plugin if ( array( 'plugin', 'scaffold' ) == array_slice( $args, 0, 2 ) ) { list( $args[0], $args[1] ) = array( $args[1], $args[0] ); } // foo --help -> help foo if ( isset( $assoc_args['help'] ) ) { array_unshift( $args, 'help' ); unset( $assoc_args['help'] ); } // {post|user} list --ids -> {post|user} list --format=ids if ( count( $args ) > 1 && in_array( $args[0], array( 'post', 'user' ) ) && 'list' === $args[1] && isset( $assoc_args['ids'] ) ) { $assoc_args['format'] = 'ids'; unset( $assoc_args['ids'] ); } // --json -> --format=json if ( isset( $assoc_args['json'] ) ) { $assoc_args['format'] = 'json'; unset( $assoc_args['json'] ); } // --{version|info} -> cli {version|info} if ( empty( $args ) ) { $special_flags = array( 'version', 'info' ); foreach ( $special_flags as $key ) { if ( isset( $assoc_args[ $key ] ) ) { $args = array( 'cli', $key ); unset( $assoc_args[ $key ] ); break; } } } // (post|comment|site|term) url --> (post|comment|site|term) list --*__in --field=url if ( count( $args ) >= 2 && in_array( $args[0], array( 'post', 'comment', 'site', 'term' ) ) && 'url' === $args[1] ) { switch ( $args[0] ) { case 'post': $post_ids = array_slice( $args, 2 ); $args = array( 'post', 'list' ); $assoc_args['post__in'] = implode( ',', $post_ids ); $assoc_args['post_type'] = 'any'; $assoc_args['orderby'] = 'post__in'; $assoc_args['field'] = 'url'; break; case 'comment': $comment_ids = array_slice( $args, 2 ); $args = array( 'comment', 'list' ); $assoc_args['comment__in'] = implode( ',', $comment_ids ); $assoc_args['orderby'] = 'comment__in'; $assoc_args['field'] = 'url'; break; case 'site': $site_ids = array_slice( $args, 2 ); $args = array( 'site', 'list' ); $assoc_args['site__in'] = implode( ',', $site_ids ); $assoc_args['field'] = 'url'; break; case 'term': $taxonomy = ''; if ( isset( $args[2] ) ) { $taxonomy = $args[2]; } $term_ids = array_slice( $args, 3 ); $args = array( 'term', 'list', $taxonomy ); $assoc_args['include'] = implode( ',', $term_ids ); $assoc_args['orderby'] = 'include'; $assoc_args['field'] = 'url'; break; } } return array( $args, $assoc_args ); } /** * Whether or not the output should be rendered in color * * @return bool */ public function in_color() { return $this->colorize; } public function init_colorization() { if ( 'auto' === $this->config['color'] ) { $this->colorize = ( ! \WP_CLI\Utils\isPiped() && ! \WP_CLI\Utils\is_windows() ); } else { $this->colorize = $this->config['color']; } } public function init_logger() { if ( $this->config['quiet'] ) { $logger = new \WP_CLI\Loggers\Quiet; } else { $logger = new \WP_CLI\Loggers\Regular( $this->in_color() ); } WP_CLI::set_logger( $logger ); } public function get_required_files() { return $this->_required_files; } /** * Do WordPress core files exist? * * @return bool */ private function wp_exists() { return is_readable( ABSPATH . 'wp-includes/version.php' ); } private function check_wp_version() { if ( ! $this->wp_exists() ) { // If the command doesn't exist use as error. $args = $this->cmd_starts_with( array( 'help' ) ) ? array_slice( $this->arguments, 1 ) : $this->arguments; $suggestion_or_disabled = $this->find_command_to_run( $args ); if ( is_string( $suggestion_or_disabled ) ) { if ( ! preg_match( '/disabled from the config file.$/', $suggestion_or_disabled ) ) { WP_CLI::warning( "No WordPress install found. If the command '" . implode( ' ', $args ) . "' is in a plugin or theme, pass --path=`path/to/wordpress`." ); } WP_CLI::error( $suggestion_or_disabled ); } WP_CLI::error( "This does not seem to be a WordPress install.\n" . 'Pass --path=`path/to/wordpress` or run `wp core download`.' ); } global $wp_version; include ABSPATH . 'wp-includes/version.php'; $minimum_version = '3.7'; // @codingStandardsIgnoreStart if ( version_compare( $wp_version, $minimum_version, '<' ) ) { WP_CLI::error( "WP-CLI needs WordPress $minimum_version or later to work properly. " . "The version currently installed is $wp_version.\n" . 'Try running `wp core download --force`.' ); } // @codingStandardsIgnoreEnd } public function init_config() { $configurator = \WP_CLI::get_configurator(); $argv = array_slice( $GLOBALS['argv'], 1 ); $this->alias = null; if ( ! empty( $argv[0] ) && preg_match( '#' . Configurator::ALIAS_REGEX . '#', $argv[0], $matches ) ) { $this->alias = array_shift( $argv ); } // File config { $this->global_config_path = $this->get_global_config_path(); $this->project_config_path = $this->get_project_config_path(); $configurator->merge_yml( $this->global_config_path, $this->alias ); $config = $configurator->to_array(); $this->_required_files['global'] = $config[0]['require']; $configurator->merge_yml( $this->project_config_path, $this->alias ); $config = $configurator->to_array(); $this->_required_files['project'] = $config[0]['require']; } // Runtime config and args { list( $args, $assoc_args, $this->runtime_config ) = $configurator->parse_args( $argv ); list( $this->arguments, $this->assoc_args ) = self::back_compat_conversions( $args, $assoc_args ); $configurator->merge_array( $this->runtime_config ); } list( $this->config, $this->extra_config ) = $configurator->to_array(); $this->aliases = $configurator->get_aliases(); if ( count( $this->aliases ) && ! isset( $this->aliases['@all'] ) ) { $this->aliases = array_reverse( $this->aliases ); $this->aliases['@all'] = 'Run command against every registered alias.'; $this->aliases = array_reverse( $this->aliases ); } $this->_required_files['runtime'] = $this->config['require']; } private function check_root() { if ( $this->config['allow-root'] ) { return; # they're aware of the risks! } if ( count( $this->arguments ) >= 2 && 'cli' === $this->arguments[0] && in_array( $this->arguments[1], array( 'update', 'info' ), true ) ) { return; # make it easier to update root-owned copies } if ( ! function_exists( 'posix_geteuid' ) ) { return; # posix functions not available } if ( posix_geteuid() !== 0 ) { return; # not root } WP_CLI::error( "YIKES! It looks like you're running this as root. You probably meant to " . "run this as the user that your WordPress install exists under.\n" . "\n" . "If you REALLY mean to run this as root, we won't stop you, but just " . 'bear in mind that any code on this site will then have full control of ' . "your server, making it quite DANGEROUS.\n" . "\n" . "If you'd like to continue as root, please run this again, adding this " . "flag: --allow-root\n" . "\n" . "If you'd like to run it as the user that this site is under, you can " . "run the following to become the respective user:\n" . "\n" . " sudo -u USER -i -- wp \n" . "\n" ); } private function run_alias_group( $aliases ) { Utils\check_proc_available( 'group alias' ); $php_bin = WP_CLI::get_php_binary(); $script_path = $GLOBALS['argv'][0]; if ( getenv( 'WP_CLI_CONFIG_PATH' ) ) { $config_path = getenv( 'WP_CLI_CONFIG_PATH' ); } else { $config_path = Utils\get_home_dir() . '/.wp-cli/config.yml'; } $config_path = escapeshellarg( $config_path ); foreach ( $aliases as $alias ) { WP_CLI::log( $alias ); $args = implode( ' ', array_map( 'escapeshellarg', $this->arguments ) ); $assoc_args = Utils\assoc_args_to_str( $this->assoc_args ); $runtime_config = Utils\assoc_args_to_str( $this->runtime_config ); $full_command = "WP_CLI_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$alias} {$args}{$assoc_args}{$runtime_config}"; $proc = proc_open( $full_command, array( STDIN, STDOUT, STDERR ), $pipes ); proc_close( $proc ); } } private function set_alias( $alias ) { $orig_config = $this->config; $alias_config = $this->aliases[ $this->alias ]; $this->config = array_merge( $orig_config, $alias_config ); foreach ( $alias_config as $key => $_ ) { if ( isset( $orig_config[ $key ] ) && ! is_null( $orig_config[ $key ] ) ) { $this->assoc_args[ $key ] = $orig_config[ $key ]; } } } public function start() { WP_CLI::debug( $this->_global_config_path_debug, 'bootstrap' ); WP_CLI::debug( $this->_project_config_path_debug, 'bootstrap' ); WP_CLI::debug( 'argv: ' . implode( ' ', $GLOBALS['argv'] ), 'bootstrap' ); $this->check_root(); if ( $this->alias ) { if ( '@all' === $this->alias && ! isset( $this->aliases['@all'] ) ) { WP_CLI::error( "Cannot use '@all' when no aliases are registered." ); } if ( '@all' === $this->alias && is_string( $this->aliases['@all'] ) ) { $aliases = array_keys( $this->aliases ); $k = array_search( '@all', $aliases ); unset( $aliases[ $k ] ); $this->run_alias_group( $aliases ); exit; } if ( ! array_key_exists( $this->alias, $this->aliases ) ) { $error_msg = "Alias '{$this->alias}' not found."; $suggestion = Utils\get_suggestion( $this->alias, array_keys( $this->aliases ), $threshold = 2 ); if ( $suggestion ) { $error_msg .= PHP_EOL . "Did you mean '{$suggestion}'?"; } WP_CLI::error( $error_msg ); } // Numerically indexed means a group of aliases if ( isset( $this->aliases[ $this->alias ][0] ) ) { $group_aliases = $this->aliases[ $this->alias ]; $all_aliases = array_keys( $this->aliases ); if ( $diff = array_diff( $group_aliases, $all_aliases ) ) { WP_CLI::error( "Group '{$this->alias}' contains one or more invalid aliases: " . implode( ', ', $diff ) ); } $this->run_alias_group( $group_aliases ); exit; } $this->set_alias( $this->alias ); } if ( empty( $this->arguments ) ) { $this->arguments[] = 'help'; } // Protect 'cli info' from most of the runtime, // except when the command will be run over SSH if ( 'cli' === $this->arguments[0] && ! empty( $this->arguments[1] ) && 'info' === $this->arguments[1] && ! $this->config['ssh'] ) { $this->_run_command_and_exit(); } if ( isset( $this->config['http'] ) && ! class_exists( '\WP_REST_CLI\Runner' ) ) { WP_CLI::error( "RESTful WP-CLI needs to be installed. Try 'wp package install wp-cli/restful'." ); } if ( $this->config['ssh'] ) { $this->run_ssh_command( $this->config['ssh'] ); return; } // Show synopsis if it's a composite command. $r = $this->find_command_to_run( $this->arguments ); if ( is_array( $r ) ) { list( $command ) = $r; if ( $command->can_have_subcommands() ) { $command->show_usage(); exit; } } // Handle --path parameter self::set_wp_root( $this->find_wp_root() ); // First try at showing man page - if help command and either haven't found 'version.php' or 'wp-config.php' (so won't be loading WP & adding commands) or help on subcommand. if ( $this->cmd_starts_with( array( 'help' ) ) && ( ! $this->wp_exists() || ! Utils\locate_wp_config() || count( $this->arguments ) > 2 ) ) { $this->auto_check_update(); $this->run_command( $this->arguments, $this->assoc_args ); // Help didn't exit so failed to find the command at this stage. } // Handle --url parameter $url = self::guess_url( $this->config ); if ( $url ) { \WP_CLI::set_url( $url ); } $this->do_early_invoke( 'before_wp_load' ); $this->check_wp_version(); if ( $this->cmd_starts_with( array( 'config', 'create' ) ) ) { $this->_run_command_and_exit(); } if ( ! Utils\locate_wp_config() ) { WP_CLI::error( "'wp-config.php' not found.\n" . 'Either create one manually or use `wp config create`.' ); } if ( $this->cmd_starts_with( array( 'db' ) ) && ! $this->cmd_starts_with( array( 'db', 'tables' ) ) ) { eval( $this->get_wp_config_code() ); $this->_run_command_and_exit(); } if ( $this->cmd_starts_with( array( 'core', 'is-installed' ) ) || $this->cmd_starts_with( array( 'core', 'update-db' ) ) ) { define( 'WP_INSTALLING', true ); } if ( count( $this->arguments ) >= 2 && 'core' === $this->arguments[0] && in_array( $this->arguments[1], array( 'install', 'multisite-install' ) ) ) { define( 'WP_INSTALLING', true ); // We really need a URL here if ( ! isset( $_SERVER['HTTP_HOST'] ) ) { $url = 'http://example.com'; \WP_CLI::set_url( $url ); } if ( 'multisite-install' == $this->arguments[1] ) { // need to fake some globals to skip the checks in wp-includes/ms-settings.php $url_parts = Utils\parse_url( $url ); self::fake_current_site_blog( $url_parts ); if ( ! defined( 'COOKIEHASH' ) ) { define( 'COOKIEHASH', md5( $url_parts['host'] ) ); } } } if ( $this->cmd_starts_with( array( 'import' ) ) ) { define( 'WP_LOAD_IMPORTERS', true ); define( 'WP_IMPORTING', true ); } if ( $this->cmd_starts_with( array( 'cron', 'event', 'run' ) ) ) { define( 'DOING_CRON', true ); } $this->load_wordpress(); $this->_run_command_and_exit(); } /** * Load WordPress, if it hasn't already been loaded */ public function load_wordpress() { static $wp_cli_is_loaded; // Globals not explicitly globalized in WordPress global $site_id, $wpdb, $public, $current_site, $current_blog, $path, $shortcode_tags; if ( ! empty( $wp_cli_is_loaded ) ) { return; } $wp_cli_is_loaded = true; WP_CLI::debug( 'Begin WordPress load', 'bootstrap' ); WP_CLI::do_hook( 'before_wp_load' ); $this->check_wp_version(); $wp_config_path = Utils\locate_wp_config(); if ( ! $wp_config_path ) { WP_CLI::error( "'wp-config.php' not found.\n" . 'Either create one manually or use `wp config create`.' ); } WP_CLI::debug( 'wp-config.php path: ' . $wp_config_path, 'bootstrap' ); WP_CLI::do_hook( 'before_wp_config_load' ); // Load wp-config.php code, in the global scope $wp_cli_original_defined_vars = get_defined_vars(); eval( $this->get_wp_config_code() ); foreach ( get_defined_vars() as $key => $var ) { if ( array_key_exists( $key, $wp_cli_original_defined_vars ) || 'wp_cli_original_defined_vars' === $key ) { continue; } global ${$key}; ${$key} = $var; } $this->maybe_update_url_from_domain_constant(); WP_CLI::do_hook( 'after_wp_config_load' ); // Prevent error notice from wp_guess_url() when core isn't installed if ( $this->cmd_starts_with( array( 'core', 'is-installed' ) ) && ! defined( 'COOKIEHASH' ) ) { define( 'COOKIEHASH', md5( 'wp-cli' ) ); } // Load WP-CLI utilities require WP_CLI_ROOT . '/php/utils-wp.php'; // Set up WordPress bootstrap actions and filters $this->setup_bootstrap_hooks(); // Load Core, mu-plugins, plugins, themes etc. if ( Utils\wp_version_compare( '4.6-alpha-37575', '>=' ) ) { if ( $this->cmd_starts_with( array( 'help' ) ) ) { // Hack: define `WP_DEBUG` and `WP_DEBUG_DISPLAY` to get `wpdb::bail()` to `wp_die()`. if ( ! defined( 'WP_DEBUG' ) ) { define( 'WP_DEBUG', true ); } if ( ! defined( 'WP_DEBUG_DISPLAY' ) ) { define( 'WP_DEBUG_DISPLAY', true ); } } require ABSPATH . 'wp-settings.php'; } else { require WP_CLI_ROOT . '/php/wp-settings-cli.php'; } // Fix memory limit. See http://core.trac.wordpress.org/ticket/14889 ini_set( 'memory_limit', -1 ); // Load all the admin APIs, for convenience require ABSPATH . 'wp-admin/includes/admin.php'; add_filter( 'filesystem_method', function() { return 'direct'; }, 99 ); WP_CLI::debug( 'Loaded WordPress', 'bootstrap' ); WP_CLI::do_hook( 'after_wp_load' ); } private static function fake_current_site_blog( $url_parts ) { global $current_site, $current_blog; if ( ! isset( $url_parts['path'] ) ) { $url_parts['path'] = '/'; } $current_site = (object) array( 'id' => 1, 'blog_id' => 1, 'domain' => $url_parts['host'], 'path' => $url_parts['path'], 'cookie_domain' => $url_parts['host'], 'site_name' => 'Fake Site', ); $current_blog = (object) array( 'blog_id' => 1, 'site_id' => 1, 'domain' => $url_parts['host'], 'path' => $url_parts['path'], 'public' => '1', 'archived' => '0', 'mature' => '0', 'spam' => '0', 'deleted' => '0', 'lang_id' => '0', ); } /** * Called after wp-config.php is eval'd, to potentially reset `--url` */ private function maybe_update_url_from_domain_constant() { if ( ! empty( $this->config['url'] ) || ! empty( $this->config['blog'] ) ) { return; } if ( defined( 'DOMAIN_CURRENT_SITE' ) ) { $url = DOMAIN_CURRENT_SITE; if ( defined( 'PATH_CURRENT_SITE' ) ) { $url .= PATH_CURRENT_SITE; } \WP_CLI::set_url( $url ); } } /** * Set up hooks meant to run during the WordPress bootstrap process */ private function setup_bootstrap_hooks() { if ( $this->config['skip-plugins'] ) { $this->setup_skip_plugins_filters(); } if ( $this->config['skip-themes'] ) { WP_CLI::add_wp_hook( 'setup_theme', array( $this, 'action_setup_theme_wp_cli_skip_themes' ), 999 ); } if ( $this->cmd_starts_with( array( 'help' ) ) ) { // Try to trap errors on help. $help_handler = array( $this, 'help_wp_die_handler' ); // Avoid any cross PHP version issues by not using $this in anon function. WP_CLI::add_wp_hook( 'wp_die_handler', function () use ( $help_handler ) { return $help_handler; } ); } else { WP_CLI::add_wp_hook( 'wp_die_handler', function() { return '\WP_CLI\Utils\wp_die_handler'; } ); } // Prevent code from performing a redirect WP_CLI::add_wp_hook( 'wp_redirect', 'WP_CLI\\Utils\\wp_redirect_handler' ); WP_CLI::add_wp_hook( 'nocache_headers', function( $headers ) { // WordPress might be calling nocache_headers() because of a dead db global $wpdb; if ( ! empty( $wpdb->error ) ) { Utils\wp_die_handler( $wpdb->error ); } // Otherwise, WP might be calling nocache_headers() because WP isn't installed Utils\wp_not_installed(); return $headers; } ); // ALTERNATE_WP_CRON might trigger a redirect, which we can't handle if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { WP_CLI::add_wp_hook( 'muplugins_loaded', function() { remove_action( 'init', 'wp_cron' ); } ); } // Get rid of warnings when converting single site to multisite if ( defined( 'WP_INSTALLING' ) && $this->is_multisite() ) { $values = array( 'ms_files_rewriting' => null, 'active_sitewide_plugins' => array(), '_site_transient_update_core' => null, '_site_transient_update_themes' => null, '_site_transient_update_plugins' => null, 'WPLANG' => '', ); foreach ( $values as $key => $value ) { WP_CLI::add_wp_hook( "pre_site_option_$key", function () use ( $values, $key ) { return $values[ $key ]; } ); } } // Always permit operations against sites, regardless of status WP_CLI::add_wp_hook( 'ms_site_check', '__return_true' ); // Always permit operations against WordPress, regardless of maintenance mode WP_CLI::add_wp_hook( 'enable_maintenance_mode', function() { return false; } ); // Use our own debug mode handling instead of WP core WP_CLI::add_wp_hook( 'enable_wp_debug_mode_checks', function( $ret ) { Utils\wp_debug_mode(); return false; } ); // Never load advanced-cache.php drop-in when WP-CLI is operating WP_CLI::add_wp_hook( 'enable_loading_advanced_cache_dropin', function() { return false; } ); // In a multisite install, die if unable to find site given in --url parameter if ( $this->is_multisite() ) { WP_CLI::add_wp_hook( 'ms_site_not_found', function( $current_site, $domain, $path ) { $url = $domain . $path; $message = $url ? "Site '{$url}' not found." : 'Site not found.'; $has_param = isset( WP_CLI::get_runner()->config['url'] ); $has_const = defined( 'DOMAIN_CURRENT_SITE' ); $explanation = ''; if ( $has_param ) { $explanation = 'Verify `--url=` matches an existing site.'; } else { $explanation = "Define DOMAIN_CURRENT_SITE in 'wp-config.php' or use `--url=` to override."; if ( $has_const ) { $explanation = 'Verify DOMAIN_CURRENT_SITE matches an existing site or use `--url=` to override.'; } } if ( $explanation ) { $message .= ' ' . $explanation; } WP_CLI::error( $message ); }, 10, 3 ); } // The APC cache is not available on the command-line, so bail, to prevent cache poisoning WP_CLI::add_wp_hook( 'muplugins_loaded', function() { if ( $GLOBALS['_wp_using_ext_object_cache'] && class_exists( 'APC_Object_Cache' ) ) { WP_CLI::warning( 'Running WP-CLI while the APC object cache is activated can result in cache corruption.' ); WP_CLI::confirm( 'Given the consequences, do you wish to continue?' ); } }, 0 ); // Handle --user parameter if ( ! defined( 'WP_INSTALLING' ) ) { $config = $this->config; WP_CLI::add_wp_hook( 'init', function() use ( $config ) { if ( isset( $config['user'] ) ) { $fetcher = new \WP_CLI\Fetchers\User; $user = $fetcher->get_check( $config['user'] ); wp_set_current_user( $user->ID ); } else { add_action( 'init', 'kses_remove_filters', 11 ); } }, 0 ); } // Avoid uncaught exception when using wp_mail() without defined $_SERVER['SERVER_NAME'] WP_CLI::add_wp_hook( 'wp_mail_from', function( $from_email ) { if ( 'wordpress@' === $from_email ) { $sitename = strtolower( parse_url( site_url(), PHP_URL_HOST ) ); if ( substr( $sitename, 0, 4 ) == 'www.' ) { $sitename = substr( $sitename, 4 ); } $from_email = 'wordpress@' . $sitename; } return $from_email; } ); } /** * Set up the filters to skip the loaded plugins */ private function setup_skip_plugins_filters() { $wp_cli_filter_active_plugins = function( $plugins ) { $skipped_plugins = WP_CLI::get_runner()->config['skip-plugins']; if ( true === $skipped_plugins ) { return array(); } if ( ! is_array( $plugins ) ) { return $plugins; } foreach ( $plugins as $a => $b ) { // active_sitewide_plugins stores plugin name as the key. if ( false !== strpos( current_filter(), 'active_sitewide_plugins' ) && Utils\is_plugin_skipped( $a ) ) { unset( $plugins[ $a ] ); // active_plugins stores plugin name as the value. } elseif ( false !== strpos( current_filter(), 'active_plugins' ) && Utils\is_plugin_skipped( $b ) ) { unset( $plugins[ $a ] ); } } // Reindex because active_plugins expects a numeric index. if ( false !== strpos( current_filter(), 'active_plugins' ) ) { $plugins = array_values( $plugins ); } return $plugins; }; $hooks = array( 'pre_site_option_active_sitewide_plugins', 'site_option_active_sitewide_plugins', 'pre_option_active_plugins', 'option_active_plugins', ); foreach ( $hooks as $hook ) { WP_CLI::add_wp_hook( $hook, $wp_cli_filter_active_plugins, 999 ); } WP_CLI::add_wp_hook( 'plugins_loaded', function() use ( $hooks, $wp_cli_filter_active_plugins ) { foreach ( $hooks as $hook ) { remove_filter( $hook, $wp_cli_filter_active_plugins, 999 ); } }, 0 ); } /** * Set up the filters to skip the loaded theme */ public function action_setup_theme_wp_cli_skip_themes() { $wp_cli_filter_active_theme = function( $value ) { $skipped_themes = WP_CLI::get_runner()->config['skip-themes']; if ( true === $skipped_themes ) { return ''; } if ( ! is_array( $skipped_themes ) ) { $skipped_themes = explode( ',', $skipped_themes ); } $checked_value = $value; // Always check against the stylesheet value // This ensures a child theme can be skipped when template differs if ( false !== stripos( current_filter(), 'option_template' ) ) { $checked_value = get_option( 'stylesheet' ); } if ( '' === $checked_value || in_array( $checked_value, $skipped_themes ) ) { return ''; } return $value; }; $hooks = array( 'pre_option_template', 'option_template', 'pre_option_stylesheet', 'option_stylesheet', ); foreach ( $hooks as $hook ) { add_filter( $hook, $wp_cli_filter_active_theme, 999 ); } // Clean up after the TEMPLATEPATH and STYLESHEETPATH constants are defined WP_CLI::add_wp_hook( 'after_setup_theme', function() use ( $hooks, $wp_cli_filter_active_theme ) { foreach ( $hooks as $hook ) { remove_filter( $hook, $wp_cli_filter_active_theme, 999 ); } }, 0 ); } /** * Whether or not this WordPress install is multisite. * * For use after wp-config.php has loaded, but before the rest of WordPress * is loaded. */ private function is_multisite() { if ( defined( 'MULTISITE' ) ) { return MULTISITE; } if ( defined( 'SUBDOMAIN_INSTALL' ) || defined( 'VHOST' ) || defined( 'SUNRISE' ) ) { return true; } return false; } /** * Error handler for `wp_die()` when the command is help to try to trap errors (db connection failure in particular) during WordPress load. */ public function help_wp_die_handler( $message ) { $help_exit_warning = 'Error during WordPress load.'; if ( $message instanceof \WP_Error ) { $help_exit_warning = WP_CLI\Utils\wp_clean_error_message( $message->get_error_message() ); } elseif ( is_string( $message ) ) { $help_exit_warning = WP_CLI\Utils\wp_clean_error_message( $message ); } $this->_run_command_and_exit( $help_exit_warning ); } /** * Check whether there's a WP-CLI update available, and suggest update if so. */ private function auto_check_update() { // `wp cli update` only works with Phars at this time. if ( ! Utils\inside_phar() ) { return; } $existing_phar = realpath( $_SERVER['argv'][0] ); // Phar needs to be writable to be easily updateable. if ( ! is_writable( $existing_phar ) || ! is_writable( dirname( $existing_phar ) ) ) { return; } // Only check for update when a human is operating. if ( ! function_exists( 'posix_isatty' ) || ! posix_isatty( STDOUT ) ) { return; } // Allow hosts and other providers to disable automatic check update. if ( getenv( 'WP_CLI_DISABLE_AUTO_CHECK_UPDATE' ) ) { return; } // Permit configuration of number of days between checks. $days_between_checks = getenv( 'WP_CLI_AUTO_CHECK_UPDATE_DAYS' ); if ( false === $days_between_checks ) { $days_between_checks = 1; } $cache = WP_CLI::get_cache(); $cache_key = 'wp-cli-update-check'; // Bail early on the first check, so we don't always check on an unwritable cache. if ( ! $cache->has( $cache_key ) ) { $cache->write( $cache_key, time() ); return; } // Bail if last check is still within our update check time period. $last_check = (int) $cache->read( $cache_key ); if ( time() - ( 24 * 60 * 60 * $days_between_checks ) < $last_check ) { return; } // In case the operation fails, ensure the timestamp has been updated. $cache->write( $cache_key, time() ); // Check whether any updates are available. ob_start(); WP_CLI::run_command( array( 'cli', 'check-update' ), array( 'format' => 'count', ) ); $count = ob_get_clean(); if ( ! $count ) { return; } // Looks like an update is available, so let's prompt to update. WP_CLI::run_command( array( 'cli', 'update' ) ); // If the Phar was replaced, we can't proceed with the original process. exit; } /** * Get a suggestion on similar (sub)commands when the user entered an * unknown (sub)command. * * @param string $entry User entry that didn't match an * existing command. * @param CompositeCommand $root_command Root command to start search for * suggestions at. * * @return string Suggestion that fits the user entry, or an empty string. */ private function get_subcommand_suggestion( $entry, CompositeCommand $root_command = null ) { $commands = array(); $this->enumerate_commands( $root_command ?: \WP_CLI::get_root_command(), $commands ); return Utils\get_suggestion( $entry, $commands, $threshold = 2 ); } /** * Recursive method to enumerate all known commands. * * @param CompositeCommand $command Composite command to recurse over. * @param array $list Reference to list accumulating results. * @param string $parent Parent command to use as prefix. */ private function enumerate_commands( CompositeCommand $command, array &$list, $parent = '' ) { foreach ( $command->get_subcommands() as $subcommand ) { /** @var CompositeCommand $subcommand */ $command_string = empty( $parent ) ? $subcommand->get_name() : "{$parent} {$subcommand->get_name()}"; $list[] = $command_string; $this->enumerate_commands( $subcommand, $list, $command_string ); } } } 'table', 'fields' => $fields, 'field' => null, ); foreach ( array( 'format', 'fields', 'field' ) as $key ) { if ( isset( $assoc_args[ $key ] ) ) { $format_args[ $key ] = $assoc_args[ $key ]; unset( $assoc_args[ $key ] ); } } if ( ! is_array( $format_args['fields'] ) ) { $format_args['fields'] = explode( ',', $format_args['fields'] ); } $format_args['fields'] = array_map( 'trim', $format_args['fields'] ); $this->args = $format_args; $this->prefix = $prefix; } /** * Magic getter for arguments. * * @param string $key * @return mixed */ public function __get( $key ) { return $this->args[ $key ]; } /** * Display multiple items according to the output arguments. * * @param array $items * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `format()` if items in the table are pre-colorized. Default false. */ public function display_items( $items, $ascii_pre_colorized = false ) { if ( $this->args['field'] ) { $this->show_single_field( $items, $this->args['field'] ); } else { if ( in_array( $this->args['format'], array( 'csv', 'json', 'table' ) ) ) { $item = is_array( $items ) && ! empty( $items ) ? array_shift( $items ) : false; if ( $item && ! empty( $this->args['fields'] ) ) { foreach ( $this->args['fields'] as &$field ) { $field = $this->find_item_key( $item, $field ); } array_unshift( $items, $item ); } } if ( in_array( $this->args['format'], array( 'table', 'csv' ) ) ) { if ( is_object( $items ) && is_a( $items, 'Iterator' ) ) { $items = \WP_CLI\Utils\iterator_map( $items, array( $this, 'transform_item_values_to_json' ) ); } else { $items = array_map( array( $this, 'transform_item_values_to_json' ), $items ); } } $this->format( $items, $ascii_pre_colorized ); } } /** * Display a single item according to the output arguments. * * @param mixed $item * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_multiple_fields()` if the item in the table is pre-colorized. Default false. */ public function display_item( $item, $ascii_pre_colorized = false ) { if ( isset( $this->args['field'] ) ) { $item = (object) $item; $key = $this->find_item_key( $item, $this->args['field'] ); $value = $item->$key; if ( in_array( $this->args['format'], array( 'table', 'csv' ) ) && ( is_object( $value ) || is_array( $value ) ) ) { $value = json_encode( $value ); } \WP_CLI::print_value( $value, array( 'format' => $this->args['format'], ) ); } else { $this->show_multiple_fields( $item, $this->args['format'], $ascii_pre_colorized ); } } /** * Format items according to arguments. * * @param array $items * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if items in the table are pre-colorized. Default false. */ private function format( $items, $ascii_pre_colorized = false ) { $fields = $this->args['fields']; switch ( $this->args['format'] ) { case 'count': if ( ! is_array( $items ) ) { $items = iterator_to_array( $items ); } echo count( $items ); break; case 'ids': if ( ! is_array( $items ) ) { $items = iterator_to_array( $items ); } echo implode( ' ', $items ); break; case 'table': self::show_table( $items, $fields, $ascii_pre_colorized ); break; case 'csv': \WP_CLI\Utils\write_csv( STDOUT, $items, $fields ); break; case 'json': case 'yaml': $out = array(); foreach ( $items as $item ) { $out[] = \WP_CLI\Utils\pick_fields( $item, $fields ); } if ( 'json' === $this->args['format'] ) { if ( defined( 'JSON_PARTIAL_OUTPUT_ON_ERROR' ) ) { echo json_encode( $out, JSON_PARTIAL_OUTPUT_ON_ERROR ); } else { echo json_encode( $out ); } } elseif ( 'yaml' === $this->args['format'] ) { echo Spyc::YAMLDump( $out, 2, 0 ); } break; default: \WP_CLI::error( 'Invalid format: ' . $this->args['format'] ); } } /** * Show a single field from a list of items. * * @param array Array of objects to show fields from * @param string The field to show */ private function show_single_field( $items, $field ) { $key = null; $values = array(); foreach ( $items as $item ) { $item = (object) $item; if ( null === $key ) { $key = $this->find_item_key( $item, $field ); } if ( 'json' == $this->args['format'] ) { $values[] = $item->$key; } else { \WP_CLI::print_value( $item->$key, array( 'format' => $this->args['format'], ) ); } } if ( 'json' == $this->args['format'] ) { echo json_encode( $values ); } } /** * Find an object's key. * If $prefix is set, a key with that prefix will be prioritized. * * @param object $item * @param string $field * @return string $key */ private function find_item_key( $item, $field ) { foreach ( array( $field, $this->prefix . '_' . $field ) as $maybe_key ) { if ( ( is_object( $item ) && ( property_exists( $item, $maybe_key ) || isset( $item->$maybe_key ) ) ) || ( is_array( $item ) && array_key_exists( $maybe_key, $item ) ) ) { $key = $maybe_key; break; } } if ( ! isset( $key ) ) { \WP_CLI::error( "Invalid field: $field." ); } return $key; } /** * Show multiple fields of an object. * * @param object|array $data Data to display * @param string $format Format to display the data in * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if the item in the table is pre-colorized. Default false. */ private function show_multiple_fields( $data, $format, $ascii_pre_colorized = false ) { $true_fields = array(); foreach ( $this->args['fields'] as $field ) { $true_fields[] = $this->find_item_key( $data, $field ); } foreach ( $data as $key => $value ) { if ( ! in_array( $key, $true_fields ) ) { if ( is_array( $data ) ) { unset( $data[ $key ] ); } elseif ( is_object( $data ) ) { unset( $data->$key ); } } } switch ( $format ) { case 'table': case 'csv': $rows = $this->assoc_array_to_rows( $data ); $fields = array( 'Field', 'Value' ); if ( 'table' == $format ) { self::show_table( $rows, $fields, $ascii_pre_colorized ); } elseif ( 'csv' == $format ) { \WP_CLI\Utils\write_csv( STDOUT, $rows, $fields ); } break; case 'yaml': case 'json': \WP_CLI::print_value( $data, array( 'format' => $format, ) ); break; default: \WP_CLI::error( 'Invalid format: ' . $format ); break; } } /** * Show items in a \cli\Table. * * @param array $items * @param array $fields * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `Table::setAsciiPreColorized()` if items in the table are pre-colorized. Default false. */ private static function show_table( $items, $fields, $ascii_pre_colorized = false ) { $table = new \cli\Table(); $enabled = \cli\Colors::shouldColorize(); if ( $enabled ) { \cli\Colors::disable( true ); } $table->setAsciiPreColorized( $ascii_pre_colorized ); $table->setHeaders( $fields ); foreach ( $items as $item ) { $table->addRow( array_values( \WP_CLI\Utils\pick_fields( $item, $fields ) ) ); } foreach ( $table->getDisplayLines() as $line ) { \WP_CLI::line( $line ); } if ( $enabled ) { \cli\Colors::enable( true ); } } /** * Format an associative array as a table. * * @param array $fields Fields and values to format * @return array $rows */ private function assoc_array_to_rows( $fields ) { $rows = array(); foreach ( $fields as $field => $value ) { if ( ! is_string( $value ) ) { $value = json_encode( $value ); } $rows[] = (object) array( 'Field' => $field, 'Value' => $value, ); } return $rows; } /** * Transforms objects and arrays to JSON as necessary * * @param mixed $item * @return mixed */ public function transform_item_values_to_json( $item ) { foreach ( $this->args['fields'] as $field ) { $true_field = $this->find_item_key( $item, $field ); $value = is_object( $item ) ? $item->$true_field : $item[ $true_field ]; if ( is_array( $value ) || is_object( $value ) ) { if ( is_object( $item ) ) { $item->$true_field = json_encode( $value ); } elseif ( is_array( $item ) ) { $item[ $true_field ] = json_encode( $value ); } } } return $item; } } upgrader->strings[ $error ] ) ) { $error = $this->upgrader->strings[ $error ]; } // TODO: show all errors, not just the first one \WP_CLI::warning( $error ); } public function feedback( $string ) { if ( 'parent_theme_prepare_install' === $string ) { \WP_CLI::get_http_cache_manager()->whitelist_package( $this->api->download_link, 'theme', $this->api->slug, $this->api->version ); } if ( isset( $this->upgrader->strings[ $string ] ) ) { $string = $this->upgrader->strings[ $string ]; } if ( strpos( $string, '%' ) !== false ) { $args = func_get_args(); $args = array_splice( $args, 1 ); if ( ! empty( $args ) ) { $string = vsprintf( $string, $args ); } } if ( empty( $string ) ) { return; } $string = str_replace( '…', '...', strip_tags( $string ) ); $string = html_entity_decode( $string, ENT_QUOTES, get_bloginfo( 'charset' ) ); \WP_CLI::log( $string ); } } transformers[] = $fn; } public function current() { $value = parent::current(); foreach ( $this->transformers as $fn ) { $value = call_user_func( $fn, $value ); } return $value; } } * foreach( new Iterators\Table( array( 'table' => $wpdb->posts, 'fields' => array( 'ID', 'post_content' ) ) ) as $post ) { * count_words_for( $post->ID, $post->post_content ); * } * * * * foreach( new Iterators\Table( array( 'table' => $wpdb->posts, 'where' => 'ID = 8 OR post_status = "publish"' ) ) as $post ) { * … * } * * * * foreach( new PostIterator( array( 'table' => $wpdb->posts, 'where' => array( 'post_status' => 'publish', 'post_date_gmt BETWEEN x AND y' ) ) ) as $post ) { * … * } * * * * @param array $args Supported arguments: * table – the name of the database table * fields – an array of columns to get from the table, '*' is a valid value and the default * where – conditions for filtering rows. Supports two formats: * = string – this will be the where clause * = array – each element is treated as a condition if it's positional, or as column => value if * it's a key/value pair. In the latter case the value is automatically quoted and escaped * append - add arbitrary extra SQL */ public function __construct( $args = array() ) { $defaults = array( 'fields' => '*', 'where' => array(), 'append' => '', 'table' => null, 'chunk_size' => 500, ); $table = $args['table']; $args = array_merge( $defaults, $args ); $fields = self::build_fields( $args['fields'] ); $conditions = self::build_where_conditions( $args['where'] ); $where_sql = $conditions ? " WHERE $conditions" : ''; $query = "SELECT $fields FROM `$table` $where_sql {$args['append']}"; parent::__construct( $query, $args['chunk_size'] ); } private static function build_fields( $fields ) { if ( '*' === $fields ) { return $fields; } return implode( ', ', array_map( function ( $v ) { return "`$v`"; }, $fields ) ); } private static function build_where_conditions( $where ) { global $wpdb; if ( is_array( $where ) ) { $conditions = array(); foreach ( $where as $key => $value ) { if ( is_array( $value ) ) { $conditions[] = $key . ' IN (' . esc_sql( implode( ',', $value ) ) . ')'; } elseif ( is_numeric( $key ) ) { $conditions[] = $value; } else { $conditions[] = $key . $wpdb->prepare( ' = %s', $value ); } } $where = implode( ' AND ', $conditions ); } return $where; } } * foreach( new Iterators\Query( 'SELECT * FROM users', 100 ) as $user ) { * tickle( $user ); * } * * * @param string $query The query as a string. It shouldn't include any LIMIT clauses * @param number $chunk_size How many rows to retrieve at once; default value is 500 (optional) */ public function __construct( $query, $chunk_size = 500 ) { $this->query = $query; $this->count_query = preg_replace( '/^.*? FROM /', 'SELECT COUNT(*) FROM ', $query, 1, $replacements ); if ( 1 !== $replacements ) { $this->count_query = ''; } $this->chunk_size = $chunk_size; $this->db = $GLOBALS['wpdb']; } /** * Reduces the offset when the query row count shrinks * * In cases where the iterated rows are being updated such that they will no * longer be returned by the original query, the offset must be reduced to * iterate over all remaining rows. */ private function adjust_offset_for_shrinking_result_set() { if ( empty( $this->count_query ) ) { return; } $row_count = $this->db->get_var( $this->count_query ); if ( $row_count < $this->row_count ) { $this->offset -= $this->row_count - $row_count; } $this->row_count = $row_count; } private function load_items_from_db() { $this->adjust_offset_for_shrinking_result_set(); $query = $this->query . sprintf( ' LIMIT %d OFFSET %d', $this->chunk_size, $this->offset ); $this->results = $this->db->get_results( $query ); if ( ! $this->results ) { if ( $this->db->last_error ) { throw new Exception( 'Database error: ' . $this->db->last_error ); } return false; } $this->offset += $this->chunk_size; return true; } public function current() { return $this->results[ $this->index_in_results ]; } public function key() { return $this->global_index; } public function next() { $this->index_in_results++; $this->global_index++; } public function rewind() { $this->results = array(); $this->global_index = 0; $this->index_in_results = 0; $this->offset = 0; $this->depleted = false; } public function valid() { if ( $this->depleted ) { return false; } if ( ! isset( $this->results[ $this->index_in_results ] ) ) { $items_loaded = $this->load_items_from_db(); if ( ! $items_loaded ) { $this->rewind(); $this->depleted = true; return false; } $this->index_in_results = 0; } return true; } } filePointer = fopen( $filename, 'rb' ); if ( ! $this->filePointer ) { \WP_CLI::error( sprintf( 'Could not open file: %s', $filename ) ); } $this->delimiter = $delimiter; } public function rewind() { rewind( $this->filePointer ); $this->columns = fgetcsv( $this->filePointer, self::ROW_SIZE, $this->delimiter ); $this->currentIndex = -1; $this->next(); } public function current() { return $this->currentElement; } public function key() { return $this->currentIndex; } public function next() { $this->currentElement = false; while ( true ) { $str = fgets( $this->filePointer ); if ( false === $str ) { break; } $row = str_getcsv( $str, $this->delimiter ); $element = array(); foreach ( $this->columns as $i => $key ) { if ( isset( $row[ $i ] ) ) { $element[ $key ] = $row[ $i ]; } } if ( ! empty( $element ) ) { $this->currentElement = $element; $this->currentIndex++; break; } } } public function valid() { return is_array( $this->currentElement ); } } unregister(); } /** * Registers the autoload callback with the SPL autoload system. */ public function register() { spl_autoload_register( array( $this, 'autoload' ) ); } /** * Unregisters the autoload callback with the SPL autoload system. */ public function unregister() { spl_autoload_unregister( array( $this, 'autoload' ) ); } /** * Add a specific namespace structure with our custom autoloader. * * @param string $root Root namespace name. * @param string $base_dir Directory containing the class files. * @param string $prefix Prefix to be added before the class. * @param string $suffix Suffix to be added after the class. * @param boolean $lowercase Whether the class should be changed to * lowercase. * @param boolean $underscores Whether the underscores should be changed to * hyphens. * * @return self */ public function add_namespace( $root, $base_dir, $prefix = '', $suffix = '.php', $lowercase = false, $underscores = false ) { $this->namespaces[] = array( 'root' => $this->normalize_root( (string) $root ), 'base_dir' => $this->add_trailing_slash( (string) $base_dir ), 'prefix' => (string) $prefix, 'suffix' => (string) $suffix, 'lowercase' => (bool) $lowercase, 'underscores' => (bool) $underscores, ); return $this; } /** * The autoload function that gets registered with the SPL Autoloader * system. * * @param string $class The class that got requested by the spl_autoloader. */ public function autoload( $class ) { // Iterate over namespaces to find a match. foreach ( $this->namespaces as $namespace ) { // Move on if the object does not belong to the current namespace. if ( 0 !== strpos( $class, $namespace['root'] ) ) { continue; } // Remove namespace root level to correspond with root filesystem, and // replace the namespace separator "\" by the system-dependent directory separator. $filename = str_replace( array( $namespace['root'], '\\' ), array( '', DIRECTORY_SEPARATOR ), $class ); // Remove a leading backslash from the class name. $filename = $this->remove_leading_backslash( $filename ); // Change to lower case if requested. if ( $namespace['lowercase'] ) { $filename = strtolower( $filename ); } // Change underscores into hyphens if requested. if ( $namespace['underscores'] ) { $filename = str_replace( '_', '-', $filename ); } // Add base_dir, prefix and suffix. $filepath = $namespace['base_dir'] . $namespace['prefix'] . $filename . $namespace['suffix']; // Throw an exception if the file does not exist or is not readable. if ( is_readable( $filepath ) ) { require_once $filepath; } } } /** * Normalize a namespace root. * * @param string $root Namespace root that needs to be normalized. * * @return string Normalized namespace root. */ protected function normalize_root( $root ) { $root = $this->remove_leading_backslash( $root ); return $this->add_trailing_backslash( $root ); } /** * Remove a leading backslash from a string. * * @param string $string String to remove the leading backslash from. * * @return string Modified string. */ protected function remove_leading_backslash( $string ) { return ltrim( $string, '\\' ); } /** * Make sure a string ends with a trailing backslash. * * @param string $string String to check the trailing backslash of. * * @return string Modified string. */ protected function add_trailing_backslash( $string ) { return rtrim( $string, '\\' ) . '\\'; } /** * Make sure a string ends with a trailing slash. * * @param string $string String to check the trailing slash of. * * @return string Modified string. */ protected function add_trailing_slash( $string ) { return rtrim( $string, '/\\' ) . '/'; } } ..." * into [ optional=>false, type=>positional, repeating=>true, name=>object-id ] */ class SynopsisParser { /** * @param string A synopsis * @return array List of parameters */ public static function parse( $synopsis ) { $tokens = array_filter( preg_split( '/[\s\t]+/', $synopsis ) ); $params = array(); foreach ( $tokens as $token ) { $param = self::classify_token( $token ); // Some types of parameters shouldn't be mandatory if ( isset( $param['optional'] ) && ! $param['optional'] ) { if ( 'flag' === $param['type'] || ( 'assoc' === $param['type'] && $param['value']['optional'] ) ) { $param['type'] = 'unknown'; } } $param['token'] = $token; $params[] = $param; } return $params; } /** * @param array A structured synopsis * @return string Rendered synopsis */ public static function render( $synopsis ) { if ( ! is_array( $synopsis ) ) { return ''; } $bits = array( 'positional' => '', 'assoc' => '', 'generic' => '', 'flag' => '', ); foreach ( $bits as $key => &$value ) { foreach ( $synopsis as $arg ) { if ( empty( $arg['type'] ) || $key !== $arg['type'] ) { continue; } if ( empty( $arg['name'] ) && 'generic' !== $arg['type'] ) { continue; } if ( 'positional' === $key ) { $rendered_arg = "<{$arg['name']}>"; } elseif ( 'assoc' === $key ) { $arg_value = isset( $arg['value']['name'] ) ? $arg['value']['name'] : $arg['name']; $rendered_arg = "--{$arg['name']}=<{$arg_value}>"; } elseif ( 'generic' === $key ) { $rendered_arg = '--='; } elseif ( 'flag' === $key ) { $rendered_arg = "--{$arg['name']}"; } if ( ! empty( $arg['repeating'] ) ) { $rendered_arg = "{$rendered_arg}..."; } if ( ! empty( $arg['optional'] ) ) { $rendered_arg = "[{$rendered_arg}]"; } $value .= "{$rendered_arg} "; } } $rendered = ''; foreach ( $bits as $v ) { if ( ! empty( $v ) ) { $rendered .= $v; } } return rtrim( $rendered, ' ' ); } /** * Classify argument attributes based on its syntax. * * @param string $token * @return array $param */ private static function classify_token( $token ) { $param = array(); list( $param['optional'], $token ) = self::is_optional( $token ); list( $param['repeating'], $token ) = self::is_repeating( $token ); $p_name = '([a-z-_0-9]+)'; $p_value = '([a-zA-Z-_|,0-9]+)'; if ( '--=' === $token ) { $param['type'] = 'generic'; } elseif ( preg_match( "/^<($p_value)>$/", $token, $matches ) ) { $param['type'] = 'positional'; $param['name'] = $matches[1]; } elseif ( preg_match( "/^--(?:\\[no-\\])?$p_name/", $token, $matches ) ) { $param['name'] = $matches[1]; $value = substr( $token, strlen( $matches[0] ) ); // substr returns false <= PHP 5.6, and '' PHP 7+ if ( false === $value || '' === $value ) { $param['type'] = 'flag'; } else { $param['type'] = 'assoc'; list( $param['value']['optional'], $value ) = self::is_optional( $value ); if ( preg_match( "/^=<$p_value>$/", $value, $matches ) ) { $param['value']['name'] = $matches[1]; } else { $param = array( 'type' => 'unknown', ); } } } else { $param['type'] = 'unknown'; } return $param; } /** * An optional parameter is surrounded by square brackets. * * @param string $token * @return array */ private static function is_optional( $token ) { if ( '[' == substr( $token, 0, 1 ) && ']' == substr( $token, -1 ) ) { return array( true, substr( $token, 1, -1 ) ); } return array( false, $token ); } /** * A repeating parameter is followed by an ellipsis. * * @param string $token * @return array */ private static function is_repeating( $token ) { if ( '...' === substr( $token, -3 ) ) { return array( true, substr( $token, 0, -3 ) ); } return array( false, $token ); } } parent = false; $this->name = 'wp'; $this->shortdesc = 'Manage WordPress through the command-line.'; } /** * Get the human-readable long description. * * @return string */ public function get_longdesc() { return $this->get_global_params( true ); } /** * Find a subcommand registered on the root * command. * * @param array $args * @return \WP_CLI\Dispatcher\Subcommand|false */ public function find_subcommand( &$args ) { $command = array_shift( $args ); Utils\load_command( $command ); if ( ! isset( $this->subcommands[ $command ] ) ) { return false; } return $this->subcommands[ $command ]; } /** * Get all registered subcommands. * * @return array */ public function get_subcommands() { return parent::get_subcommands(); } } getMethod( $callable[1] ) ); } else { $reflection = new \ReflectionClass( $callable ); if ( $reflection->hasMethod( '__invoke' ) ) { $class = is_object( $callable ) ? $callable : $reflection->name; $command = self::create_subcommand( $parent, $name, array( $class, '__invoke' ), $reflection->getMethod( '__invoke' ) ); } else { $command = self::create_composite_command( $parent, $name, $callable ); } } return $command; } /** * Clear the file contents cache. */ public static function clear_file_contents_cache() { self::$file_contents = array(); } /** * Create a new Subcommand instance. * * @param mixed $parent The new command's parent Composite command * @param string $name Represents how the command should be invoked * @param mixed $callable A callable function or closure, or class name and method * @param object $reflection Reflection instance, for doc parsing * @param string $class A subclass of WP_CLI_Command * @param string $method Class method to be called upon invocation. */ private static function create_subcommand( $parent, $name, $callable, $reflection ) { $doc_comment = self::get_doc_comment( $reflection ); $docparser = new \WP_CLI\DocParser( $doc_comment ); if ( is_array( $callable ) ) { if ( ! $name ) { $name = $docparser->get_tag( 'subcommand' ); } if ( ! $name ) { $name = $reflection->name; } } if ( ! $doc_comment ) { \WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); } $when_invoked = function ( $args, $assoc_args ) use ( $callable ) { if ( is_array( $callable ) ) { $callable[0] = is_object( $callable[0] ) ? $callable[0] : new $callable[0]; call_user_func( array( $callable[0], $callable[1] ), $args, $assoc_args ); } else { call_user_func( $callable, $args, $assoc_args ); } }; return new Subcommand( $parent, $name, $docparser, $when_invoked ); } /** * Create a new Composite command instance. * * @param mixed $parent The new command's parent Root or Composite command * @param string $name Represents how the command should be invoked * @param mixed $callable */ private static function create_composite_command( $parent, $name, $callable ) { $reflection = new \ReflectionClass( $callable ); $doc_comment = self::get_doc_comment( $reflection ); if ( ! $doc_comment ) { \WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); } $docparser = new \WP_CLI\DocParser( $doc_comment ); $container = new CompositeCommand( $parent, $name, $docparser ); foreach ( $reflection->getMethods() as $method ) { if ( ! self::is_good_method( $method ) ) { continue; } $class = is_object( $callable ) ? $callable : $reflection->name; $subcommand = self::create_subcommand( $container, false, array( $class, $method->name ), $method ); $subcommand_name = $subcommand->get_name(); $container->add_subcommand( $subcommand_name, $subcommand ); } return $container; } /** * Check whether a method is actually callable. * * @param ReflectionMethod $method * @return bool */ private static function is_good_method( $method ) { return $method->isPublic() && ! $method->isStatic() && 0 !== strpos( $method->getName(), '__' ); } /** * Gets the document comment. Caters for PHP directive `opcache.save comments` being disabled. * * @param ReflectionMethod|ReflectionClass|ReflectionFunction $reflection Reflection instance. * @return string|false|null Doc comment string if any, false if none (same as `Reflection*::getDocComment()`), null if error. */ private static function get_doc_comment( $reflection ) { $doc_comment = $reflection->getDocComment(); if ( false !== $doc_comment || ! ( ini_get( 'opcache.enable_cli' ) && ! ini_get( 'opcache.save_comments' ) ) ) { // Either have doc comment, or no doc comment and save comments enabled - standard situation. if ( ! getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ) ) { return $doc_comment; } } $filename = $reflection->getFileName(); if ( isset( self::$file_contents[ $filename ] ) ) { $contents = self::$file_contents[ $filename ]; } elseif ( is_readable( $filename ) && ( $contents = file_get_contents( $filename ) ) ) { self::$file_contents[ $filename ] = $contents = explode( "\n", $contents ); } else { \WP_CLI::debug( "Could not read contents for filename '{$filename}'.", 'commandfactory' ); return null; } return self::extract_last_doc_comment( implode( "\n", array_slice( $contents, 0, $reflection->getStartLine() ) ) ); } /** * Returns the last doc comment if any in `$content`. * * @param string $content The content, which should end at the class or function declaration. * @return string|bool The last doc comment if any, or false if none. */ private static function extract_last_doc_comment( $content ) { $content = trim( $content ); $comment_end_pos = strrpos( $content, '*/' ); if ( false === $comment_end_pos ) { return false; } // Make sure comment end belongs to this class/function. if ( preg_match_all( '/(?:^|[\s;}])(?:class|function)\s+/', substr( $content, $comment_end_pos + 2 ), $dummy /*needed for PHP 5.3*/ ) > 1 ) { return false; } $content = substr( $content, 0, $comment_end_pos + 2 ); if ( false === ( $comment_start_pos = strrpos( $content, '/**' ) ) || $comment_start_pos + 2 === $comment_end_pos ) { return false; } // Make sure comment start belongs to this comment end. if ( false !== ( $comment_end2_pos = strpos( substr( $content, $comment_start_pos ), '*/' ) ) && $comment_start_pos + $comment_end2_pos < $comment_end_pos ) { return false; } // Allow for '/**' within doc comment. $subcontent = substr( $content, 0, $comment_start_pos ); while ( false !== ( $comment_start2_pos = strrpos( $subcontent, '/**' ) ) && false === strpos( $subcontent, '*/', $comment_start2_pos ) ) { $comment_start_pos = $comment_start2_pos; $subcontent = substr( $subcontent, 0, $comment_start_pos ); } return substr( $content, $comment_start_pos, $comment_end_pos + 2 ); } } abort = true; $this->reason = (string) $reason; } /** * Check whether the command addition was aborted. * * @return bool */ public function was_aborted() { return $this->abort; } /** * Get the reason as to why the addition was aborted. * * @return string */ public function get_reason() { return $this->reason; } } when_invoked = $when_invoked; $this->alias = $docparser->get_tag( 'alias' ); $this->synopsis = $docparser->get_synopsis(); if ( ! $this->synopsis && $this->longdesc ) { $this->synopsis = self::extract_synopsis( $this->longdesc ); } } /** * Extract the synopsis from PHPdoc string. * * @param string $longdesc Command docs via PHPdoc * @return string */ private static function extract_synopsis( $longdesc ) { preg_match_all( '/(.+?)[\r\n]+:/', $longdesc, $matches ); return implode( ' ', $matches[1] ); } /** * Subcommands can't have subcommands because they * represent code to be executed. * * @return bool */ public function can_have_subcommands() { return false; } /** * Get the synopsis string for this subcommand. * A synopsis defines what runtime arguments are * expected, useful to humans and argument validation. * * @return string */ public function get_synopsis() { return $this->synopsis; } /** * Set the synopsis string for this subcommand. * * @param string */ public function set_synopsis( $synopsis ) { $this->synopsis = $synopsis; } /** * If an alias is set, grant access to it. * Aliases permit subcommands to be instantiated * with a secondary identity. * * @return string */ public function get_alias() { return $this->alias; } /** * Print the usage details to the end user. * * @param string $prefix */ public function show_usage( $prefix = 'usage: ' ) { \WP_CLI::line( $this->get_usage( $prefix ) ); } /** * Get the usage of the subcommand as a formatted string. * * @param string $prefix * @return string */ public function get_usage( $prefix ) { return sprintf( '%s%s %s', $prefix, implode( ' ', get_path( $this ) ), $this->get_synopsis() ); } /** * Wrapper for CLI Tools' prompt() method. * * @param string $question * @param string $default * @return string|false */ private function prompt( $question, $default ) { $question .= ': '; if ( function_exists( 'readline' ) ) { return readline( $question ); } echo $question; return stream_get_line( STDIN, 1024, PHP_EOL ); } /** * Interactively prompt the user for input * based on defined synopsis and passed arguments. * * @param array $args * @param array $assoc_args * @return array */ private function prompt_args( $args, $assoc_args ) { $synopsis = $this->get_synopsis(); if ( ! $synopsis ) { return array( $args, $assoc_args ); } $spec = array_filter( \WP_CLI\SynopsisParser::parse( $synopsis ), function( $spec_arg ) { return in_array( $spec_arg['type'], array( 'generic', 'positional', 'assoc', 'flag' ) ); } ); $spec = array_values( $spec ); $prompt_args = WP_CLI::get_config( 'prompt' ); if ( true !== $prompt_args ) { $prompt_args = explode( ',', $prompt_args ); } // 'positional' arguments are positional (aka zero-indexed) // so $args needs to be reset before prompting for new arguments $args = array(); foreach ( $spec as $key => $spec_arg ) { // When prompting for specific arguments (e.g. --prompt=user_pass), // ignore all arguments that don't match. if ( is_array( $prompt_args ) ) { if ( 'assoc' !== $spec_arg['type'] ) { continue; } if ( ! in_array( $spec_arg['name'], $prompt_args, true ) ) { continue; } } $current_prompt = ( $key + 1 ) . '/' . count( $spec ) . ' '; $default = $spec_arg['optional'] ? '' : false; // 'generic' permits arbitrary key=value (e.g. [--=] ) if ( 'generic' == $spec_arg['type'] ) { list( $key_token, $value_token ) = explode( '=', $spec_arg['token'] ); $repeat = false; do { if ( ! $repeat ) { $key_prompt = $current_prompt . $key_token; } else { $key_prompt = str_repeat( ' ', strlen( $current_prompt ) ) . $key_token; } $key = $this->prompt( $key_prompt, $default ); if ( false === $key ) { return array( $args, $assoc_args ); } if ( $key ) { $key_prompt_count = strlen( $key_prompt ) - strlen( $value_token ) - 1; $value_prompt = str_repeat( ' ', $key_prompt_count ) . '=' . $value_token; $value = $this->prompt( $value_prompt, $default ); if ( false === $value ) { return array( $args, $assoc_args ); } $assoc_args[ $key ] = $value; $repeat = true; } else { $repeat = false; } } while ( $repeat ); } else { $prompt = $current_prompt . $spec_arg['token']; if ( 'flag' == $spec_arg['type'] ) { $prompt .= ' (Y/n)'; } $response = $this->prompt( $prompt, $default ); if ( false === $response ) { return array( $args, $assoc_args ); } if ( $response ) { switch ( $spec_arg['type'] ) { case 'positional': if ( $spec_arg['repeating'] ) { $response = explode( ' ', $response ); } else { $response = array( $response ); } $args = array_merge( $args, $response ); break; case 'assoc': $assoc_args[ $spec_arg['name'] ] = $response; break; case 'flag': if ( 'Y' == strtoupper( $response ) ) { $assoc_args[ $spec_arg['name'] ] = true; } break; } } } } return array( $args, $assoc_args ); } /** * Validate the supplied arguments to the command. * Throws warnings or errors if arguments are missing * or invalid. * * @param array $args * @param array $assoc_args * @param array $extra_args * @return array list of invalid $assoc_args keys to unset */ private function validate_args( $args, $assoc_args, $extra_args ) { $synopsis = $this->get_synopsis(); if ( ! $synopsis ) { return array( array(), $args, $assoc_args, $extra_args ); } $validator = new \WP_CLI\SynopsisValidator( $synopsis ); $cmd_path = implode( ' ', get_path( $this ) ); foreach ( $validator->get_unknown() as $token ) { \WP_CLI::warning( sprintf( 'The `%s` command has an invalid synopsis part: %s', $cmd_path, $token ) ); } if ( ! $validator->enough_positionals( $args ) ) { $this->show_usage(); exit( 1 ); } $unknown_positionals = $validator->unknown_positionals( $args ); if ( ! empty( $unknown_positionals ) ) { \WP_CLI::error( 'Too many positional arguments: ' . implode( ' ', $unknown_positionals ) ); } $synopsis_spec = \WP_CLI\SynopsisParser::parse( $synopsis ); $i = 0; $errors = array( 'fatal' => array(), 'warning' => array(), ); $mock_doc = array( $this->get_shortdesc(), '' ); $mock_doc = array_merge( $mock_doc, explode( "\n", $this->get_longdesc() ) ); $mock_doc = '/**' . PHP_EOL . '* ' . implode( PHP_EOL . '* ', $mock_doc ) . PHP_EOL . '*/'; $docparser = new \WP_CLI\DocParser( $mock_doc ); foreach ( $synopsis_spec as $spec ) { if ( 'positional' === $spec['type'] ) { $spec_args = $docparser->get_arg_args( $spec['name'] ); if ( ! isset( $args[ $i ] ) ) { if ( isset( $spec_args['default'] ) ) { $args[ $i ] = $spec_args['default']; } } if ( isset( $spec_args['options'] ) ) { if ( ! empty( $spec['repeating'] ) ) { do { if ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { \WP_CLI::error( 'Invalid value specified for positional arg.' ); } $i++; } while ( isset( $args[ $i ] ) ); } else { if ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { \WP_CLI::error( 'Invalid value specified for positional arg.' ); } } } $i++; } elseif ( 'assoc' === $spec['type'] ) { $spec_args = $docparser->get_param_args( $spec['name'] ); if ( ! isset( $assoc_args[ $spec['name'] ] ) && ! isset( $extra_args[ $spec['name'] ] ) ) { if ( isset( $spec_args['default'] ) ) { $assoc_args[ $spec['name'] ] = $spec_args['default']; } } if ( isset( $assoc_args[ $spec['name'] ] ) && isset( $spec_args['options'] ) ) { if ( ! in_array( $assoc_args[ $spec['name'] ], $spec_args['options'] ) ) { $errors['fatal'][ $spec['name'] ] = "Invalid value specified for '{$spec['name']}'"; } } } } list( $returned_errors, $to_unset ) = $validator->validate_assoc( array_merge( \WP_CLI::get_config(), $extra_args, $assoc_args ) ); foreach ( array( 'fatal', 'warning' ) as $error_type ) { $errors[ $error_type ] = array_merge( $errors[ $error_type ], $returned_errors[ $error_type ] ); } if ( 'help' !== $this->name ) { foreach ( $validator->unknown_assoc( $assoc_args ) as $key ) { $suggestion = Utils\get_suggestion( $key, $this->get_parameters( $synopsis_spec ), $threshold = 2 ); $errors['fatal'][] = sprintf( 'unknown --%s parameter%s', $key, ! empty( $suggestion ) ? PHP_EOL . "Did you mean '--{$suggestion}'?" : '' ); } } if ( ! empty( $errors['fatal'] ) ) { $out = 'Parameter errors:'; foreach ( $errors['fatal'] as $key => $error ) { $out .= "\n {$error}"; if ( $desc = $docparser->get_param_desc( $key ) ) { $out .= " ({$desc})"; } } \WP_CLI::error( $out ); } array_map( '\\WP_CLI::warning', $errors['warning'] ); return array( $to_unset, $args, $assoc_args, $extra_args ); } /** * Invoke the subcommand with the supplied arguments. * Given a --prompt argument, interactively request input * from the end user. * * @param array $args * @param array $assoc_args */ public function invoke( $args, $assoc_args, $extra_args ) { static $prompted_once = false; if ( \WP_CLI::get_config( 'prompt' ) && ! $prompted_once ) { list( $_args, $assoc_args ) = $this->prompt_args( $args, $assoc_args ); $args = array_merge( $args, $_args ); $prompted_once = true; } $extra_positionals = array(); foreach ( $extra_args as $k => $v ) { if ( is_numeric( $k ) ) { if ( ! isset( $args[ $k ] ) ) { $extra_positionals[ $k ] = $v; } unset( $extra_args[ $k ] ); } } $args += $extra_positionals; list( $to_unset, $args, $assoc_args, $extra_args ) = $this->validate_args( $args, $assoc_args, $extra_args ); foreach ( $to_unset as $key ) { unset( $assoc_args[ $key ] ); } $path = get_path( $this->get_parent() ); $parent = implode( ' ', array_slice( $path, 1 ) ); $cmd = $this->name; if ( $parent ) { WP_CLI::do_hook( "before_invoke:{$parent}" ); $cmd = $parent . ' ' . $cmd; } WP_CLI::do_hook( "before_invoke:{$cmd}" ); call_user_func( $this->when_invoked, $args, array_merge( $extra_args, $assoc_args ) ); if ( $parent ) { WP_CLI::do_hook( "after_invoke:{$parent}" ); } WP_CLI::do_hook( "after_invoke:{$cmd}" ); } /** * Get an array of parameter names, by merging the command-specific and the * global parameters. * * @param array $spec Optional. Specification of the current command. * * @return array Array of parameter names */ private function get_parameters( $spec = array() ) { $local_parameters = array_column( $spec, 'name' ); $global_parameters = array_column( WP_CLI\SynopsisParser::parse( $this->get_global_params() ), 'name' ); return array_unique( array_merge( $local_parameters, $global_parameters ) ); } } parent = $parent; $this->name = $name; $this->shortdesc = $docparser->get_shortdesc(); $this->longdesc = $docparser->get_longdesc(); $this->docparser = $docparser; $when_to_invoke = $docparser->get_tag( 'when' ); if ( $when_to_invoke ) { \WP_CLI::get_runner()->register_early_invoke( $when_to_invoke, $this ); } } /** * Get the parent composite (or root) command * * @return mixed */ public function get_parent() { return $this->parent; } /** * Add a named subcommand to this composite command's * set of contained subcommands. * * @param string $name Represents how subcommand should be invoked * @param \WP_CLI\Dispatcher\Subcommand */ public function add_subcommand( $name, $command ) { $this->subcommands[ $name ] = $command; } /** * Remove a named subcommand from this composite command's set of contained * subcommands * * @param string $name Represents how subcommand should be invoked */ public function remove_subcommand( $name ) { if ( isset( $this->subcommands[ $name ] ) ) { unset( $this->subcommands[ $name ] ); } } /** * Composite commands always contain subcommands. * * @return true */ public function can_have_subcommands() { return true; } /** * Get the subcommands contained by this composite * command. * * @return array */ public function get_subcommands() { ksort( $this->subcommands ); return $this->subcommands; } /** * Get the name of this composite command. * * @return string */ public function get_name() { return $this->name; } /** * Get the short description for this composite * command. * * @return string */ public function get_shortdesc() { return $this->shortdesc; } /** * Set the short description for this composite command. * * @param string */ public function set_shortdesc( $shortdesc ) { $this->shortdesc = $shortdesc; } /** * Get the long description for this composite * command. * * @return string */ public function get_longdesc() { return $this->longdesc . $this->get_global_params(); } /** * Set the long description for this composite command * * @param string */ public function set_longdesc( $longdesc ) { $this->longdesc = $longdesc; } /** * Get the synopsis for this composite command. * As a collection of subcommands, the composite * command is only intended to invoke those * subcommands. * * @return string */ public function get_synopsis() { return ''; } /** * Get the usage for this composite command. * * @return string */ public function get_usage( $prefix ) { return sprintf( '%s%s %s', $prefix, implode( ' ', get_path( $this ) ), $this->get_synopsis() ); } /** * Show the usage for all subcommands contained * by the composite command. */ public function show_usage() { $methods = $this->get_subcommands(); $i = 0; foreach ( $methods as $name => $subcommand ) { $prefix = ( 0 == $i++ ) ? 'usage: ' : ' or: '; if ( \WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) { continue; } \WP_CLI::line( $subcommand->get_usage( $prefix ) ); } $cmd_name = implode( ' ', array_slice( get_path( $this ), 1 ) ); \WP_CLI::line(); \WP_CLI::line( "See 'wp help $cmd_name ' for more information on a specific command." ); } /** * When a composite command is invoked, it shows usage * docs for its subcommands. * * @param array $args * @param array $assoc_args * @param array $extra_args */ public function invoke( $args, $assoc_args, $extra_args ) { $this->show_usage(); } /** * Given supplied arguments, find a contained * subcommand * * @param array $args * @return \WP_CLI\Dispatcher\Subcommand|false */ public function find_subcommand( &$args ) { $name = array_shift( $args ); $subcommands = $this->get_subcommands(); if ( ! isset( $subcommands[ $name ] ) ) { $aliases = self::get_aliases( $subcommands ); if ( isset( $aliases[ $name ] ) ) { $name = $aliases[ $name ]; } } if ( ! isset( $subcommands[ $name ] ) ) { return false; } return $subcommands[ $name ]; } /** * Get any registered aliases for this composite command's * subcommands. * * @param array $subcommands * @return array */ private static function get_aliases( $subcommands ) { $aliases = array(); foreach ( $subcommands as $name => $subcommand ) { $alias = $subcommand->get_alias(); if ( $alias ) { $aliases[ $alias ] = $name; } } return $aliases; } /** * Composite commands can only be known by one name. * * @return false */ public function get_alias() { return false; } /*** * Get the list of global parameters * * @param string $root_command whether to include or not root command specific description * @return string */ protected function get_global_params( $root_command = false ) { $binding = array(); $binding['root_command'] = $root_command; if ( ! $this->can_have_subcommands() || ( is_object( $this->parent ) && get_class( $this->parent ) == 'WP_CLI\Dispatcher\CompositeCommand' ) ) { $binding['is_subcommand'] = true; } foreach ( \WP_CLI::get_configurator()->get_spec() as $key => $details ) { if ( false === $details['runtime'] ) { continue; } if ( isset( $details['deprecated'] ) ) { continue; } if ( isset( $details['hidden'] ) ) { continue; } if ( true === $details['runtime'] ) { $synopsis = "--[no-]$key"; } else { $synopsis = "--$key" . $details['runtime']; } $binding['parameters'][] = array( 'synopsis' => $synopsis, 'desc' => $details['desc'], ); } if ( $this->get_subcommands() ) { $binding['has_subcommands'] = true; } return Utils\mustache_render( 'man-params.mustache', $binding ); } } * Jordi Boggiano */ namespace WP_CLI; use Symfony\Component\Finder\Finder; /** * Reads/writes to a filesystem cache */ class FileCache { /** * @var string cache path */ protected $root; /** * @var bool */ protected $enabled = true; /** * @var int files time to live */ protected $ttl; /** * @var int max total size */ protected $maxSize; /** * @var string key allowed chars (regex class) */ protected $whitelist; /** * @param string $cacheDir location of the cache * @param int $ttl cache files default time to live (expiration) * @param int $maxSize max total cache size * @param string $whitelist List of characters that are allowed in path names (used in a regex character class) */ public function __construct( $cacheDir, $ttl, $maxSize, $whitelist = 'a-z0-9._-' ) { $this->root = Utils\trailingslashit( $cacheDir ); $this->ttl = (int) $ttl; $this->maxSize = (int) $maxSize; $this->whitelist = $whitelist; if ( ! $this->ensure_dir_exists( $this->root ) ) { $this->enabled = false; } } /** * Cache is enabled * * @return bool */ public function is_enabled() { return $this->enabled; } /** * Cache root * * @return string */ public function get_root() { return $this->root; } /** * Check if a file is in cache and return its filename * * @param string $key cache key * @param int $ttl time to live * @return bool|string filename or false */ public function has( $key, $ttl = null ) { if ( ! $this->enabled ) { return false; } $filename = $this->filename( $key ); if ( ! file_exists( $filename ) ) { return false; } // use ttl param or global ttl if ( null === $ttl ) { $ttl = $this->ttl; } elseif ( $this->ttl > 0 ) { $ttl = min( (int) $ttl, $this->ttl ); } else { $ttl = (int) $ttl; } // if ( $ttl > 0 && filemtime( $filename ) + $ttl < time() ) { if ( $this->ttl > 0 && $ttl >= $this->ttl ) { unlink( $filename ); } return false; } return $filename; } /** * Write to cache file * * @param string $key cache key * @param string $contents file contents * @return bool */ public function write( $key, $contents ) { $filename = $this->prepare_write( $key ); if ( $filename ) { return file_put_contents( $filename, $contents ) && touch( $filename ); } return false; } /** * Read from cache file * * @param string $key cache key * @param int $ttl time to live * @return bool|string file contents or false */ public function read( $key, $ttl = null ) { $filename = $this->has( $key, $ttl ); if ( $filename ) { return file_get_contents( $filename ); } return false; } /** * Copy a file into the cache * * @param string $key cache key * @param string $source source filename * @return bool */ public function import( $key, $source ) { $filename = $this->prepare_write( $key ); if ( $filename ) { return copy( $source, $filename ) && touch( $filename ); } return false; } /** * Copy a file out of the cache * * @param string $key cache key * @param string $target target filename * @param int $ttl time to live * @return bool */ public function export( $key, $target, $ttl = null ) { $filename = $this->has( $key, $ttl ); if ( $filename ) { return copy( $filename, $target ); } return false; } /** * Remove file from cache * * @param string $key cache key * @return bool */ public function remove( $key ) { if ( ! $this->enabled ) { return false; } $filename = $this->filename( $key ); if ( file_exists( $filename ) ) { return unlink( $filename ); } return false; } /** * Clean cache based on time to live and max size * * @return bool */ public function clean() { if ( ! $this->enabled ) { return false; } $ttl = $this->ttl; $maxSize = $this->maxSize; // unlink expired files if ( $ttl > 0 ) { try { $expire = new \DateTime(); } catch ( \Exception $e ) { \WP_CLI::error( $e->getMessage() ); } $expire->modify( '-' . $ttl . ' seconds' ); $finder = $this->get_finder()->date( 'until ' . $expire->format( 'Y-m-d H:i:s' ) ); foreach ( $finder as $file ) { unlink( $file->getRealPath() ); } } // unlink older files if max cache size is exceeded if ( $maxSize > 0 ) { $files = array_reverse( iterator_to_array( $this->get_finder()->sortByAccessedTime()->getIterator() ) ); $total = 0; foreach ( $files as $file ) { if ( $total + $file->getSize() <= $maxSize ) { $total += $file->getSize(); } else { unlink( $file->getRealPath() ); } } } return true; } /** * Ensure directory exists * * @param string $dir directory * @return bool */ protected function ensure_dir_exists( $dir ) { if ( ! is_dir( $dir ) ) { if ( file_exists( $dir ) ) { // exists and not a dir return false; } if ( ! mkdir( $dir, 0777, true ) ) { return false; } } return true; } /** * Prepare cache write * * @param string $key cache key * @return bool|string filename or false */ protected function prepare_write( $key ) { if ( ! $this->enabled ) { return false; } $filename = $this->filename( $key ); if ( ! $this->ensure_dir_exists( dirname( $filename ) ) ) { return false; } return $filename; } /** * Validate cache key * * @param string $key cache key * @return string relative filename */ protected function validate_key( $key ) { $url_parts = parse_url( $key ); if ( ! empty( $url_parts['scheme'] ) ) { // is url $parts = array( 'misc' ); $parts[] = $url_parts['scheme'] . '-' . $url_parts['host'] . ( empty( $url_parts['port'] ) ? '' : '-' . $url_parts['port'] ); $parts[] = substr( $url_parts['path'], 1 ) . ( empty( $url_parts['query'] ) ? '' : '-' . $url_parts['query'] ); } else { $key = str_replace( '\\', '/', $key ); $parts = explode( '/', ltrim( $key ) ); } $parts = preg_replace( "#[^{$this->whitelist}]#i", '-', $parts ); return implode( '/', $parts ); } /** * Filename from key * * @param string $key * @return string filename */ protected function filename( $key ) { return $this->root . $this->validate_key( $key ); } /** * Get a Finder that iterates in cache root only the files * * @return Finder */ protected function get_finder() { return Finder::create()->in( $this->root )->files(); } } spec = include $path; $defaults = array( 'runtime' => false, 'file' => false, 'synopsis' => '', 'default' => null, 'multiple' => false, ); foreach ( $this->spec as $key => &$details ) { $details = array_merge( $defaults, $details ); $this->config[ $key ] = $details['default']; } } /** * Get declared configuration values as an array. * * @return array */ public function to_array() { return array( $this->config, $this->extra_config ); } /** * Get configuration specification, i.e. list of accepted keys. * * @return array */ public function get_spec() { return $this->spec; } /** * Get any aliases defined in config files. * * @return array */ public function get_aliases() { if ( $runtime_alias = getenv( 'WP_CLI_RUNTIME_ALIAS' ) ) { $returned_aliases = array(); foreach ( json_decode( $runtime_alias, true ) as $key => $value ) { if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { $returned_aliases[ $key ] = array(); foreach ( self::$alias_spec as $i ) { if ( isset( $value[ $i ] ) ) { $returned_aliases[ $key ][ $i ] = $value[ $i ]; } } } } return $returned_aliases; } return $this->aliases; } /** * Splits a list of arguments into positional, associative and config. * * @param array(string) * @return array(array) */ public function parse_args( $arguments ) { list( $positional_args, $mixed_args, $global_assoc, $local_assoc ) = self::extract_assoc( $arguments ); list( $assoc_args, $runtime_config ) = $this->unmix_assoc_args( $mixed_args, $global_assoc, $local_assoc ); return array( $positional_args, $assoc_args, $runtime_config ); } /** * Splits positional args from associative args. * * @param array * @return array(array) */ public static function extract_assoc( $arguments ) { $positional_args = $assoc_args = $global_assoc = $local_assoc = array(); foreach ( $arguments as $arg ) { $positional_arg = $assoc_arg = null; if ( preg_match( '|^--no-([^=]+)$|', $arg, $matches ) ) { $assoc_arg = array( $matches[1], false ); } elseif ( preg_match( '|^--([^=]+)$|', $arg, $matches ) ) { $assoc_arg = array( $matches[1], true ); } elseif ( preg_match( '|^--([^=]+)=(.*)|s', $arg, $matches ) ) { $assoc_arg = array( $matches[1], $matches[2] ); } else { $positional = $arg; } if ( ! is_null( $assoc_arg ) ) { $assoc_args[] = $assoc_arg; if ( count( $positional_args ) ) { $local_assoc[] = $assoc_arg; } else { $global_assoc[] = $assoc_arg; } } elseif ( ! is_null( $positional ) ) { $positional_args[] = $positional; } } return array( $positional_args, $assoc_args, $global_assoc, $local_assoc ); } /** * Separate runtime parameters from command-specific parameters. * * @param array $mixed_args * @return array */ private function unmix_assoc_args( $mixed_args, $global_assoc = array(), $local_assoc = array() ) { $assoc_args = $runtime_config = array(); if ( getenv( 'WP_CLI_STRICT_ARGS_MODE' ) ) { foreach ( $global_assoc as $tmp ) { list( $key, $value ) = $tmp; if ( isset( $this->spec[ $key ] ) && false !== $this->spec[ $key ]['runtime'] ) { $this->assoc_arg_to_runtime_config( $key, $value, $runtime_config ); } } foreach ( $local_assoc as $tmp ) { $assoc_args[ $tmp[0] ] = $tmp[1]; } } else { foreach ( $mixed_args as $tmp ) { list( $key, $value ) = $tmp; if ( isset( $this->spec[ $key ] ) && false !== $this->spec[ $key ]['runtime'] ) { $this->assoc_arg_to_runtime_config( $key, $value, $runtime_config ); } else { $assoc_args[ $key ] = $value; } } } return array( $assoc_args, $runtime_config ); } /** * Handle turning an $assoc_arg into a runtime arg. */ private function assoc_arg_to_runtime_config( $key, $value, &$runtime_config ) { $details = $this->spec[ $key ]; if ( isset( $details['deprecated'] ) ) { fwrite( STDERR, "WP-CLI: The --{$key} global parameter is deprecated. {$details['deprecated']}\n" ); } if ( $details['multiple'] ) { $runtime_config[ $key ][] = $value; } else { $runtime_config[ $key ] = $value; } } /** * Load a YAML file of parameters into scope. * * @param string $path Path to YAML file. */ public function merge_yml( $path, $current_alias = null ) { $yaml = self::load_yml( $path ); if ( ! empty( $yaml['_']['inherit'] ) ) { $this->merge_yml( $yaml['_']['inherit'], $current_alias ); } // Prepare the base path for absolutized alias paths $yml_file_dir = $path ? dirname( $path ) : false; foreach ( $yaml as $key => $value ) { if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { $this->aliases[ $key ] = array(); $is_alias = false; foreach ( self::$alias_spec as $i ) { if ( isset( $value[ $i ] ) ) { if ( 'path' === $i && ! isset( $value['ssh'] ) ) { self::absolutize( $value[ $i ], $yml_file_dir ); } $this->aliases[ $key ][ $i ] = $value[ $i ]; $is_alias = true; } } // If it's not an alias, it might be a group of aliases if ( ! $is_alias && is_array( $value ) ) { $alias_group = array(); foreach ( $value as $i => $k ) { if ( preg_match( '#' . self::ALIAS_REGEX . '#', $k ) ) { $alias_group[] = $k; } } $this->aliases[ $key ] = $alias_group; } } elseif ( ! isset( $this->spec[ $key ] ) || false === $this->spec[ $key ]['file'] ) { if ( isset( $this->extra_config[ $key ] ) && ! empty( $yaml['_']['merge'] ) && is_array( $this->extra_config[ $key ] ) && is_array( $value ) ) { $this->extra_config[ $key ] = array_merge( $this->extra_config[ $key ], $value ); } else { $this->extra_config[ $key ] = $value; } } elseif ( $this->spec[ $key ]['multiple'] ) { self::arrayify( $value ); $this->config[ $key ] = array_merge( $this->config[ $key ], $value ); } else { if ( $current_alias && in_array( $key, self::$alias_spec, true ) ) { continue; } $this->config[ $key ] = $value; } } } /** * Merge an array of values into the configurator config. * * @param array $config */ public function merge_array( $config ) { foreach ( $this->spec as $key => $details ) { if ( false !== $details['runtime'] && isset( $config[ $key ] ) ) { $value = $config[ $key ]; if ( 'require' == $key ) { $value = \WP_CLI\Utils\expand_globs( $value ); } if ( $details['multiple'] ) { self::arrayify( $value ); $this->config[ $key ] = array_merge( $this->config[ $key ], $value ); } else { $this->config[ $key ] = $value; } } } } /** * Load values from a YAML file. * * @param string $yml_file Path to the YAML file * @return array $config Declared configuration values */ private static function load_yml( $yml_file ) { if ( ! $yml_file ) { return array(); } $config = Spyc::YAMLLoad( $yml_file ); // Make sure config-file-relative paths are made absolute. $yml_file_dir = dirname( $yml_file ); if ( isset( $config['path'] ) ) { self::absolutize( $config['path'], $yml_file_dir ); } if ( isset( $config['require'] ) ) { self::arrayify( $config['require'] ); $config['require'] = \WP_CLI\Utils\expand_globs( $config['require'] ); foreach ( $config['require'] as &$path ) { self::absolutize( $path, $yml_file_dir ); } } // Backwards compat // 'core config' -> 'config create' if ( isset( $config['core config'] ) ) { $config['config create'] = $config['core config']; unset( $config['core config'] ); } // 'core verify-checksums' -> 'checksum core' if ( isset( $config['core verify-checksums'] ) ) { $config['checksum core'] = $config['core verify-checksums']; unset( $config['core verify-checksums'] ); } return $config; } /** * Conform a variable to an array. * * @param mixed $val A string or an array */ private static function arrayify( &$val ) { $val = (array) $val; } /** * Make a path absolute. * * @param string $path Path to file. * @param string $base Base path to prepend. */ private static function absolutize( &$path, $base ) { if ( ! empty( $path ) && ! \WP_CLI\Utils\is_path_absolute( $path ) ) { $path = $base . DIRECTORY_SEPARATOR . $path; } } } get_runner()->config['debug']; if ( ! $debug ) { return; } if ( true !== $debug && $group !== $debug ) { return; } $time = round( microtime( true ) - ( defined( 'WP_CLI_START_MICROTIME' ) ? WP_CLI_START_MICROTIME : $start_time ), 3 ); $prefix = 'Debug'; if ( $group && true === $debug ) { $prefix = 'Debug (' . $group . ')'; } $this->_line( "$message ({$time}s)", $prefix, '%B', STDERR ); } /** * Write a string to a resource. * * @param resource $handle Commonly STDOUT or STDERR. * @param string $str Message to write. */ protected function write( $handle, $str ) { fwrite( $handle, $str ); } /** * Output one line of message to a resource. * * @param string $message Message to write. * @param string $label Prefix message with a label. * @param string $color Colorize label with a given color. * @param resource $handle Resource to write to. Defaults to STDOUT. */ protected function _line( $message, $label, $color, $handle = STDOUT ) { if ( class_exists( 'cli\Colors' ) ) { $label = \cli\Colors::colorize( "$color$label:%n", $this->in_color ); } else { $label = "$label:"; } $this->write( $handle, "$label $message\n" ); } } write( STDERR, \WP_CLI::colorize( "%RError:%n $message\n" ) ); } /** * Similar to error( $message ), but outputs $message in a red box * * @param array $message Message to write. */ public function error_multi_line( $message_lines ) { $message = implode( "\n", $message_lines ); $this->write( STDERR, \WP_CLI::colorize( "%RError:%n\n$message\n" ) ); $this->write( STDERR, \WP_CLI::colorize( "%R---------%n\n\n" ) ); } } in_color = $in_color; } /** * Write an informational message to STDOUT. * * @param string $message Message to write. */ public function info( $message ) { $this->write( STDOUT, $message . "\n" ); } /** * Write a success message, prefixed with "Success: ". * * @param string $message Message to write. */ public function success( $message ) { $this->_line( $message, 'Success', '%G' ); } /** * Write a warning message to STDERR, prefixed with "Warning: ". * * @param string $message Message to write. */ public function warning( $message ) { $this->_line( $message, 'Warning', '%C', STDERR ); } /** * Write an message to STDERR, prefixed with "Error: ". * * @param string $message Message to write. */ public function error( $message ) { $this->_line( $message, 'Error', '%R', STDERR ); } /** * Similar to error( $message ), but outputs $message in a red box * * @param array $message Message to write. */ public function error_multi_line( $message_lines ) { // convert tabs to four spaces, as some shells will output the tabs as variable-length $message_lines = array_map( function( $line ) { return str_replace( "\t", ' ', $line ); }, $message_lines ); $longest = max( array_map( 'strlen', $message_lines ) ); // write an empty line before the message $empty_line = \cli\Colors::colorize( '%w%1 ' . str_repeat( ' ', $longest ) . ' %n' ); $this->write( STDERR, "\n\t$empty_line\n" ); foreach ( $message_lines as $line ) { $padding = str_repeat( ' ', $longest - strlen( $line ) ); $line = \cli\Colors::colorize( "%w%1 $line $padding%n" ); $this->write( STDERR, "\t$line\n" ); } // write an empty line after the message $this->write( STDERR, "\t$empty_line\n\n" ); } } write( STDERR, \WP_CLI::colorize( "%RError:%n\n$message\n" ) ); $this->write( STDERR, \WP_CLI::colorize( "%R---------%n\n\n" ) ); } /** * Write a string to a resource. * * @param resource $handle Commonly STDOUT or STDERR. * @param string $str Message to write. */ protected function write( $handle, $str ) { switch ( $handle ) { case STDOUT: $this->stdout .= $str; break; case STDERR: $this->stderr .= $str; break; } } /** * Starts output buffering, using a callback to capture output from `echo`, `print`, `printf` (which write to the output buffer 'php://output' rather than STDOUT). */ public function ob_start() { // To ensure sequential output, give a chunk size of 1 (or 2 if PHP < 5.4 as 1 was a special value meaning a 4KB chunk) to `ob_start()`, so that each write gets flushed immediately. ob_start( array( $this, 'ob_start_callback' ), version_compare( PHP_VERSION, '5.4.0', '<' ) ? 2 : 1 ); } /** * Callback for `ob_start()`. * * @param string $str String to write. * @return string Returns zero-length string so nothing gets written to the output buffer. */ public function ob_start_callback( $str ) { $this->write( STDOUT, $str ); return ''; } /** * To match `ob_start() above. Does an `ob_end_flush()`. */ public function ob_end() { ob_end_flush(); } } STDIN, 1 => array( 'pipe', 'w' ), 2 => array( 'pipe', 'w' ), ); /** * @var bool Whether to log run time info or not. */ public static $log_run_times = false; /** * @var array Array of process run time info, keyed by process command, each a 2-element array containing run time and run count. */ public static $run_times = array(); /** * @param string $command Command to execute. * @param string $cwd Directory to execute the command in. * @param array $env Environment variables to set when running the command. * * @return Process */ public static function create( $command, $cwd = null, $env = array() ) { $proc = new self; $proc->command = $command; $proc->cwd = $cwd; $proc->env = $env; return $proc; } private function __construct() {} /** * Run the command. * * @return ProcessRun */ public function run() { $start_time = microtime( true ); $proc = proc_open( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env ); $stdout = stream_get_contents( $pipes[1] ); fclose( $pipes[1] ); $stderr = stream_get_contents( $pipes[2] ); fclose( $pipes[2] ); $return_code = proc_close( $proc ); $run_time = microtime( true ) - $start_time; if ( self::$log_run_times ) { if ( ! isset( self::$run_times[ $this->command ] ) ) { self::$run_times[ $this->command ] = array( 0, 0 ); } self::$run_times[ $this->command ][0] += $run_time; self::$run_times[ $this->command ][1]++; } return new ProcessRun( array( 'stdout' => $stdout, 'stderr' => $stderr, 'return_code' => $return_code, 'command' => $this->command, 'cwd' => $this->cwd, 'env' => $this->env, 'run_time' => $run_time, ) ); } /** * Run the command, but throw an Exception on error. * * @return ProcessRun */ public function run_check() { $r = $this->run(); // $r->STDERR is incorrect, but kept incorrect for backwards-compat if ( $r->return_code || ! empty( $r->STDERR ) ) { throw new \RuntimeException( $r ); } return $r; } } 'pre_install', PackageEvents::POST_PACKAGE_INSTALL => 'post_install', ); } public static function pre_install( PackageEvent $event ) { $operation_message = $event->getOperation()->__toString(); WP_CLI::log( ' - ' . $operation_message ); } public static function post_install( PackageEvent $event ) { $operation = $event->getOperation(); $reason = $operation->getReason(); if ( $reason instanceof Rule ) { switch ( $reason->getReason() ) { case Rule::RULE_PACKAGE_CONFLICT: case Rule::RULE_PACKAGE_SAME_NAME: case Rule::RULE_PACKAGE_REQUIRES: $composer_error = $reason->getPrettyString( $event->getPool() ); break; } if ( ! empty( $composer_error ) ) { WP_CLI::log( sprintf( ' - Warning: %s', $composer_error ) ); } } } } cache = $cache; // hook into wp http api add_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 10, 3 ); add_filter( 'http_response', array( $this, 'filter_http_response' ), 10, 3 ); } /** * short circuit wp http api with cached file */ public function filter_pre_http_request( $response, $args, $url ) { // check if whitelisted if ( ! isset( $this->whitelist[ $url ] ) ) { return $response; } // check if downloading if ( 'GET' !== $args['method'] || empty( $args['filename'] ) ) { return $response; } // check cache and export to designated location $filename = $this->cache->has( $this->whitelist[ $url ]['key'], $this->whitelist[ $url ]['ttl'] ); if ( $filename ) { WP_CLI::log( sprintf( 'Using cached file \'%s\'...', $filename ) ); if ( copy( $filename, $args['filename'] ) ) { // simulate successful download response return array( 'response' => array( 'code' => 200, 'message' => 'OK', ), 'filename' => $args['filename'], ); } WP_CLI::error( sprintf( 'Error copying cached file %s to %s', $filename, $url ) ); } return $response; } /** * cache wp http api downloads * * @param array $response * @param array $args * @param string $url */ public function filter_http_response( $response, $args, $url ) { // check if whitelisted if ( ! isset( $this->whitelist[ $url ] ) ) { return $response; } // check if downloading if ( 'GET' !== $args['method'] || empty( $args['filename'] ) ) { return $response; } // check if download was successful if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return $response; } // cache downloaded file $this->cache->import( $this->whitelist[ $url ]['key'], $response['filename'] ); return $response; } /** * whitelist a package url * * @param string $url * @param string $group package group (themes, plugins, ...) * @param string $slug package slug * @param string $version package version * @param int $ttl */ public function whitelist_package( $url, $group, $slug, $version, $ttl = null ) { $ext = pathinfo( parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION ); $key = "$group/$slug-$version.$ext"; $this->whitelist_url( $url, $key, $ttl ); wp_update_plugins(); } /** * whitelist a url * * @param string $url * @param string $key * @param int $ttl */ public function whitelist_url( $url, $key = null, $ttl = null ) { $key = $key ? : $url; $this->whitelist[ $url ] = compact( 'key', 'ttl' ); } /** * check if url is whitelisted * * @param string $url * @return bool */ public function is_whitelisted( $url ) { return isset( $this->whitelist[ $url ] ); } } $value ) { $this->$key = $value; } } /** * Return properties of executed command as a string. * * @return string */ public function __toString() { $out = "$ $this->command\n"; $out .= "$this->stdout\n$this->stderr"; $out .= "cwd: $this->cwd\n"; $out .= "run time: $this->run_time\n"; $out .= "exit status: $this->return_code"; return $out; } } start(); return $state; } } get_protected_commands(); $current_command = $this->get_current_command(); foreach ( $commands as $command ) { if ( 0 === strpos( $current_command, $command ) ) { $state->setValue( BootstrapState::IS_PROTECTED_COMMAND, true ); } } return $state; } /** * Get the list of protected commands. * * @return array */ private function get_protected_commands() { return array( 'cli info', 'package', ); } /** * Get the current command as a string. * * @return string Current command to be executed. */ private function get_current_command() { $runner = new RunnerInstance(); return implode( ' ', (array) $runner()->arguments ); } } init_config(); return $state; } } state->getValue( BootstrapState::IS_PROTECTED_COMMAND, $fallback = false ) ) { return false; } $runner = new RunnerInstance(); $skip_packages = $runner()->config['skip-packages']; if ( true === $skip_packages ) { \WP_CLI::debug( 'Skipped loading packages.', 'bootstrap' ); return false; } $autoloader_path = $runner()->get_packages_dir_path() . 'vendor/autoload.php'; if ( is_readable( $autoloader_path ) ) { \WP_CLI::debug( 'Loading packages from: ' . $autoloader_path, 'bootstrap' ); return array( $autoloader_path, ); } return false; } /** * Handle the failure to find an autoloader. * * @return void */ protected function handle_failure() { \WP_CLI::debug( 'No package autoload found to load.', 'bootstrap' ); } } get_custom_vendor_folder() ) { array_unshift( $autoloader_paths, WP_CLI_ROOT . '/../../../' . $custom_vendor . '/autoload.php' ); } return $autoloader_paths; } } get_custom_vendor_folder() ) { array_unshift( $autoloader_paths, WP_CLI_ROOT . '/../../../' . $custom_vendor . '/autoload_commands.php' ); } return $autoloader_paths; } } get_custom_vendor_folder() ) { array_unshift( $autoloader_paths, WP_CLI_ROOT . '/../../../' . $custom_vendor . '/autoload_framework.php' ); } return $autoloader_paths; } /** * Handle the failure to find an autoloader. * * @return void */ protected function handle_failure() { fwrite( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" ); exit( 3 ); } } declare_loggers(); $runner = new RunnerInstance(); $runner()->init_logger(); return $state; } /** * Load the class declarations for the loggers. */ private function declare_loggers() { $logger_dir = WP_CLI_ROOT . '/php/WP_CLI/Loggers'; $iterator = new \DirectoryIterator( $logger_dir ); // Make sure the base class is declared first. include_once "$logger_dir/Base.php"; foreach ( $iterator as $filename ) { if ( '.php' !== substr( $filename, - 4 ) ) { continue; } include_once "$logger_dir/$filename"; } } } init_colorization(); return $state; } } getMessage() ); } } return $state; } } add_deferred_commands(); // Process deferred command additions for commands added through // plugins. \WP_CLI::add_hook( 'find_command_to_run_pre', array( $this, 'add_deferred_commands' ) ); return $state; } /** * Add deferred commands that are still waiting to be processed. */ public function add_deferred_commands() { $deferred_additions = \WP_CLI::get_deferred_additions(); foreach ( $deferred_additions as $name => $addition ) { \WP_CLI::add_command( $name, $addition['callable'], $addition['args'] ); } } } ` option. * * @package WP_CLI\Bootstrap */ final class LoadRequiredCommand implements BootstrapStep { /** * Process this single bootstrapping step. * * @param BootstrapState $state Contextual state to pass into the step. * * @return BootstrapState Modified state to pass to the next step. */ public function process( BootstrapState $state ) { if ( $state->getValue( BootstrapState::IS_PROTECTED_COMMAND, $fallback = false ) ) { return $state; } $runner = new RunnerInstance(); if ( ! isset( $runner()->config['require'] ) ) { return $state; } foreach ( $runner()->config['require'] as $path ) { if ( ! file_exists( $path ) ) { $context = ''; $required_files = $runner()->get_required_files(); foreach ( array( 'global', 'project', 'runtime' ) as $scope ) { if ( in_array( $path, $required_files[ $scope ], true ) ) { switch ( $scope ) { case 'global': $context = ' (from global ' . Utils\basename( $runner()->get_global_config_path() ) . ')'; break; case 'project': $context = ' (from project\'s ' . Utils\basename( $runner()->get_project_config_path() ) . ')'; break; case 'runtime': $context = ' (from runtime argument)'; break; } break; } } WP_CLI::error( sprintf( "Required file '%s' doesn't exist%s.", Utils\basename( $path ), $context ) ); } Utils\load_file( $path ); WP_CLI::debug( 'Required file from config: ' . $path, 'bootstrap' ); } return $state; } } state = $state; $found_autoloader = false; $autoloader_paths = $this->get_autoloader_paths(); if ( false === $autoloader_paths ) { // Skip this autoloading step. return $state; } foreach ( $autoloader_paths as $autoloader_path ) { if ( is_readable( $autoloader_path ) ) { try { require $autoloader_path; $found_autoloader = true; } catch ( \Exception $exception ) { \WP_CLI::warning( "Failed to load autoloader '{$autoloader_path}'. Reason: " . $exception->getMessage() ); } } } if ( ! $found_autoloader ) { $this->handle_failure(); } return $this->state; } /** * Get the name of the custom vendor folder as set in `composer.json`. * * @return string|false Name of the custom vendor folder or false if none. */ protected function get_custom_vendor_folder() { $maybe_composer_json = WP_CLI_ROOT . '/../../../composer.json'; if ( ! is_readable( $maybe_composer_json ) ) { return false; } $composer = json_decode( file_get_contents( $maybe_composer_json ) ); if ( ! empty( $composer->config ) && ! empty( $composer->config->{'vendor-dir'} ) ) { return $composer->config->{'vendor-dir'}; } return false; } /** * Handle the failure to find an autoloader. * * @return void */ protected function handle_failure() { } /** * Get the autoloader paths to scan for an autoloader. * * @return string[]|false Array of strings with autoloader paths, or false * to skip. */ abstract protected function get_autoloader_paths(); } state ) ? $this->state[ $key ] : $fallback; } /** * Set the state value for a given key. * * @param string $key Key to set the state for. * @param mixed $value Value to set the state for the given key to. * * @return void */ // @codingStandardsIgnoreLine public function setValue( $key, $value ) { $this->state[ $key ] = $value; } } open( $zipfile ); if ( true === $res ) { $tempdir = implode( DIRECTORY_SEPARATOR, array( dirname( $zipfile ), Utils\basename( $zipfile, '.zip' ), $zip->getNameIndex( 0 ), ) ); $zip->extractTo( dirname( $tempdir ) ); $zip->close(); self::copy_overwrite_files( $tempdir, $dest ); self::rmdir( dirname( $tempdir ) ); } else { throw Exception( $res ); } } /** * Extract a tarball to a specific destination. * * @param string $tarball * @param string $dest */ private static function extract_tarball( $tarball, $dest ) { if ( class_exists( 'PharData' ) ) { try { $phar = new PharData( $tarball ); $tempdir = implode( DIRECTORY_SEPARATOR, array( dirname( $tarball ), Utils\basename( $tarball, '.tar.gz' ), $phar->getFilename(), ) ); $phar->extractTo( dirname( $tempdir ), null, true ); self::copy_overwrite_files( $tempdir, $dest ); self::rmdir( dirname( $tempdir ) ); return; } catch ( \Exception $e ) { WP_CLI::warning( "PharData failed, falling back to 'tar gz' (" . $e->getMessage() . ')' ); // Fall through to trying `tar xz` below } } $cmd = "tar xz --strip-components=1 --directory=%s -f $tarball"; WP_CLI::launch( Utils\esc_cmd( $cmd, $dest ) ); } public static function copy_overwrite_files( $source, $dest ) { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $source, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::SELF_FIRST ); $error = 0; if ( ! is_dir( $dest ) ) { mkdir( $dest, 0777, true ); } foreach ( $iterator as $item ) { $dest_path = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); if ( $item->isDir() ) { if ( ! is_dir( $dest_path ) ) { mkdir( $dest_path ); } } else { if ( file_exists( $dest_path ) && is_writable( $dest_path ) ) { copy( $item, $dest_path ); } elseif ( ! file_exists( $dest_path ) ) { copy( $item, $dest_path ); } else { $error = 1; WP_CLI::warning( "Unable to copy '" . $iterator->getSubPathName() . "' to current directory." ); } } } if ( $error ) { throw new Exception( 'There was an error overwriting existing files.' ); } } public static function rmdir( $dir ) { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ); foreach ( $files as $fileinfo ) { $todo = $fileinfo->isDir() ? 'rmdir' : 'unlink'; $todo( $fileinfo->getRealPath() ); } rmdir( $dir ); } } words = explode( ' ', $line ); // first word is always `wp` array_shift( $this->words ); // last word is either empty or an incomplete subcommand $this->cur_word = end( $this->words ); if ( '' !== $this->cur_word && ! preg_match( '/^\-/', $this->cur_word ) ) { array_pop( $this->words ); } $is_alias = false; $is_help = false; if ( ! empty( $this->words[0] ) && preg_match( '/^@/', $this->words[0] ) ) { array_shift( $this->words ); // `wp @al` is false, but `wp @all ` is true. if ( count( $this->words ) ) { $is_alias = true; } } elseif ( ! empty( $this->words[0] ) && 'help' === $this->words[0] ) { array_shift( $this->words ); $is_help = true; } $r = $this->get_command( $this->words ); if ( ! is_array( $r ) ) { return; } list( $command, $args, $assoc_args ) = $r; $spec = SynopsisParser::parse( $command->get_synopsis() ); foreach ( $spec as $arg ) { if ( 'positional' === $arg['type'] && 'file' === $arg['name'] ) { $this->add( ' ' ); return; } } if ( $command->can_have_subcommands() ) { // add completion when command is `wp` and alias isn't set. if ( 'wp' === $command->get_name() && false === $is_alias && false == $is_help ) { $aliases = \WP_CLI::get_configurator()->get_aliases(); foreach ( $aliases as $name => $_ ) { $this->add( "$name " ); } } foreach ( $command->get_subcommands() as $name => $_ ) { $this->add( "$name " ); } } else { foreach ( $spec as $arg ) { if ( in_array( $arg['type'], array( 'flag', 'assoc' ) ) ) { if ( isset( $assoc_args[ $arg['name'] ] ) ) { continue; } $opt = "--{$arg['name']}"; if ( 'flag' === $arg['type'] ) { $opt .= ' '; } elseif ( ! $arg['value']['optional'] ) { $opt .= '='; } $this->add( $opt ); } } foreach ( $this->get_global_parameters() as $param => $runtime ) { if ( isset( $assoc_args[ $param ] ) ) { continue; } $opt = "--{$param}"; if ( '' === $runtime || ! is_string( $runtime ) ) { $opt .= ' '; } else { $opt .= '='; } $this->add( $opt ); } } } private function get_command( $words ) { $positional_args = $assoc_args = array(); foreach ( $words as $arg ) { if ( preg_match( '|^--([^=]+)=?|', $arg, $matches ) ) { $assoc_args[ $matches[1] ] = true; } else { $positional_args[] = $arg; } } $r = \WP_CLI::get_runner()->find_command_to_run( $positional_args ); if ( ! is_array( $r ) && array_pop( $positional_args ) == $this->cur_word ) { $r = \WP_CLI::get_runner()->find_command_to_run( $positional_args ); } if ( ! is_array( $r ) ) { return $r; } list( $command, $args ) = $r; return array( $command, $args, $assoc_args ); } private function get_global_parameters() { $params = array(); foreach ( \WP_CLI::get_configurator()->get_spec() as $key => $details ) { if ( false === $details['runtime'] ) { continue; } if ( isset( $details['deprecated'] ) ) { continue; } if ( isset( $details['hidden'] ) ) { continue; } $params[ $key ] = $details['runtime']; // Add additional option like `--[no-]color`. if ( true === $details['runtime'] ) { $params[ 'no-' . $key ] = ''; } } return $params; } private function add( $opt ) { if ( '' !== $this->cur_word ) { if ( 0 !== strpos( $opt, $this->cur_word ) ) { return; } } $this->opts[] = $opt; } public function render() { foreach ( $this->opts as $opt ) { \WP_CLI::line( $opt ); } } } spec = SynopsisParser::parse( $synopsis ); } /** * Get any unknown arguments. * * @return array */ public function get_unknown() { return array_column( $this->query_spec( array( 'type' => 'unknown', ) ), 'token' ); } /** * Check whether there are enough positional arguments. * * @param array $args Positional arguments. * @return bool */ public function enough_positionals( $args ) { $positional = $this->query_spec( array( 'type' => 'positional', 'optional' => false, ) ); return count( $args ) >= count( $positional ); } /** * Check for any unknown positionals. * * @param array $args Positional arguments. * @return array */ public function unknown_positionals( $args ) { $positional_repeating = $this->query_spec( array( 'type' => 'positional', 'repeating' => true, ) ); // At least one positional supports as many as possible. if ( ! empty( $positional_repeating ) ) { return array(); } $positional = $this->query_spec( array( 'type' => 'positional', 'repeating' => false, ) ); return array_slice( $args, count( $positional ) ); } /** * Check that all required keys are present and that they have values. * * @param array $assoc_args Parameters passed to command. * @return array */ public function validate_assoc( $assoc_args ) { $assoc_spec = $this->query_spec( array( 'type' => 'assoc', ) ); $errors = array( 'fatal' => array(), 'warning' => array(), ); $to_unset = array(); foreach ( $assoc_spec as $param ) { $key = $param['name']; if ( ! isset( $assoc_args[ $key ] ) ) { if ( ! $param['optional'] ) { $errors['fatal'][ $key ] = "missing --$key parameter"; } } else { if ( true === $assoc_args[ $key ] && ! $param['value']['optional'] ) { $error_type = ( ! $param['optional'] ) ? 'fatal' : 'warning'; $errors[ $error_type ][ $key ] = "--$key parameter needs a value"; $to_unset[] = $key; } } } return array( $errors, $to_unset ); } /** * Check whether there are unknown parameters supplied. * * @param array $assoc_args Parameters passed to command. * @return array|false */ public function unknown_assoc( $assoc_args ) { $generic = $this->query_spec( array( 'type' => 'generic', ) ); if ( count( $generic ) ) { return array(); } $known_assoc = array(); foreach ( $this->spec as $param ) { if ( in_array( $param['type'], array( 'assoc', 'flag' ) ) ) { $known_assoc[] = $param['name']; } } return array_diff( array_keys( $assoc_args ), $known_assoc ); } /** * Filters a list of associative arrays, based on a set of key => value arguments. * * @param array $args An array of key => value arguments to match against * @param string $operator * @return array */ private function query_spec( $args, $operator = 'AND' ) { $operator = strtoupper( $operator ); $count = count( $args ); $filtered = array(); foreach ( $this->spec as $key => $to_match ) { $matched = 0; foreach ( $args as $m_key => $m_value ) { if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] ) { $matched++; } } if ( ( 'AND' == $operator && $matched == $count ) || ( 'OR' == $operator && $matched > 0 ) || ( 'NOT' == $operator && 0 == $matched ) ) { $filtered[ $key ] = $to_match; } } return $filtered; } } docComment = self::remove_decorations( $docComment ); } /** * Remove unused cruft from PHPdoc comment. * * @param string $comment PHPdoc comment. * @return string */ private static function remove_decorations( $comment ) { $comment = preg_replace( '|^/\*\*[\r\n]+|', '', $comment ); $comment = preg_replace( '|\n[\t ]*\*/$|', '', $comment ); $comment = preg_replace( '|^[\t ]*\* ?|m', '', $comment ); return $comment; } /** * Get the command's short description (e.g. summary). * * @return string */ public function get_shortdesc() { if ( ! preg_match( '|^([^@][^\n]+)\n*|', $this->docComment, $matches ) ) { return ''; } return $matches[1]; } /** * Get the command's full description * * @return string */ public function get_longdesc() { $shortdesc = $this->get_shortdesc(); if ( ! $shortdesc ) { return ''; } $longdesc = substr( $this->docComment, strlen( $shortdesc ) ); $lines = array(); foreach ( explode( "\n", $longdesc ) as $line ) { if ( 0 === strpos( $line, '@' ) ) { break; } $lines[] = $line; } $longdesc = trim( implode( $lines, "\n" ) ); return $longdesc; } /** * Get the value for a given tag (e.g. "@alias" or "@subcommand") * * @param string $name Name for the tag, without '@' * @return string */ public function get_tag( $name ) { if ( preg_match( '|^@' . $name . '\s+([a-z-_0-9]+)|m', $this->docComment, $matches ) ) { return $matches[1]; } return ''; } /** * Get the command's synopsis. * * @return string */ public function get_synopsis() { if ( ! preg_match( '|^@synopsis\s+(.+)|m', $this->docComment, $matches ) ) { return ''; } return $matches[1]; } /** * Get the description for a given argument. * * @param string $name Argument's doc name. * @return string */ public function get_arg_desc( $name ) { if ( preg_match( "/\[?<{$name}>.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { return $matches[1]; } return ''; } /** * Get the arguments for a given argument. * * @param string $name Argument's doc name. * @return mixed|null */ public function get_arg_args( $name ) { return $this->get_arg_or_param_args( "/^\[?<{$name}>.*/" ); } /** * Get the description for a given parameter. * * @param string $key Parameter's key. * @return string */ public function get_param_desc( $key ) { if ( preg_match( "/\[?--{$key}=.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { return $matches[1]; } return ''; } /** * Get the arguments for a given parameter. * * @param string $key Parameter's key. * @return mixed|null */ public function get_param_args( $key ) { return $this->get_arg_or_param_args( "/^\[?--{$key}=.*/" ); } /** * Get the args for an arg or param * * @param string $regex Pattern to match against * @return array|null Interpreted YAML document, or null. */ private function get_arg_or_param_args( $regex ) { $bits = explode( PHP_EOL, $this->docComment ); $within_arg = $within_doc = false; $document = array(); foreach ( $bits as $bit ) { if ( preg_match( $regex, $bit ) ) { $within_arg = true; } if ( $within_arg && $within_doc && '---' === $bit ) { $within_doc = false; } if ( $within_arg && ! $within_doc && '---' === $bit ) { $within_doc = true; } if ( $within_doc ) { $document[] = $bit; } if ( $within_arg && '' === $bit ) { $within_arg = false; break; } } if ( $document ) { return Spyc::YAMLLoadString( implode( PHP_EOL, $document ) ); } return null; } } ]+)>#', '$1$2', $message ); WP_CLI::log( strip_tags( trim( $message ) ) ); } } add_namespace( 'WP_CLI\Bootstrap', WP_CLI_ROOT . '/php/WP_CLI/Bootstrap' )->register(); } /** * Initialize and return the bootstrap state to pass from step to step. * * @return BootstrapState */ function initialize_bootstrap_state() { return new BootstrapState(); } /** * Process the bootstrapping steps. * * Loops over each of the provided steps, instantiates it and then calls its * `process()` method. */ function bootstrap() { prepare_bootstrap(); $state = initialize_bootstrap_state(); foreach ( get_bootstrap_steps() as $step ) { /** @var \WP_CLI\Bootstrap\BootstrapStep $step_instance */ $step_instance = new $step(); $state = $step_instance->process( $state ); } } get_error_message(); } $message = wp_clean_error_message( $message ); \WP_CLI::error( $message ); } /** * Clean HTML error message so suitable for text display. */ function wp_clean_error_message( $message ) { $original_message = $message = trim( $message ); if ( preg_match( '|^\

(.+?)

|', $original_message, $matches ) ) { $message = $matches[1] . '.'; } if ( preg_match( '|\

(.+?)

|', $original_message, $matches ) ) { $message .= ' ' . $matches[1]; } $search_replace = array( '' => '`', '' => '`', ); $message = str_replace( array_keys( $search_replace ), array_values( $search_replace ), $message ); $message = strip_tags( $message ); $message = html_entity_decode( $message, ENT_COMPAT, 'UTF-8' ); return $message; } function wp_redirect_handler( $url ) { \WP_CLI::warning( 'Some code is trying to do a URL redirect. Backtrace:' ); ob_start(); debug_print_backtrace(); fwrite( STDERR, ob_get_clean() ); return $url; } function maybe_require( $since, $path ) { if ( wp_version_compare( $since, '>=' ) ) { require $path; } } function get_upgrader( $class ) { if ( ! class_exists( '\WP_Upgrader' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } return new $class( new \WP_CLI\UpgraderSkin ); } /** * Converts a plugin basename back into a friendly slug. */ function get_plugin_name( $basename ) { if ( false === strpos( $basename, '/' ) ) { $name = basename( $basename, '.php' ); } else { $name = dirname( $basename ); } return $name; } function is_plugin_skipped( $file ) { $name = get_plugin_name( str_replace( WP_PLUGIN_DIR . '/', '', $file ) ); $skipped_plugins = \WP_CLI::get_runner()->config['skip-plugins']; if ( true === $skipped_plugins ) { return true; } if ( ! is_array( $skipped_plugins ) ) { $skipped_plugins = explode( ',', $skipped_plugins ); } return in_array( $name, array_filter( $skipped_plugins ) ); } function get_theme_name( $path ) { return basename( $path ); } function is_theme_skipped( $path ) { $name = get_theme_name( $path ); $skipped_themes = \WP_CLI::get_runner()->config['skip-themes']; if ( true === $skipped_themes ) { return true; } if ( ! is_array( $skipped_themes ) ) { $skipped_themes = explode( ',', $skipped_themes ); } return in_array( $name, array_filter( $skipped_themes ) ); } /** * Register the sidebar for unused widgets * Core does this in /wp-admin/widgets.php, which isn't helpful */ function wp_register_unused_sidebar() { register_sidebar( array( 'name' => __( 'Inactive Widgets' ), 'id' => 'wp_inactive_widgets', 'class' => 'inactive-sidebar', 'description' => __( 'Drag widgets here to remove them from the sidebar but keep their settings.' ), 'before_widget' => '', 'after_widget' => '', 'before_title' => '', 'after_title' => '', ) ); } /** * Attempts to determine which object cache is being used. * * Note that the guesses made by this function are based on the WP_Object_Cache classes * that define the 3rd party object cache extension. Changes to those classes could render * problems with this function's ability to determine which object cache is being used. * * @return string */ function wp_get_cache_type() { global $_wp_using_ext_object_cache, $wp_object_cache; if ( ! empty( $_wp_using_ext_object_cache ) ) { // Test for Memcached PECL extension memcached object cache (https://github.com/tollmanz/wordpress-memcached-backend) if ( isset( $wp_object_cache->m ) && is_a( $wp_object_cache->m, 'Memcached' ) ) { $message = 'Memcached'; // Test for Memcache PECL extension memcached object cache (http://wordpress.org/extend/plugins/memcached/) } elseif ( isset( $wp_object_cache->mc ) ) { $is_memcache = true; foreach ( $wp_object_cache->mc as $bucket ) { if ( ! is_a( $bucket, 'Memcache' ) && ! is_a( $bucket, 'Memcached' ) ) { $is_memcache = false; } } if ( $is_memcache ) { $message = 'Memcache'; } // Test for Xcache object cache (http://plugins.svn.wordpress.org/xcache/trunk/object-cache.php) } elseif ( is_a( $wp_object_cache, 'XCache_Object_Cache' ) ) { $message = 'Xcache'; // Test for WinCache object cache (http://wordpress.org/extend/plugins/wincache-object-cache-backend/) } elseif ( class_exists( 'WinCache_Object_Cache' ) ) { $message = 'WinCache'; // Test for APC object cache (http://wordpress.org/extend/plugins/apc/) } elseif ( class_exists( 'APC_Object_Cache' ) ) { $message = 'APC'; // Test for Redis Object Cache (https://github.com/alleyinteractive/wp-redis) } elseif ( isset( $wp_object_cache->redis ) && is_a( $wp_object_cache->redis, 'Redis' ) ) { $message = 'Redis'; // Test for WP LCache Object cache (https://github.com/lcache/wp-lcache) } elseif ( isset( $wp_object_cache->lcache ) && is_a( $wp_object_cache->lcache, '\LCache\Integrated' ) ) { $message = 'WP LCache'; } elseif ( function_exists( 'w3_instance' ) ) { $config = w3_instance( 'W3_Config' ); $message = 'Unknown'; if ( $config->get_boolean( 'objectcache.enabled' ) ) { $message = 'W3TC ' . $config->get_string( 'objectcache.engine' ); } } else { $message = 'Unknown'; } } else { $message = 'Default'; } return $message; } /** * Clear WordPress internal object caches. * * In long-running scripts, the internal caches on `$wp_object_cache` and `$wpdb` * can grow to consume gigabytes of memory. Periodically calling this utility * can help with memory management. * * @access public * @category System */ function wp_clear_object_cache() { global $wpdb, $wp_object_cache; $wpdb->queries = array(); // or define( 'WP_IMPORTING', true ); if ( ! is_object( $wp_object_cache ) ) { return; } $wp_object_cache->group_ops = array(); $wp_object_cache->stats = array(); $wp_object_cache->memcache_debug = array(); $wp_object_cache->cache = array(); if ( is_callable( $wp_object_cache, '__remoteset' ) ) { $wp_object_cache->__remoteset(); // important } } /** * Get a set of tables in the database. * * Interprets common command-line options into a resolved set of table names. * * @param array $args Provided table names, or tables with wildcards. * @param array $assoc_args Optional flags for groups of tables (e.g. --network) * @return array $tables */ function wp_get_table_names( $args, $assoc_args = array() ) { global $wpdb; // Prioritize any supplied $args as tables if ( ! empty( $args ) ) { $new_tables = array(); $get_tables_for_glob = function( $glob ) { global $wpdb; static $all_tables = array(); if ( ! $all_tables ) { $all_tables = $wpdb->get_col( 'SHOW TABLES' ); } $tables = array(); foreach ( $all_tables as $table ) { if ( fnmatch( $glob, $table ) ) { $tables[] = $table; } } return $tables; }; foreach ( $args as $key => $table ) { if ( false !== strpos( $table, '*' ) || false !== strpos( $table, '?' ) ) { $expanded_tables = $get_tables_for_glob( $table ); if ( empty( $expanded_tables ) ) { \WP_CLI::error( "Couldn't find any tables matching: {$table}" ); } $new_tables = array_merge( $new_tables, $expanded_tables ); } else { $new_tables[] = $table; } } return $new_tables; } // Fall back to flag if no tables were passed $table_type = 'WordPress'; if ( get_flag_value( $assoc_args, 'network' ) ) { $table_type = 'network'; } if ( get_flag_value( $assoc_args, 'all-tables-with-prefix' ) ) { $table_type = 'all-tables-with-prefix'; } if ( get_flag_value( $assoc_args, 'all-tables' ) ) { $table_type = 'all-tables'; } $network = 'network' == $table_type; if ( 'all-tables' == $table_type ) { return $wpdb->get_col( 'SHOW TABLES' ); } $prefix = $network ? $wpdb->base_prefix : $wpdb->prefix; // '_' is a special wildcard for MySQL LIKE queries // so it needs to be escaped with '\', but then '\' needs to be escaped as well $sql_prefix = str_replace( '_', '\\_', $prefix ); $matching_tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $sql_prefix . '%' ) ); if ( 'all-tables-with-prefix' == $table_type ) { return $matching_tables; } if ( $scope = get_flag_value( $assoc_args, 'scope' ) ) { return $wpdb->tables( $scope ); } $allowed_tables = array(); $allowed_table_types = array( 'tables', 'global_tables' ); if ( $network ) { $allowed_table_types[] = 'ms_global_tables'; } foreach ( $allowed_table_types as $table_type ) { foreach ( $wpdb->$table_type as $table ) { $allowed_tables[] = $prefix . $table; } } // Given our matching tables, also allow site-specific tables on the network foreach ( $matching_tables as $key => $matched_table ) { if ( in_array( $matched_table, $allowed_tables ) ) { continue; } if ( $network ) { $valid_table = false; foreach ( array_merge( $wpdb->tables, $wpdb->old_tables ) as $maybe_site_table ) { if ( preg_match( "#{$prefix}([\d]+)_{$maybe_site_table}#", $matched_table ) ) { $valid_table = true; } } if ( $valid_table ) { continue; } } unset( $matching_tables[ $key ] ); } return array_values( $matching_tables ); } error ) ) { wp_die( $wpdb->error ); } // Set the database table prefix and the format specifiers for database table columns. // @codingStandardsIgnoreStart $GLOBALS['table_prefix'] = $table_prefix; // @codingStandardsIgnoreEnd wp_set_wpdb_vars(); // Start the WordPress object cache, or an external object cache if the drop-in is present. wp_start_object_cache(); // Attach the default filters. require ABSPATH . WPINC . '/default-filters.php'; // Initialize multisite if enabled. if ( is_multisite() ) { Utils\maybe_require( '4.6-alpha-37575', ABSPATH . WPINC . '/class-wp-site-query.php' ); Utils\maybe_require( '4.6-alpha-37896', ABSPATH . WPINC . '/class-wp-network-query.php' ); require ABSPATH . WPINC . '/ms-blogs.php'; require ABSPATH . WPINC . '/ms-settings.php'; } elseif ( ! defined( 'MULTISITE' ) ) { define( 'MULTISITE', false ); } register_shutdown_function( 'shutdown_action_hook' ); // Stop most of WordPress from being loaded if we just want the basics. if ( SHORTINIT ) { return false; } // Load the L10n library. require_once ABSPATH . WPINC . '/l10n.php'; // WP-CLI: Permit Utils\wp_not_installed() to run on < WP 4.0 apply_filters( 'nocache_headers', array() ); // Run the installer if WordPress is not installed. wp_not_installed(); // Load most of WordPress. require ABSPATH . WPINC . '/class-wp-walker.php'; require ABSPATH . WPINC . '/class-wp-ajax-response.php'; require ABSPATH . WPINC . '/formatting.php'; require ABSPATH . WPINC . '/capabilities.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-roles.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-role.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-user.php' ); require ABSPATH . WPINC . '/query.php'; Utils\maybe_require( '3.7-alpha-25139', ABSPATH . WPINC . '/date.php' ); require ABSPATH . WPINC . '/theme.php'; require ABSPATH . WPINC . '/class-wp-theme.php'; require ABSPATH . WPINC . '/template.php'; require ABSPATH . WPINC . '/user.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-user-query.php' ); Utils\maybe_require( '4.0', ABSPATH . WPINC . '/session.php' ); require ABSPATH . WPINC . '/meta.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-meta-query.php' ); Utils\maybe_require( '4.5-alpha-35776', ABSPATH . WPINC . '/class-wp-metadata-lazyloader.php' ); require ABSPATH . WPINC . '/general-template.php'; require ABSPATH . WPINC . '/link-template.php'; require ABSPATH . WPINC . '/author-template.php'; require ABSPATH . WPINC . '/post.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-page.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-page-dropdown.php' ); Utils\maybe_require( '4.6-alpha-37890', ABSPATH . WPINC . '/class-wp-post-type.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-post.php' ); require ABSPATH . WPINC . '/post-template.php'; Utils\maybe_require( '3.6-alpha-23451', ABSPATH . WPINC . '/revision.php' ); Utils\maybe_require( '3.6-alpha-23451', ABSPATH . WPINC . '/post-formats.php' ); require ABSPATH . WPINC . '/post-thumbnail-template.php'; require ABSPATH . WPINC . '/category.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-category.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-category-dropdown.php' ); require ABSPATH . WPINC . '/category-template.php'; require ABSPATH . WPINC . '/comment.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-comment.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-comment-query.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-comment.php' ); require ABSPATH . WPINC . '/comment-template.php'; require ABSPATH . WPINC . '/rewrite.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-rewrite.php' ); require ABSPATH . WPINC . '/feed.php'; require ABSPATH . WPINC . '/bookmark.php'; require ABSPATH . WPINC . '/bookmark-template.php'; require ABSPATH . WPINC . '/kses.php'; require ABSPATH . WPINC . '/cron.php'; require ABSPATH . WPINC . '/deprecated.php'; require ABSPATH . WPINC . '/script-loader.php'; require ABSPATH . WPINC . '/taxonomy.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-term.php' ); Utils\maybe_require( '4.6-alpha-37575', ABSPATH . WPINC . '/class-wp-term-query.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-tax-query.php' ); require ABSPATH . WPINC . '/update.php'; require ABSPATH . WPINC . '/canonical.php'; require ABSPATH . WPINC . '/shortcodes.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/embed.php' ); require ABSPATH . WPINC . '/class-wp-embed.php'; require ABSPATH . WPINC . '/media.php'; Utils\maybe_require( '4.4-alpha-34903', ABSPATH . WPINC . '/class-wp-oembed-controller.php' ); require ABSPATH . WPINC . '/http.php'; require_once ABSPATH . WPINC . '/class-http.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-streams.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-curl.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-proxy.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-cookie.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-encoding.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-response.php' ); Utils\maybe_require( '4.6-alpha-37438', ABSPATH . WPINC . '/class-wp-http-requests-response.php' ); require ABSPATH . WPINC . '/widgets.php'; Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-widget.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-widget-factory.php' ); require ABSPATH . WPINC . '/nav-menu.php'; require ABSPATH . WPINC . '/nav-menu-template.php'; require ABSPATH . WPINC . '/admin-bar.php'; Utils\maybe_require( '4.4-alpha-34928', ABSPATH . WPINC . '/rest-api.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' ); Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' ); // Load multisite-specific files. if ( is_multisite() ) { require ABSPATH . WPINC . '/ms-functions.php'; require ABSPATH . WPINC . '/ms-default-filters.php'; require ABSPATH . WPINC . '/ms-deprecated.php'; } // Define constants that rely on the API to obtain the default value. // Define must-use plugin directory constants, which may be overridden in the sunrise.php drop-in. wp_plugin_directory_constants(); $symlinked_plugins_supported = function_exists( 'wp_register_plugin_realpath' ); if ( $symlinked_plugins_supported ) { $GLOBALS['wp_plugin_paths'] = array(); } // Load must-use plugins. foreach ( wp_get_mu_plugins() as $mu_plugin ) { include_once $mu_plugin; } unset( $mu_plugin ); // Load network activated plugins. if ( is_multisite() ) { foreach ( wp_get_active_network_plugins() as $network_plugin ) { if ( $symlinked_plugins_supported ) { wp_register_plugin_realpath( $network_plugin ); } include_once $network_plugin; } unset( $network_plugin ); } do_action( 'muplugins_loaded' ); if ( is_multisite() ) { ms_cookie_constants(); } // Define constants after multisite is loaded. Cookie-related constants may be overridden in ms_network_cookies(). wp_cookie_constants(); // Define and enforce our SSL constants wp_ssl_constants(); // Create common globals. require ABSPATH . WPINC . '/vars.php'; // Make taxonomies and posts available to plugins and themes. // @plugin authors: warning: these get registered again on the init hook. create_initial_taxonomies(); create_initial_post_types(); // Register the default theme directory root register_theme_directory( get_theme_root() ); // Load active plugins. foreach ( wp_get_active_and_valid_plugins() as $plugin ) { if ( $symlinked_plugins_supported ) { wp_register_plugin_realpath( $plugin ); } include_once $plugin; } unset( $plugin, $symlinked_plugins_supported ); // Load pluggable functions. require ABSPATH . WPINC . '/pluggable.php'; require ABSPATH . WPINC . '/pluggable-deprecated.php'; // Set internal encoding. wp_set_internal_encoding(); // Run wp_cache_postload() if object cache is enabled and the function exists. if ( WP_CACHE && function_exists( 'wp_cache_postload' ) ) { wp_cache_postload(); } do_action( 'plugins_loaded' ); // Define constants which affect functionality if not already defined. wp_functionality_constants(); // Add magic quotes and set up $_REQUEST ( $_GET + $_POST ) wp_magic_quotes(); do_action( 'sanitize_comment_cookies' ); /** * WordPress Query object * @global object $wp_the_query * @since 2.0.0 */ $GLOBALS['wp_the_query'] = new WP_Query(); /** * Holds the reference to @see $wp_the_query * Use this global for WordPress queries * @global object $wp_query * @since 1.5.0 */ $GLOBALS['wp_query'] = $GLOBALS['wp_the_query']; /** * Holds the WordPress Rewrite object for creating pretty URLs * @global object $wp_rewrite * @since 1.5.0 */ $GLOBALS['wp_rewrite'] = new WP_Rewrite(); /** * WordPress Object * @global object $wp * @since 2.0.0 */ $GLOBALS['wp'] = new WP(); /** * WordPress Widget Factory Object * @global object $wp_widget_factory * @since 2.8.0 */ $GLOBALS['wp_widget_factory'] = new WP_Widget_Factory(); /** * WordPress User Roles * @global object $wp_roles * @since 2.0.0 */ $GLOBALS['wp_roles'] = new WP_Roles(); do_action( 'setup_theme' ); // Define the template related constants. wp_templating_constants(); // Load the default text localization domain. load_default_textdomain(); $locale = get_locale(); $locale_file = WP_LANG_DIR . "/$locale.php"; if ( ( 0 === validate_file( $locale ) ) && is_readable( $locale_file ) ) { require $locale_file; } unset( $locale_file ); // Pull in locale data after loading text domain. require_once ABSPATH . WPINC . '/locale.php'; /** * WordPress Locale object for loading locale domain date and various strings. * @global object $wp_locale * @since 2.1.0 */ $GLOBALS['wp_locale'] = new WP_Locale(); // Load the functions for the active theme, for both parent and child theme if applicable. global $pagenow; if ( ! defined( 'WP_INSTALLING' ) || 'wp-activate.php' === $pagenow ) { if ( TEMPLATEPATH !== STYLESHEETPATH && file_exists( STYLESHEETPATH . '/functions.php' ) ) { include STYLESHEETPATH . '/functions.php'; } if ( file_exists( TEMPLATEPATH . '/functions.php' ) ) { include TEMPLATEPATH . '/functions.php'; } } do_action( 'after_setup_theme' ); // Set up current user. $GLOBALS['wp']->init(); /** * Most of WP is loaded at this stage, and the user is authenticated. WP continues * to load on the init hook that follows (e.g. widgets), and many plugins instantiate * themselves on it for all sorts of reasons (e.g. they need a user, a taxonomy, etc.). * * If you wish to plug an action once WP is loaded, use the wp_loaded hook below. */ do_action( 'init' ); // Check site status # if ( is_multisite() ) { // WP-CLI if ( is_multisite() && ! defined( 'WP_INSTALLING' ) ) { if ( true !== ( $file = ms_site_check() ) ) { require $file; die(); } unset( $file ); } /** * This hook is fired once WP, all plugins, and the theme are fully loaded and instantiated. * * AJAX requests should use wp-admin/admin-ajax.php. admin-ajax.php can handle requests for * users not logged in. * * @link http://codex.wordpress.org/AJAX_in_Plugins * * @since 3.0.0 */ do_action( 'wp_loaded' ); ...] * : Get help on a specific command. * * ## EXAMPLES * * # get help for `core` command * wp help core * * # get help for `core download` subcommand * wp help core download */ public function __invoke( $args, $assoc_args ) { $r = WP_CLI::get_runner()->find_command_to_run( $args ); if ( is_array( $r ) ) { list( $command ) = $r; self::show_help( $command ); exit; } } private static function show_help( $command ) { $out = self::get_initial_markdown( $command ); // Remove subcommands if in columns - will wordwrap separately. $subcommands = ''; $column_subpattern = '[ \t]+[^\t]+\t+'; if ( preg_match( '/(^## SUBCOMMANDS[^\n]*\n+' . $column_subpattern . '.+?)(?:^##|\z)/ms', $out, $matches, PREG_OFFSET_CAPTURE ) ) { $subcommands = $matches[1][0]; $subcommands_header = "## SUBCOMMANDS\n"; $out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) ); } $out .= $command->get_longdesc(); // definition lists $out = preg_replace_callback( '/([^\n]+)\n: (.+?)(\n\n|$)/s', array( __CLASS__, 'rewrap_param_desc' ), $out ); // Ensure all non-section headers are indented. $out = preg_replace( '#^([^\s^\#])#m', "\t$1", $out ); $tab = str_repeat( ' ', 2 ); // Need to de-tab for wordwrapping to work properly. $out = str_replace( "\t", $tab, $out ); $wordwrap_width = \cli\Shell::columns(); // Wordwrap with indent. $out = preg_replace_callback( '/^( *)([^\n]+)\n/m', function ( $matches ) use ( $wordwrap_width ) { return $matches[1] . str_replace( "\n", "\n{$matches[1]}", wordwrap( $matches[2], $wordwrap_width - strlen( $matches[1] ) ) ) . "\n"; }, $out ); if ( $subcommands ) { // Wordwrap with column indent. $subcommands = preg_replace_callback( '/^(' . $column_subpattern . ')([^\n]+)\n/m', function ( $matches ) use ( $wordwrap_width, $tab ) { // Need to de-tab for wordwrapping to work properly. $matches[1] = str_replace( "\t", $tab, $matches[1] ); $matches[2] = str_replace( "\t", $tab, $matches[2] ); $padding_len = strlen( $matches[1] ); $padding = str_repeat( ' ', $padding_len ); return $matches[1] . str_replace( "\n", "\n$padding", wordwrap( $matches[2], $wordwrap_width - $padding_len ) ) . "\n"; }, $subcommands ); // Put subcommands back. $out = str_replace( $subcommands_header, $subcommands, $out ); } // section headers $out = preg_replace( '/^## ([A-Z ]+)/m', WP_CLI::colorize( '%9\1%n' ), $out ); self::pass_through_pager( $out ); } private static function rewrap_param_desc( $matches ) { $param = $matches[1]; $desc = self::indent( "\t\t", $matches[2] ); return "\t$param\n$desc\n\n"; } private static function indent( $whitespace, $text ) { $lines = explode( "\n", $text ); foreach ( $lines as &$line ) { $line = $whitespace . $line; } return implode( $lines, "\n" ); } private static function pass_through_pager( $out ) { if ( ! Utils\check_proc_available( null /*context*/, true /*return*/ ) ) { WP_CLI::debug( 'Warning: check_proc_available() failed in pass_through_pager().', 'help' ); return $out; } if ( false === ( $pager = getenv( 'PAGER' ) ) ) { $pager = Utils\is_windows() ? 'more' : 'less -r'; } // convert string to file handle $fd = fopen( 'php://temp', 'r+b' ); fwrite( $fd, $out ); rewind( $fd ); $descriptorspec = array( 0 => $fd, 1 => STDOUT, 2 => STDERR, ); return proc_close( proc_open( $pager, $descriptorspec, $pipes ) ); } private static function get_initial_markdown( $command ) { $name = implode( ' ', Dispatcher\get_path( $command ) ); $binding = array( 'name' => $name, 'shortdesc' => $command->get_shortdesc(), ); $binding['synopsis'] = "$name " . $command->get_synopsis(); $alias = $command->get_alias(); if ( $alias ) { $binding['alias'] = $alias; } if ( $command->can_have_subcommands() ) { $binding['has-subcommands']['subcommands'] = self::render_subcommands( $command ); } return Utils\mustache_render( 'man.mustache', $binding ); } private static function render_subcommands( $command ) { $subcommands = array(); foreach ( $command->get_subcommands() as $subcommand ) { if ( WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) { continue; } $subcommands[ $subcommand->get_name() ] = $subcommand->get_shortdesc(); } $max_len = self::get_max_len( array_keys( $subcommands ) ); $lines = array(); foreach ( $subcommands as $name => $desc ) { $lines[] = str_pad( $name, $max_len ) . "\t\t\t" . $desc; } return $lines; } private static function get_max_len( $strings ) { $max_len = 0; foreach ( $strings as $str ) { $len = strlen( $str ); if ( $len > $max_len ) { $max_len = $len; } } return $max_len; } } WP_CLI::add_command( 'help', 'Help_Command' ); $command->get_name(), 'description' => $command->get_shortdesc(), 'longdesc' => $command->get_longdesc(), ); foreach ( $command->get_subcommands() as $subcommand ) { $dump['subcommands'][] = $this->command_to_array( $subcommand ); } if ( empty( $dump['subcommands'] ) ) { $dump['synopsis'] = (string) $command->get_synopsis(); } return $dump; } /** * Print WP-CLI version. * * ## EXAMPLES * * # Display CLI version. * $ wp cli version * WP-CLI 0.24.1 */ public function version() { WP_CLI::line( 'WP-CLI ' . WP_CLI_VERSION ); } /** * Print various details about the WP-CLI environment. * * Helpful for diagnostic purposes, this command shares: * * * PHP binary used. * * PHP binary version. * * php.ini configuration file used (which is typically different than web). * * WP-CLI root dir: where WP-CLI is installed (if non-Phar install). * * WP-CLI global config: where the global config YAML file is located. * * WP-CLI project config: where the project config YAML file is located. * * WP-CLI version: currently installed version. * * See [config docs](https://wp-cli.org/config/) for more details on global * and project config YAML files. * * ## OPTIONS * * [--format=] * : Render output in a particular format. * --- * default: list * options: * - list * - json * --- * * ## EXAMPLES * * # Display various data about the CLI environment. * $ wp cli info * PHP binary: /usr/bin/php5 * PHP version: 5.5.9-1ubuntu4.16 * php.ini used: /etc/php5/cli/php.ini * WP-CLI root dir: phar://wp-cli.phar * WP-CLI packages dir: /home/person/.wp-cli/packages/ * WP-CLI global config: * WP-CLI project config: * WP-CLI version: 0.24.1 */ public function info( $_, $assoc_args ) { $php_bin = WP_CLI::get_php_binary(); $runner = WP_CLI::get_runner(); $packages_dir = $runner->get_packages_dir_path(); if ( ! is_dir( $packages_dir ) ) { $packages_dir = null; } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { $info = array( 'php_binary_path' => $php_bin, 'global_config_path' => $runner->global_config_path, 'project_config_path' => $runner->project_config_path, 'wp_cli_dir_path' => WP_CLI_ROOT, 'wp_cli_packages_dir_path' => $packages_dir, 'wp_cli_version' => WP_CLI_VERSION, ); WP_CLI::line( json_encode( $info ) ); } else { WP_CLI::line( "PHP binary:\t" . $php_bin ); WP_CLI::line( "PHP version:\t" . PHP_VERSION ); WP_CLI::line( "php.ini used:\t" . get_cfg_var( 'cfg_file_path' ) ); WP_CLI::line( "WP-CLI root dir:\t" . WP_CLI_ROOT ); WP_CLI::line( "WP-CLI vendor dir:\t" . WP_CLI_VENDOR_DIR ); WP_CLI::line( "WP_CLI phar path:\t" . ( defined( 'WP_CLI_PHAR_PATH' ) ? WP_CLI_PHAR_PATH : '' ) ); WP_CLI::line( "WP-CLI packages dir:\t" . $packages_dir ); WP_CLI::line( "WP-CLI global config:\t" . $runner->global_config_path ); WP_CLI::line( "WP-CLI project config:\t" . $runner->project_config_path ); WP_CLI::line( "WP-CLI version:\t" . WP_CLI_VERSION ); } } /** * Check to see if there is a newer version of WP-CLI available. * * Queries the Github releases API. Returns available versions if there are * updates available, or success message if using the latest release. * * ## OPTIONS * * [--patch] * : Only list patch updates. * * [--minor] * : Only list minor updates. * * [--major] * : Only list major updates. * * [--field=] * : Prints the value of a single field for each update. * * [--fields=] * : Limit the output to specific object fields. Defaults to version,update_type,package_url. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * ## EXAMPLES * * # Check for update. * $ wp cli check-update * Success: WP-CLI is at the latest version. * * # Check for update and new version is available. * $ wp cli check-update * +---------+-------------+-------------------------------------------------------------------------------+ * | version | update_type | package_url | * +---------+-------------+-------------------------------------------------------------------------------+ * | 0.24.1 | patch | https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar | * +---------+-------------+-------------------------------------------------------------------------------+ * * @subcommand check-update */ public function check_update( $_, $assoc_args ) { $updates = $this->get_updates( $assoc_args ); if ( $updates ) { $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'version', 'update_type', 'package_url' ) ); $formatter->display_items( $updates ); } elseif ( empty( $assoc_args['format'] ) || 'table' == $assoc_args['format'] ) { $update_type = $this->get_update_type_str( $assoc_args ); WP_CLI::success( "WP-CLI is at the latest{$update_type}version." ); } } /** * Update WP-CLI to the latest release. * * Default behavior is to check the releases API for the newest stable * version, and prompt if one is available. * * Use `--stable` to install or reinstall the latest stable version. * * Use `--nightly` to install the latest built version of the master branch. * While not recommended for production, nightly contains the latest and * greatest, and should be stable enough for development and staging * environments. * * Only works for the Phar installation mechanism. * * ## OPTIONS * * [--patch] * : Only perform patch updates. * * [--minor] * : Only perform minor updates. * * [--major] * : Only perform major updates. * * [--stable] * : Update to the latest stable release. Skips update check. * * [--nightly] * : Update to the latest built version of the master branch. Potentially unstable. * * [--yes] * : Do not prompt for confirmation. * * ## EXAMPLES * * # Update CLI. * $ wp cli update * You have version 0.24.0. Would you like to update to 0.24.1? [y/n] y * Downloading from https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar... * New version works. Proceeding to replace. * Success: Updated WP-CLI to 0.24.1. */ public function update( $_, $assoc_args ) { if ( ! Utils\inside_phar() ) { WP_CLI::error( 'You can only self-update Phar files.' ); } $old_phar = realpath( $_SERVER['argv'][0] ); if ( ! is_writable( $old_phar ) ) { WP_CLI::error( sprintf( '%s is not writable by current user.', $old_phar ) ); } elseif ( ! is_writable( dirname( $old_phar ) ) ) { WP_CLI::error( sprintf( '%s is not writable by current user.', dirname( $old_phar ) ) ); } if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) { WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to the latest nightly?', WP_CLI_VERSION ), $assoc_args ); $download_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar'; $md5_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar.md5'; } elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) { WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to the latest stable release?', WP_CLI_VERSION ), $assoc_args ); $download_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'; $md5_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.md5'; } else { $updates = $this->get_updates( $assoc_args ); if ( empty( $updates ) ) { $update_type = $this->get_update_type_str( $assoc_args ); WP_CLI::success( "WP-CLI is at the latest{$update_type}version." ); return; } $newest = $updates[0]; WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to %s?', WP_CLI_VERSION, $newest['version'] ), $assoc_args ); $download_url = $newest['package_url']; $md5_url = str_replace( '.phar', '.phar.md5', $download_url ); } WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) ); $temp = \WP_CLI\Utils\get_temp_dir() . uniqid( 'wp_', true ) . '.phar'; $headers = array(); $options = array( 'timeout' => 600, // 10 minutes ought to be enough for everybody. 'filename' => $temp, ); Utils\http_request( 'GET', $download_url, null, $headers, $options ); $md5_response = Utils\http_request( 'GET', $md5_url ); if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$md5_response->status_code})." ); } $md5_file = md5_file( $temp ); $release_hash = trim( $md5_response->body ); if ( $md5_file === $release_hash ) { WP_CLI::log( 'md5 hash verified: ' . $release_hash ); } else { WP_CLI::error( "md5 hash for download ({$md5_file}) is different than the release hash ({$release_hash})." ); } $allow_root = WP_CLI::get_runner()->config['allow-root'] ? '--allow-root' : ''; $php_binary = WP_CLI::get_php_binary(); $process = WP_CLI\Process::create( "{$php_binary} $temp --info {$allow_root}" ); $result = $process->run(); if ( 0 !== $result->return_code || false === stripos( $result->stdout, 'WP-CLI version:' ) ) { $multi_line = explode( PHP_EOL, $result->stderr ); WP_CLI::error_multi_line( $multi_line ); WP_CLI::error( 'The downloaded PHAR is broken, try running wp cli update again.' ); } WP_CLI::log( 'New version works. Proceeding to replace.' ); $mode = fileperms( $old_phar ) & 511; if ( false === chmod( $temp, $mode ) ) { WP_CLI::error( sprintf( 'Cannot chmod %s.', $temp ) ); } class_exists( '\cli\Colors' ); // This autoloads \cli\Colors - after we move the file we no longer have access to this class. if ( false === rename( $temp, $old_phar ) ) { WP_CLI::error( sprintf( 'Cannot move %s to %s', $temp, $old_phar ) ); } if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) { $updated_version = 'the latest nightly release'; } elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) { $updated_version = 'the latest stable release'; } else { $updated_version = $newest['version']; } WP_CLI::success( sprintf( 'Updated WP-CLI to %s.', $updated_version ) ); } /** * Returns update information. */ private function get_updates( $assoc_args ) { $url = 'https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100'; $options = array( 'timeout' => 30, ); $headers = array( 'Accept' => 'application/json', ); if ( $github_token = getenv( 'GITHUB_TOKEN' ) ) { $headers['Authorization'] = 'token ' . $github_token; } $response = Utils\http_request( 'GET', $url, null, $headers, $options ); if ( ! $response->success || 200 !== $response->status_code ) { WP_CLI::error( sprintf( 'Failed to get latest version (HTTP code %d).', $response->status_code ) ); } $release_data = json_decode( $response->body ); $updates = array( 'major' => false, 'minor' => false, 'patch' => false, ); foreach ( $release_data as $release ) { // Get rid of leading "v" if there is one set. $release_version = $release->tag_name; if ( 'v' === substr( $release_version, 0, 1 ) ) { $release_version = ltrim( $release_version, 'v' ); } $update_type = Utils\get_named_sem_ver( $release_version, WP_CLI_VERSION ); if ( ! $update_type ) { continue; } if ( ! empty( $updates[ $update_type ] ) && ! Comparator::greaterThan( $release_version, $updates[ $update_type ]['version'] ) ) { continue; } $updates[ $update_type ] = array( 'version' => $release_version, 'update_type' => $update_type, 'package_url' => $release->assets[0]->browser_download_url, ); } foreach ( $updates as $type => $value ) { if ( empty( $value ) ) { unset( $updates[ $type ] ); } } foreach ( array( 'major', 'minor', 'patch' ) as $type ) { if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, $type ) ) { return ! empty( $updates[ $type ] ) ? array( $updates[ $type ] ) : false; } } if ( empty( $updates ) && preg_match( '#-alpha-(.+)$#', WP_CLI_VERSION, $matches ) ) { $version_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/NIGHTLY_VERSION'; $response = Utils\http_request( 'GET', $version_url ); if ( ! $response->success || 200 !== $response->status_code ) { WP_CLI::error( sprintf( 'Failed to get current nightly version (HTTP code %d)', $response->status_code ) ); } $nightly_version = trim( $response->body ); if ( WP_CLI_VERSION != $nightly_version ) { $updates['nightly'] = array( 'version' => $nightly_version, 'update_type' => 'nightly', 'package_url' => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar', ); } } return array_values( $updates ); } /** * Dump the list of global parameters, as JSON or in var_export format. * * ## OPTIONS * * [--with-values] * : Display current values also. * * [--format=] * : Render output in a particular format. * --- * default: json * options: * - var_export * - json * --- * * ## EXAMPLES * * # Dump the list of global parameters. * $ wp cli param-dump --format=var_export * array ( * 'path' => * array ( * 'runtime' => '=', * 'file' => '', * 'synopsis' => '', * 'default' => NULL, * 'multiple' => false, * 'desc' => 'Path to the WordPress files.', * ), * 'url' => * array ( * * @subcommand param-dump */ public function param_dump( $_, $assoc_args ) { $spec = \WP_CLI::get_configurator()->get_spec(); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'with-values' ) ) { $config = \WP_CLI::get_configurator()->to_array(); // Copy current config values to $spec foreach ( $spec as $key => $value ) { $current = null; if ( isset( $config[0][ $key ] ) ) { $current = $config[0][ $key ]; } $spec[ $key ]['current'] = $current; } } if ( 'var_export' === \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) ) { var_export( $spec ); } else { echo json_encode( $spec ); } } /** * Dump the list of installed commands, as JSON. * * ## EXAMPLES * * # Dump the list of installed commands. * $ wp cli cmd-dump * {"name":"wp","description":"Manage WordPress through the command-line.","longdesc":"\n\n## GLOBAL PARAMETERS\n\n --path=\n Path to the WordPress files.\n\n --ssh=\n Perform operation against a remote server over SSH (or a container using scheme of "docker" or "docker-compose").\n\n --url=\n Pretend request came from given URL. In multisite, this argument is how the target site is specified. \n\n --user=\n * * @subcommand cmd-dump */ public function cmd_dump() { echo json_encode( $this->command_to_array( WP_CLI::get_root_command() ) ); } /** * Generate tab completion strings. * * ## OPTIONS * * --line= * : The current command line to be executed. * * --point= * : The index to the current cursor position relative to the beginning of the command. * * ## EXAMPLES * * # Generate tab completion strings. * $ wp cli completions --line='wp eva' --point=100 * eval * eval-file */ public function completions( $_, $assoc_args ) { $line = substr( $assoc_args['line'], 0, $assoc_args['point'] ); $compl = new \WP_CLI\Completions( $line ); $compl->render(); } /** * List available WP-CLI aliases. * * Aliases are shorthand references to WordPress installs. For instance, * `@dev` could refer to a development install and `@prod` could refer to * a production install. This command gives you visibility in what * registered aliases you have available. * * ## OPTIONS * * [--format=] * : Render output in a particular format. * --- * default: yaml * options: * - yaml * - json * --- * * ## EXAMPLES * * # List all available aliases. * $ wp cli alias * --- * @all: Run command against every registered alias. * @prod: * ssh: runcommand@runcommand.io~/webapps/production * @dev: * ssh: vagrant@192.168.50.10/srv/www/runcommand.dev * @both: * - @prod * - @dev * * @alias aliases */ public function alias( $_, $assoc_args ) { WP_CLI::print_value( WP_CLI::get_runner()->aliases, $assoc_args ); } /** * Get a string representing the type of update being checked for. */ private function get_update_type_str( $assoc_args ) { $update_type = ' '; foreach ( array( 'major', 'minor', 'patch' ) as $type ) { if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, $type ) ) { $update_type = ' ' . $type . ' '; break; } } return $update_type; } /** * Detects if a command exists * * This commands checks if a command is registered with WP-CLI. * If the command is found then it returns with exit status 0. * If the command doesn't exist, then it will exit with status 1. * * ## OPTIONS * ... * : The command * * ## EXAMPLES * * # The "site delete" command is registered. * $ wp cli has-command "site delete" * $ echo $? * 0 * * # The "foo bar" command is not registered. * $ wp cli has-command "foo bar" * $ echo $? * 1 * * @subcommand has-command * * @when after_wp_load */ public function has_command( $_, $assoc_args ) { // If command is input as a string, then explode it into array. $command = explode( ' ', implode( ' ', $_ ) ); WP_CLI::halt( is_array( WP_CLI::get_runner()->find_command_to_run( $command ) ) ? 0 : 1 ); } } array( 'runtime' => '=', 'file' => '', 'desc' => 'Path to the WordPress files.', ), 'url' => array( 'runtime' => '=', 'file' => '', 'desc' => 'Pretend request came from given URL. In multisite, this argument is how the target site is specified.', ), 'ssh' => array( 'runtime' => '=[:][@][:][]', 'file' => '[:][@][:][]', 'desc' => 'Perform operation against a remote server over SSH (or a container using scheme of "docker", "docker-compose", "vagrant").', ), 'http' => array( 'runtime' => '=', 'file' => '', 'desc' => 'Perform operation against a remote WordPress install over HTTP.', ), 'blog' => array( 'deprecated' => 'Use --url instead.', 'runtime' => '=', ), 'user' => array( 'runtime' => '=', 'file' => '', 'desc' => 'Set the WordPress user.', ), 'skip-plugins' => array( 'runtime' => '[=]', 'file' => '', 'desc' => 'Skip loading all or some plugins. Note: mu-plugins are still loaded.', 'default' => '', ), 'skip-themes' => array( 'runtime' => '[=]', 'file' => '', 'desc' => 'Skip loading all or some themes.', 'default' => '', ), 'skip-packages' => array( 'runtime' => '', 'file' => '', 'desc' => 'Skip loading all installed packages.', 'default' => false, ), 'require' => array( 'runtime' => '=', 'file' => '', 'desc' => 'Load PHP file before running the command (may be used more than once).', 'multiple' => true, 'default' => array(), ), 'disabled_commands' => array( 'file' => '', 'default' => array(), 'desc' => '(Sub)commands to disable.', ), 'color' => array( 'runtime' => true, 'file' => '', 'default' => 'auto', 'desc' => 'Whether to colorize the output.', ), 'debug' => array( 'runtime' => '[=]', 'file' => '', 'default' => false, 'desc' => 'Show all PHP errors; add verbosity to WP-CLI bootstrap.', ), 'prompt' => array( 'runtime' => '[=]', 'file' => false, 'default' => false, 'desc' => 'Prompt the user to enter values for all command arguments, or a subset specified as comma-separated values.', ), 'quiet' => array( 'runtime' => '', 'file' => '', 'default' => false, 'desc' => 'Suppress informational messages.', ), 'apache_modules' => array( 'file' => '', 'desc' => 'List of Apache Modules that are to be reported as loaded.', 'multiple' => true, 'default' => array(), ), # --allow-root => (NOT RECOMMENDED) Allow wp-cli to run as root. This poses # a security risk, so you probably do not want to do this. 'allow-root' => array( 'file' => false, # Explicit. Just in case the default changes. 'runtime' => '', 'hidden' => true, ), ); get_name() ); } while ( $command = $command->get_parent() ); return $path; } */ class SymfonyClassCollectionLoader { private static $loaded; const HEADER = <<<'EOS' \s*$/'), '', file_get_contents($r->getFileName())); } $cache = $cacheDir . '/' . $name . $extension; $header = sprintf(self::HEADER, strftime('%Y')); self::writeCacheFile($cache, $header . substr(self::stripComments(' $helper` pairs. * * @throws Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable * * @param array|Traversable $helpers (default: null) */ public function __construct($helpers = null) { if ($helpers === null) { return; } if (!is_array($helpers) && !$helpers instanceof Traversable) { throw new Mustache_Exception_InvalidArgumentException('HelperCollection constructor expects an array of helpers'); } foreach ($helpers as $name => $helper) { $this->add($name, $helper); } } /** * Magic mutator. * * @see Mustache_HelperCollection::add * * @param string $name * @param mixed $helper */ public function __set($name, $helper) { $this->add($name, $helper); } /** * Add a helper to this collection. * * @param string $name * @param mixed $helper */ public function add($name, $helper) { $this->helpers[$name] = $helper; } /** * Magic accessor. * * @see Mustache_HelperCollection::get * * @param string $name * * @return mixed Helper */ public function __get($name) { return $this->get($name); } /** * Get a helper by name. * * @throws Mustache_Exception_UnknownHelperException If helper does not exist * * @param string $name * * @return mixed Helper */ public function get($name) { if (!$this->has($name)) { throw new Mustache_Exception_UnknownHelperException($name); } return $this->helpers[$name]; } /** * Magic isset(). * * @see Mustache_HelperCollection::has * * @param string $name * * @return bool True if helper is present */ public function __isset($name) { return $this->has($name); } /** * Check whether a given helper is present in the collection. * * @param string $name * * @return bool True if helper is present */ public function has($name) { return array_key_exists($name, $this->helpers); } /** * Magic unset(). * * @see Mustache_HelperCollection::remove * * @param string $name */ public function __unset($name) { $this->remove($name); } /** * Check whether a given helper is present in the collection. * * @throws Mustache_Exception_UnknownHelperException if the requested helper is not present * * @param string $name */ public function remove($name) { if (!$this->has($name)) { throw new Mustache_Exception_UnknownHelperException($name); } unset($this->helpers[$name]); } /** * Clear the helper collection. * * Removes all helpers from this collection */ public function clear() { $this->helpers = array(); } /** * Check whether the helper collection is empty. * * @return bool True if the collection is empty */ public function isEmpty() { return empty($this->helpers); } } baseDir = $realDir; } else { $this->baseDir = $baseDir; } } /** * Register a new instance as an SPL autoloader. * * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..') * * @return Mustache_Autoloader Registered Autoloader instance */ public static function register($baseDir = null) { $key = $baseDir ? $baseDir : 0; if (!isset(self::$instances[$key])) { self::$instances[$key] = new self($baseDir); } $loader = self::$instances[$key]; spl_autoload_register(array($loader, 'autoload')); return $loader; } /** * Autoload Mustache classes. * * @param string $class */ public function autoload($class) { if ($class[0] === '\\') { $class = substr($class, 1); } if (strpos($class, 'Mustache') !== 0) { return; } $file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class)); if (is_file($file)) { require $file; } } } true, self::PRAGMA_BLOCKS => true, self::PRAGMA_ANCHORED_DOT => true, ); // Template cache private $templates = array(); // Environment private $templateClassPrefix = '__Mustache_'; private $cache; private $lambdaCache; private $cacheLambdaTemplates = false; private $loader; private $partialsLoader; private $helpers; private $escape; private $entityFlags = ENT_COMPAT; private $charset = 'UTF-8'; private $logger; private $strictCallables = false; private $pragmas = array(); private $delimiters; // Services private $tokenizer; private $parser; private $compiler; /** * Mustache class constructor. * * Passing an $options array allows overriding certain Mustache options during instantiation: * * $options = array( * // The class prefix for compiled templates. Defaults to '__Mustache_'. * 'template_class_prefix' => '__MyTemplates_', * * // A Mustache cache instance or a cache directory string for compiled templates. * // Mustache will not cache templates unless this is set. * 'cache' => dirname(__FILE__).'/tmp/cache/mustache', * * // Override default permissions for cache files. Defaults to using the system-defined umask. It is * // *strongly* recommended that you configure your umask properly rather than overriding permissions here. * 'cache_file_mode' => 0666, * * // Optionally, enable caching for lambda section templates. This is generally not recommended, as lambda * // sections are often too dynamic to benefit from caching. * 'cache_lambda_templates' => true, * * // Customize the tag delimiters used by this engine instance. Note that overriding here changes the * // delimiters used to parse all templates and partials loaded by this instance. To override just for a * // single template, use an inline "change delimiters" tag at the start of the template file: * // * // {{=<% %>=}} * // * 'delimiters' => '<% %>', * * // A Mustache template loader instance. Uses a StringLoader if not specified. * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'), * * // A Mustache loader instance for partials. * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'), * * // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as * // efficient or lazy as a Filesystem (or database) loader. * 'partials' => array('foo' => file_get_contents(dirname(__FILE__).'/views/partials/foo.mustache')), * * // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order * // sections), or any other valid Mustache context value. They will be prepended to the context stack, * // so they will be available in any template loaded by this Mustache instance. * 'helpers' => array('i18n' => function ($text) { * // do something translatey here... * }), * * // An 'escape' callback, responsible for escaping double-mustache variables. * 'escape' => function ($value) { * return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8'); * }, * * // Type argument for `htmlspecialchars`. Defaults to ENT_COMPAT. You may prefer ENT_QUOTES. * 'entity_flags' => ENT_QUOTES, * * // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'. * 'charset' => 'ISO-8859-1', * * // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible * // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is * // available as well: * 'logger' => new Mustache_Logger_StreamLogger('php://stderr'), * * // Only treat Closure instances and invokable classes as callable. If true, values like * // `array('ClassName', 'methodName')` and `array($classInstance, 'methodName')`, which are traditionally * // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This * // helps protect against arbitrary code execution when user input is passed directly into the template. * // This currently defaults to false, but will default to true in v3.0. * 'strict_callables' => true, * * // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual * // templates. * 'pragmas' => [Mustache_Engine::PRAGMA_FILTERS], * ); * * @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable * * @param array $options (default: array()) */ public function __construct(array $options = array()) { if (isset($options['template_class_prefix'])) { if ((string) $options['template_class_prefix'] === '') { throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "template_class_prefix" must not be empty'); } $this->templateClassPrefix = $options['template_class_prefix']; } if (isset($options['cache'])) { $cache = $options['cache']; if (is_string($cache)) { $mode = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null; $cache = new Mustache_Cache_FilesystemCache($cache, $mode); } $this->setCache($cache); } if (isset($options['cache_lambda_templates'])) { $this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates']; } if (isset($options['loader'])) { $this->setLoader($options['loader']); } if (isset($options['partials_loader'])) { $this->setPartialsLoader($options['partials_loader']); } if (isset($options['partials'])) { $this->setPartials($options['partials']); } if (isset($options['helpers'])) { $this->setHelpers($options['helpers']); } if (isset($options['escape'])) { if (!is_callable($options['escape'])) { throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "escape" option must be callable'); } $this->escape = $options['escape']; } if (isset($options['entity_flags'])) { $this->entityFlags = $options['entity_flags']; } if (isset($options['charset'])) { $this->charset = $options['charset']; } if (isset($options['logger'])) { $this->setLogger($options['logger']); } if (isset($options['strict_callables'])) { $this->strictCallables = $options['strict_callables']; } if (isset($options['delimiters'])) { $this->delimiters = $options['delimiters']; } if (isset($options['pragmas'])) { foreach ($options['pragmas'] as $pragma) { if (!isset(self::$knownPragmas[$pragma])) { throw new Mustache_Exception_InvalidArgumentException(sprintf('Unknown pragma: "%s".', $pragma)); } $this->pragmas[$pragma] = true; } } } /** * Shortcut 'render' invocation. * * Equivalent to calling `$mustache->loadTemplate($template)->render($context);` * * @see Mustache_Engine::loadTemplate * @see Mustache_Template::render * * @param string $template * @param mixed $context (default: array()) * * @return string Rendered template */ public function render($template, $context = array()) { return $this->loadTemplate($template)->render($context); } /** * Get the current Mustache escape callback. * * @return callable|null */ public function getEscape() { return $this->escape; } /** * Get the current Mustache entitity type to escape. * * @return int */ public function getEntityFlags() { return $this->entityFlags; } /** * Get the current Mustache character set. * * @return string */ public function getCharset() { return $this->charset; } /** * Get the current globally enabled pragmas. * * @return array */ public function getPragmas() { return array_keys($this->pragmas); } /** * Set the Mustache template Loader instance. * * @param Mustache_Loader $loader */ public function setLoader(Mustache_Loader $loader) { $this->loader = $loader; } /** * Get the current Mustache template Loader instance. * * If no Loader instance has been explicitly specified, this method will instantiate and return * a StringLoader instance. * * @return Mustache_Loader */ public function getLoader() { if (!isset($this->loader)) { $this->loader = new Mustache_Loader_StringLoader(); } return $this->loader; } /** * Set the Mustache partials Loader instance. * * @param Mustache_Loader $partialsLoader */ public function setPartialsLoader(Mustache_Loader $partialsLoader) { $this->partialsLoader = $partialsLoader; } /** * Get the current Mustache partials Loader instance. * * If no Loader instance has been explicitly specified, this method will instantiate and return * an ArrayLoader instance. * * @return Mustache_Loader */ public function getPartialsLoader() { if (!isset($this->partialsLoader)) { $this->partialsLoader = new Mustache_Loader_ArrayLoader(); } return $this->partialsLoader; } /** * Set partials for the current partials Loader instance. * * @throws Mustache_Exception_RuntimeException If the current Loader instance is immutable * * @param array $partials (default: array()) */ public function setPartials(array $partials = array()) { if (!isset($this->partialsLoader)) { $this->partialsLoader = new Mustache_Loader_ArrayLoader(); } if (!$this->partialsLoader instanceof Mustache_Loader_MutableLoader) { throw new Mustache_Exception_RuntimeException('Unable to set partials on an immutable Mustache Loader instance'); } $this->partialsLoader->setTemplates($partials); } /** * Set an array of Mustache helpers. * * An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or * any other valid Mustache context value. They will be prepended to the context stack, so they will be available in * any template loaded by this Mustache instance. * * @throws Mustache_Exception_InvalidArgumentException if $helpers is not an array or Traversable * * @param array|Traversable $helpers */ public function setHelpers($helpers) { if (!is_array($helpers) && !$helpers instanceof Traversable) { throw new Mustache_Exception_InvalidArgumentException('setHelpers expects an array of helpers'); } $this->getHelpers()->clear(); foreach ($helpers as $name => $helper) { $this->addHelper($name, $helper); } } /** * Get the current set of Mustache helpers. * * @see Mustache_Engine::setHelpers * * @return Mustache_HelperCollection */ public function getHelpers() { if (!isset($this->helpers)) { $this->helpers = new Mustache_HelperCollection(); } return $this->helpers; } /** * Add a new Mustache helper. * * @see Mustache_Engine::setHelpers * * @param string $name * @param mixed $helper */ public function addHelper($name, $helper) { $this->getHelpers()->add($name, $helper); } /** * Get a Mustache helper by name. * * @see Mustache_Engine::setHelpers * * @param string $name * * @return mixed Helper */ public function getHelper($name) { return $this->getHelpers()->get($name); } /** * Check whether this Mustache instance has a helper. * * @see Mustache_Engine::setHelpers * * @param string $name * * @return bool True if the helper is present */ public function hasHelper($name) { return $this->getHelpers()->has($name); } /** * Remove a helper by name. * * @see Mustache_Engine::setHelpers * * @param string $name */ public function removeHelper($name) { $this->getHelpers()->remove($name); } /** * Set the Mustache Logger instance. * * @throws Mustache_Exception_InvalidArgumentException If logger is not an instance of Mustache_Logger or Psr\Log\LoggerInterface * * @param Mustache_Logger|Psr\Log\LoggerInterface $logger */ public function setLogger($logger = null) { if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) { throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.'); } if ($this->getCache()->getLogger() === null) { $this->getCache()->setLogger($logger); } $this->logger = $logger; } /** * Get the current Mustache Logger instance. * * @return Mustache_Logger|Psr\Log\LoggerInterface */ public function getLogger() { return $this->logger; } /** * Set the Mustache Tokenizer instance. * * @param Mustache_Tokenizer $tokenizer */ public function setTokenizer(Mustache_Tokenizer $tokenizer) { $this->tokenizer = $tokenizer; } /** * Get the current Mustache Tokenizer instance. * * If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one. * * @return Mustache_Tokenizer */ public function getTokenizer() { if (!isset($this->tokenizer)) { $this->tokenizer = new Mustache_Tokenizer(); } return $this->tokenizer; } /** * Set the Mustache Parser instance. * * @param Mustache_Parser $parser */ public function setParser(Mustache_Parser $parser) { $this->parser = $parser; } /** * Get the current Mustache Parser instance. * * If no Parser instance has been explicitly specified, this method will instantiate and return a new one. * * @return Mustache_Parser */ public function getParser() { if (!isset($this->parser)) { $this->parser = new Mustache_Parser(); } return $this->parser; } /** * Set the Mustache Compiler instance. * * @param Mustache_Compiler $compiler */ public function setCompiler(Mustache_Compiler $compiler) { $this->compiler = $compiler; } /** * Get the current Mustache Compiler instance. * * If no Compiler instance has been explicitly specified, this method will instantiate and return a new one. * * @return Mustache_Compiler */ public function getCompiler() { if (!isset($this->compiler)) { $this->compiler = new Mustache_Compiler(); } return $this->compiler; } /** * Set the Mustache Cache instance. * * @param Mustache_Cache $cache */ public function setCache(Mustache_Cache $cache) { if (isset($this->logger) && $cache->getLogger() === null) { $cache->setLogger($this->getLogger()); } $this->cache = $cache; } /** * Get the current Mustache Cache instance. * * If no Cache instance has been explicitly specified, this method will instantiate and return a new one. * * @return Mustache_Cache */ public function getCache() { if (!isset($this->cache)) { $this->setCache(new Mustache_Cache_NoopCache()); } return $this->cache; } /** * Get the current Lambda Cache instance. * * If 'cache_lambda_templates' is enabled, this is the default cache instance. Otherwise, it is a NoopCache. * * @see Mustache_Engine::getCache * * @return Mustache_Cache */ protected function getLambdaCache() { if ($this->cacheLambdaTemplates) { return $this->getCache(); } if (!isset($this->lambdaCache)) { $this->lambdaCache = new Mustache_Cache_NoopCache(); } return $this->lambdaCache; } /** * Helper method to generate a Mustache template class. * * This method must be updated any time options are added which make it so * the same template could be parsed and compiled multiple different ways. * * @param string|Mustache_Source $source * * @return string Mustache Template class name */ public function getTemplateClassName($source) { // For the most part, adding a new option here should do the trick. // // Pick a value here which is unique for each possible way the template // could be compiled... but not necessarily unique per option value. See // escape below, which only needs to differentiate between 'custom' and // 'default' escapes. // // Keep this list in alphabetical order :) $chunks = array( 'charset' => $this->charset, 'delimiters' => $this->delimiters ? $this->delimiters : '{{ }}', 'entityFlags' => $this->entityFlags, 'escape' => isset($this->escape) ? 'custom' : 'default', 'key' => ($source instanceof Mustache_Source) ? $source->getKey() : 'source', 'pragmas' => $this->getPragmas(), 'strictCallables' => $this->strictCallables, 'version' => self::VERSION, ); $key = json_encode($chunks); // Template Source instances have already provided their own source key. For strings, just include the whole // source string in the md5 hash. if (!$source instanceof Mustache_Source) { $key .= "\n" . $source; } return $this->templateClassPrefix . md5($key); } /** * Load a Mustache Template by name. * * @param string $name * * @return Mustache_Template */ public function loadTemplate($name) { return $this->loadSource($this->getLoader()->load($name)); } /** * Load a Mustache partial Template by name. * * This is a helper method used internally by Template instances for loading partial templates. You can most likely * ignore it completely. * * @param string $name * * @return Mustache_Template */ public function loadPartial($name) { try { if (isset($this->partialsLoader)) { $loader = $this->partialsLoader; } elseif (isset($this->loader) && !$this->loader instanceof Mustache_Loader_StringLoader) { $loader = $this->loader; } else { throw new Mustache_Exception_UnknownTemplateException($name); } return $this->loadSource($loader->load($name)); } catch (Mustache_Exception_UnknownTemplateException $e) { // If the named partial cannot be found, log then return null. $this->log( Mustache_Logger::WARNING, 'Partial not found: "{name}"', array('name' => $e->getTemplateName()) ); } } /** * Load a Mustache lambda Template by source. * * This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most * likely ignore it completely. * * @param string $source * @param string $delims (default: null) * * @return Mustache_Template */ public function loadLambda($source, $delims = null) { if ($delims !== null) { $source = $delims . "\n" . $source; } return $this->loadSource($source, $this->getLambdaCache()); } /** * Instantiate and return a Mustache Template instance by source. * * Optionally provide a Mustache_Cache instance. This is used internally by Mustache_Engine::loadLambda to respect * the 'cache_lambda_templates' configuration option. * * @see Mustache_Engine::loadTemplate * @see Mustache_Engine::loadPartial * @see Mustache_Engine::loadLambda * * @param string|Mustache_Source $source * @param Mustache_Cache $cache (default: null) * * @return Mustache_Template */ private function loadSource($source, Mustache_Cache $cache = null) { $className = $this->getTemplateClassName($source); if (!isset($this->templates[$className])) { if ($cache === null) { $cache = $this->getCache(); } if (!class_exists($className, false)) { if (!$cache->load($className)) { $compiled = $this->compile($source); $cache->cache($className, $compiled); } } $this->log( Mustache_Logger::DEBUG, 'Instantiating template: "{className}"', array('className' => $className) ); $this->templates[$className] = new $className($this); } return $this->templates[$className]; } /** * Helper method to tokenize a Mustache template. * * @see Mustache_Tokenizer::scan * * @param string $source * * @return array Tokens */ private function tokenize($source) { return $this->getTokenizer()->scan($source, $this->delimiters); } /** * Helper method to parse a Mustache template. * * @see Mustache_Parser::parse * * @param string $source * * @return array Token tree */ private function parse($source) { $parser = $this->getParser(); $parser->setPragmas($this->getPragmas()); return $parser->parse($this->tokenize($source)); } /** * Helper method to compile a Mustache template. * * @see Mustache_Compiler::compile * * @param string|Mustache_Source $source * * @return string generated Mustache template class code */ private function compile($source) { $name = $this->getTemplateClassName($source); $this->log( Mustache_Logger::INFO, 'Compiling template to "{className}" class', array('className' => $name) ); if ($source instanceof Mustache_Source) { $source = $source->getSource(); } $tree = $this->parse($source); $compiler = $this->getCompiler(); $compiler->setPragmas($this->getPragmas()); return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags); } /** * Add a log record if logging is enabled. * * @param int $level The logging level * @param string $message The log message * @param array $context The log context */ private function log($level, $message, array $context = array()) { if (isset($this->logger)) { $this->logger->log($level, $message, $context); } } } cache($className, $compiledSource); * * The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k? */ class Mustache_Cache_FilesystemCache extends Mustache_Cache_AbstractCache { private $baseDir; private $fileMode; /** * Filesystem cache constructor. * * @param string $baseDir Directory for compiled templates * @param int $fileMode Override default permissions for cache files. Defaults to using the system-defined umask */ public function __construct($baseDir, $fileMode = null) { $this->baseDir = $baseDir; $this->fileMode = $fileMode; } /** * Load the class from cache using `require_once`. * * @param string $key * * @return bool */ public function load($key) { $fileName = $this->getCacheFilename($key); if (!is_file($fileName)) { return false; } require_once $fileName; return true; } /** * Cache and load the compiled class. * * @param string $key * @param string $value */ public function cache($key, $value) { $fileName = $this->getCacheFilename($key); $this->log( Mustache_Logger::DEBUG, 'Writing to template cache: "{fileName}"', array('fileName' => $fileName) ); $this->writeFile($fileName, $value); $this->load($key); } /** * Build the cache filename. * Subclasses should override for custom cache directory structures. * * @param string $name * * @return string */ protected function getCacheFilename($name) { return sprintf('%s/%s.php', $this->baseDir, $name); } /** * Create cache directory. * * @throws Mustache_Exception_RuntimeException If unable to create directory * * @param string $fileName * * @return string */ private function buildDirectoryForFilename($fileName) { $dirName = dirname($fileName); if (!is_dir($dirName)) { $this->log( Mustache_Logger::INFO, 'Creating Mustache template cache directory: "{dirName}"', array('dirName' => $dirName) ); @mkdir($dirName, 0777, true); // @codeCoverageIgnoreStart if (!is_dir($dirName)) { throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName)); } // @codeCoverageIgnoreEnd } return $dirName; } /** * Write cache file. * * @throws Mustache_Exception_RuntimeException If unable to write file * * @param string $fileName * @param string $value */ private function writeFile($fileName, $value) { $dirName = $this->buildDirectoryForFilename($fileName); $this->log( Mustache_Logger::DEBUG, 'Caching compiled template to "{fileName}"', array('fileName' => $fileName) ); $tempFile = tempnam($dirName, basename($fileName)); if (false !== @file_put_contents($tempFile, $value)) { if (@rename($tempFile, $fileName)) { $mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask()); @chmod($fileName, $mode); return; } // @codeCoverageIgnoreStart $this->log( Mustache_Logger::ERROR, 'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"', array('tempName' => $tempFile, 'fileName' => $fileName) ); // @codeCoverageIgnoreEnd } // @codeCoverageIgnoreStart throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName)); // @codeCoverageIgnoreEnd } } log( Mustache_Logger::WARNING, 'Template cache disabled, evaluating "{className}" class at runtime', array('className' => $key) ); eval('?>' . $value); } } logger; } /** * Set a logger instance. * * @param Mustache_Logger|Psr\Log\LoggerInterface $logger */ public function setLogger($logger = null) { if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) { throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.'); } $this->logger = $logger; } /** * Add a log record if logging is enabled. * * @param int $level The logging level * @param string $message The log message * @param array $context The log context */ protected function log($level, $message, array $context = array()) { if (isset($this->logger)) { $this->logger->log($level, $message, $context); } } } stack = array($context); } } /** * Push a new Context frame onto the stack. * * @param mixed $value Object or array to use for context */ public function push($value) { array_push($this->stack, $value); } /** * Push a new Context frame onto the block context stack. * * @param mixed $value Object or array to use for block context */ public function pushBlockContext($value) { array_push($this->blockStack, $value); } /** * Pop the last Context frame from the stack. * * @return mixed Last Context frame (object or array) */ public function pop() { return array_pop($this->stack); } /** * Pop the last block Context frame from the stack. * * @return mixed Last block Context frame (object or array) */ public function popBlockContext() { return array_pop($this->blockStack); } /** * Get the last Context frame. * * @return mixed Last Context frame (object or array) */ public function last() { return end($this->stack); } /** * Find a variable in the Context stack. * * Starting with the last Context frame (the context of the innermost section), and working back to the top-level * rendering context, look for a variable with the given name: * * * If the Context frame is an associative array which contains the key $id, returns the value of that element. * * If the Context frame is an object, this will check first for a public method, then a public property named * $id. Failing both of these, it will try `__isset` and `__get` magic methods. * * If a value named $id is not found in any Context frame, returns an empty string. * * @param string $id Variable name * * @return mixed Variable value, or '' if not found */ public function find($id) { return $this->findVariableInStack($id, $this->stack); } /** * Find a 'dot notation' variable in the Context stack. * * Note that dot notation traversal bubbles through scope differently than the regular find method. After finding * the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous * result. For example, given the following context stack: * * $data = array( * 'name' => 'Fred', * 'child' => array( * 'name' => 'Bob' * ), * ); * * ... and the Mustache following template: * * {{ child.name }} * * ... the `name` value is only searched for within the `child` value of the global Context, not within parent * Context frames. * * @param string $id Dotted variable selector * * @return mixed Variable value, or '' if not found */ public function findDot($id) { $chunks = explode('.', $id); $first = array_shift($chunks); $value = $this->findVariableInStack($first, $this->stack); foreach ($chunks as $chunk) { if ($value === '') { return $value; } $value = $this->findVariableInStack($chunk, array($value)); } return $value; } /** * Find an 'anchored dot notation' variable in the Context stack. * * This is the same as findDot(), except it looks in the top of the context * stack for the first value, rather than searching the whole context stack * and starting from there. * * @see Mustache_Context::findDot * * @throws Mustache_Exception_InvalidArgumentException if given an invalid anchored dot $id * * @param string $id Dotted variable selector * * @return mixed Variable value, or '' if not found */ public function findAnchoredDot($id) { $chunks = explode('.', $id); $first = array_shift($chunks); if ($first !== '') { throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected id for findAnchoredDot: %s', $id)); } $value = $this->last(); foreach ($chunks as $chunk) { if ($value === '') { return $value; } $value = $this->findVariableInStack($chunk, array($value)); } return $value; } /** * Find an argument in the block context stack. * * @param string $id * * @return mixed Variable value, or '' if not found */ public function findInBlock($id) { foreach ($this->blockStack as $context) { if (array_key_exists($id, $context)) { return $context[$id]; } } return ''; } /** * Helper function to find a variable in the Context stack. * * @see Mustache_Context::find * * @param string $id Variable name * @param array $stack Context stack * * @return mixed Variable value, or '' if not found */ private function findVariableInStack($id, array $stack) { for ($i = count($stack) - 1; $i >= 0; $i--) { $frame = &$stack[$i]; switch (gettype($frame)) { case 'object': if (!($frame instanceof Closure)) { // Note that is_callable() *will not work here* // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods if (method_exists($frame, $id)) { return $frame->$id(); } if (isset($frame->$id)) { return $frame->$id; } if ($frame instanceof ArrayAccess && isset($frame[$id])) { return $frame[$id]; } } break; case 'array': if (array_key_exists($id, $frame)) { return $frame[$id]; } break; } } return ''; } } loaders = array(); foreach ($loaders as $loader) { $this->addLoader($loader); } } /** * Add a Loader instance. * * @param Mustache_Loader $loader */ public function addLoader(Mustache_Loader $loader) { $this->loaders[] = $loader; } /** * Load a Template by name. * * @throws Mustache_Exception_UnknownTemplateException If a template file is not found * * @param string $name * * @return string Mustache Template source */ public function load($name) { foreach ($this->loaders as $loader) { try { return $loader->load($name); } catch (Mustache_Exception_UnknownTemplateException $e) { // do nothing, check the next loader. } } throw new Mustache_Exception_UnknownTemplateException($name); } } '{{ bar }}', * 'baz' => 'Hey {{ qux }}!' * ); * * $tpl = $loader->load('foo'); // '{{ bar }}' * * The ArrayLoader is used internally as a partials loader by Mustache_Engine instance when an array of partials * is set. It can also be used as a quick-and-dirty Template loader. */ class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader { private $templates; /** * ArrayLoader constructor. * * @param array $templates Associative array of Template source (default: array()) */ public function __construct(array $templates = array()) { $this->templates = $templates; } /** * Load a Template. * * @throws Mustache_Exception_UnknownTemplateException If a template file is not found * * @param string $name * * @return string Mustache Template source */ public function load($name) { if (!isset($this->templates[$name])) { throw new Mustache_Exception_UnknownTemplateException($name); } return $this->templates[$name]; } /** * Set an associative array of Template sources for this loader. * * @param array $templates */ public function setTemplates(array $templates) { $this->templates = $templates; } /** * Set a Template source by name. * * @param string $name * @param string $template Mustache Template source */ public function setTemplate($name, $template) { $this->templates[$name] = $template; } } '.ms', * 'stat_props' => array('size', 'mtime'), * ); * * Specifying 'stat_props' overrides the stat properties used to invalidate the template cache. By default, this * uses 'mtime' and 'size', but this can be set to any of the properties supported by stat(): * * http://php.net/manual/en/function.stat.php * * You can also disable filesystem stat entirely: * * $options = array('stat_props' => null); * * But with great power comes great responsibility. Namely, if you disable stat-based cache invalidation, * YOU MUST CLEAR THE TEMPLATE CACHE YOURSELF when your templates change. Make it part of your build or deploy * process so you don't forget! * * @throws Mustache_Exception_RuntimeException if $baseDir does not exist. * * @param string $baseDir Base directory containing Mustache template files. * @param array $options Array of Loader options (default: array()) */ public function __construct($baseDir, array $options = array()) { parent::__construct($baseDir, $options); if (array_key_exists('stat_props', $options)) { if (empty($options['stat_props'])) { $this->statProps = array(); } else { $this->statProps = $options['stat_props']; } } else { $this->statProps = array('size', 'mtime'); } } /** * Helper function for loading a Mustache file by name. * * @throws Mustache_Exception_UnknownTemplateException If a template file is not found. * * @param string $name * * @return Mustache_Source Mustache Template source */ protected function loadFile($name) { $fileName = $this->getFileName($name); if (!file_exists($fileName)) { throw new Mustache_Exception_UnknownTemplateException($name); } return new Mustache_Source_FilesystemSource($fileName, $this->statProps); } } load('{{ foo }}'); // '{{ foo }}' * * This is the default Template Loader instance used by Mustache: * * $m = new Mustache; * $tpl = $m->loadTemplate('{{ foo }}'); * echo $tpl->render(array('foo' => 'bar')); // "bar" */ class Mustache_Loader_StringLoader implements Mustache_Loader { /** * Load a Template by source. * * @param string $name Mustache Template source * * @return string Mustache Template source */ public function load($name) { return $name; } } load('hello'); * $goodbye = $loader->load('goodbye'); * * __halt_compiler(); * * @@ hello * Hello, {{ planet }}! * * @@ goodbye * Goodbye, cruel {{ planet }} * * Templates are deliniated by lines containing only `@@ name`. * * The InlineLoader is well-suited to micro-frameworks such as Silex: * * $app->register(new MustacheServiceProvider, array( * 'mustache.loader' => new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__) * )); * * $app->get('/{name}', function ($name) use ($app) { * return $app['mustache']->render('hello', compact('name')); * }) * ->value('name', 'world'); * * // ... * * __halt_compiler(); * * @@ hello * Hello, {{ name }}! */ class Mustache_Loader_InlineLoader implements Mustache_Loader { protected $fileName; protected $offset; protected $templates; /** * The InlineLoader requires a filename and offset to process templates. * * The magic constants `__FILE__` and `__COMPILER_HALT_OFFSET__` are usually * perfectly suited to the job: * * $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__); * * Note that this only works if the loader is instantiated inside the same * file as the inline templates. If the templates are located in another * file, it would be necessary to manually specify the filename and offset. * * @param string $fileName The file to parse for inline templates * @param int $offset A string offset for the start of the templates. * This usually coincides with the `__halt_compiler` * call, and the `__COMPILER_HALT_OFFSET__` */ public function __construct($fileName, $offset) { if (!is_file($fileName)) { throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid filename.'); } if (!is_int($offset) || $offset < 0) { throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid file offset.'); } $this->fileName = $fileName; $this->offset = $offset; } /** * Load a Template by name. * * @throws Mustache_Exception_UnknownTemplateException If a template file is not found * * @param string $name * * @return string Mustache Template source */ public function load($name) { $this->loadTemplates(); if (!array_key_exists($name, $this->templates)) { throw new Mustache_Exception_UnknownTemplateException($name); } return $this->templates[$name]; } /** * Parse and load templates from the end of a source file. */ protected function loadTemplates() { if ($this->templates === null) { $this->templates = array(); $data = file_get_contents($this->fileName, false, null, $this->offset); foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) { if (trim($chunk)) { list($name, $content) = explode("\n", $chunk, 2); $this->templates[trim($name)] = trim($content); } } } } } load('foo'); // equivalent to `file_get_contents(dirname(__FILE__).'/views/foo.mustache'); * * This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates: * * $m = new Mustache(array( * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'), * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'), * )); */ class Mustache_Loader_FilesystemLoader implements Mustache_Loader { private $baseDir; private $extension = '.mustache'; private $templates = array(); /** * Mustache filesystem Loader constructor. * * Passing an $options array allows overriding certain Loader options during instantiation: * * $options = array( * // The filename extension used for Mustache templates. Defaults to '.mustache' * 'extension' => '.ms', * ); * * @throws Mustache_Exception_RuntimeException if $baseDir does not exist * * @param string $baseDir Base directory containing Mustache template files * @param array $options Array of Loader options (default: array()) */ public function __construct($baseDir, array $options = array()) { $this->baseDir = $baseDir; if (strpos($this->baseDir, '://') === false) { $this->baseDir = realpath($this->baseDir); } if ($this->shouldCheckPath() && !is_dir($this->baseDir)) { throw new Mustache_Exception_RuntimeException(sprintf('FilesystemLoader baseDir must be a directory: %s', $baseDir)); } if (array_key_exists('extension', $options)) { if (empty($options['extension'])) { $this->extension = ''; } else { $this->extension = '.' . ltrim($options['extension'], '.'); } } } /** * Load a Template by name. * * $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'); * $loader->load('admin/dashboard'); // loads "./views/admin/dashboard.mustache"; * * @param string $name * * @return string Mustache Template source */ public function load($name) { if (!isset($this->templates[$name])) { $this->templates[$name] = $this->loadFile($name); } return $this->templates[$name]; } /** * Helper function for loading a Mustache file by name. * * @throws Mustache_Exception_UnknownTemplateException If a template file is not found * * @param string $name * * @return string Mustache Template source */ protected function loadFile($name) { $fileName = $this->getFileName($name); if ($this->shouldCheckPath() && !file_exists($fileName)) { throw new Mustache_Exception_UnknownTemplateException($name); } return file_get_contents($fileName); } /** * Helper function for getting a Mustache template file name. * * @param string $name * * @return string Template file name */ protected function getFileName($name) { $fileName = $this->baseDir . '/' . $name; if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) { $fileName .= $this->extension; } return $fileName; } /** * Only check if baseDir is a directory and requested templates are files if * baseDir is using the filesystem stream wrapper. * * @return bool Whether to check `is_dir` and `file_exists` */ protected function shouldCheckPath() { return strpos($this->baseDir, '://') === false || strpos($this->baseDir, 'file://') === 0; } } =}}`. (default: null) */ public function __construct(Mustache_Engine $mustache, Mustache_Context $context, $delims = null) { $this->mustache = $mustache; $this->context = $context; $this->delims = $delims; } /** * Render a string as a Mustache template with the current rendering context. * * @param string $string * * @return string Rendered template */ public function render($string) { return $this->mustache ->loadLambda((string) $string, $this->delims) ->renderInternal($this->context); } /** * Render a string as a Mustache template with the current rendering context. * * @param string $string * * @return string Rendered template */ public function __invoke($string) { return $this->render($string); } /** * Get a Lambda Helper with custom delimiters. * * @param string $delims Custom delimiters, in the format `{{= <% %> =}}` * * @return Mustache_LambdaHelper */ public function withDelimiters($delims) { return new self($this->mustache, $this->context, $delims); } } pragmas = $this->defaultPragmas; $this->sections = array(); $this->blocks = array(); $this->source = $source; $this->indentNextLine = true; $this->customEscape = $customEscape; $this->entityFlags = $entityFlags; $this->charset = $charset; $this->strictCallables = $strictCallables; return $this->writeCode($tree, $name); } /** * Enable pragmas across all templates, regardless of the presence of pragma * tags in the individual templates. * * @internal Users should set global pragmas in Mustache_Engine, not here :) * * @param string[] $pragmas */ public function setPragmas(array $pragmas) { $this->pragmas = array(); foreach ($pragmas as $pragma) { $this->pragmas[$pragma] = true; } $this->defaultPragmas = $this->pragmas; } /** * Helper function for walking the Mustache token parse tree. * * @throws Mustache_Exception_SyntaxException upon encountering unknown token types * * @param array $tree Parse tree of Mustache tokens * @param int $level (default: 0) * * @return string Generated PHP source code */ private function walk(array $tree, $level = 0) { $code = ''; $level++; foreach ($tree as $node) { switch ($node[Mustache_Tokenizer::TYPE]) { case Mustache_Tokenizer::T_PRAGMA: $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true; break; case Mustache_Tokenizer::T_SECTION: $code .= $this->section( $node[Mustache_Tokenizer::NODES], $node[Mustache_Tokenizer::NAME], isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), $node[Mustache_Tokenizer::INDEX], $node[Mustache_Tokenizer::END], $node[Mustache_Tokenizer::OTAG], $node[Mustache_Tokenizer::CTAG], $level ); break; case Mustache_Tokenizer::T_INVERTED: $code .= $this->invertedSection( $node[Mustache_Tokenizer::NODES], $node[Mustache_Tokenizer::NAME], isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), $level ); break; case Mustache_Tokenizer::T_PARTIAL: $code .= $this->partial( $node[Mustache_Tokenizer::NAME], isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', $level ); break; case Mustache_Tokenizer::T_PARENT: $code .= $this->parent( $node[Mustache_Tokenizer::NAME], isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', $node[Mustache_Tokenizer::NODES], $level ); break; case Mustache_Tokenizer::T_BLOCK_ARG: $code .= $this->blockArg( $node[Mustache_Tokenizer::NODES], $node[Mustache_Tokenizer::NAME], $node[Mustache_Tokenizer::INDEX], $node[Mustache_Tokenizer::END], $node[Mustache_Tokenizer::OTAG], $node[Mustache_Tokenizer::CTAG], $level ); break; case Mustache_Tokenizer::T_BLOCK_VAR: $code .= $this->blockVar( $node[Mustache_Tokenizer::NODES], $node[Mustache_Tokenizer::NAME], $node[Mustache_Tokenizer::INDEX], $node[Mustache_Tokenizer::END], $node[Mustache_Tokenizer::OTAG], $node[Mustache_Tokenizer::CTAG], $level ); break; case Mustache_Tokenizer::T_COMMENT: break; case Mustache_Tokenizer::T_ESCAPED: case Mustache_Tokenizer::T_UNESCAPED: case Mustache_Tokenizer::T_UNESCAPED_2: $code .= $this->variable( $node[Mustache_Tokenizer::NAME], isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED, $level ); break; case Mustache_Tokenizer::T_TEXT: $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level); break; default: throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node); } } return $code; } const KLASS = 'lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context); $buffer = \'\'; %s return $buffer; } %s %s }'; const KLASS_NO_LAMBDAS = 'walk($tree); $sections = implode("\n", $this->sections); $blocks = implode("\n", $this->blocks); $klass = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS; $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : ''; return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks); } const BLOCK_VAR = ' $blockFunction = $context->findInBlock(%s); if (is_callable($blockFunction)) { $buffer .= call_user_func($blockFunction, $context); %s} '; const BLOCK_VAR_ELSE = '} else {%s'; /** * Generate Mustache Template inheritance block variable PHP source. * * @param array $nodes Array of child tokens * @param string $id Section name * @param int $start Section start offset * @param int $end Section end offset * @param string $otag Current Mustache opening tag * @param string $ctag Current Mustache closing tag * @param int $level * * @return string Generated PHP source code */ private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level) { $id = var_export($id, true); $else = $this->walk($nodes, $level); if ($else !== '') { $else = sprintf($this->prepare(self::BLOCK_VAR_ELSE, $level + 1, false, true), $else); } return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $else); } const BLOCK_ARG = '%s => array($this, \'block%s\'),'; /** * Generate Mustache Template inheritance block argument PHP source. * * @param array $nodes Array of child tokens * @param string $id Section name * @param int $start Section start offset * @param int $end Section end offset * @param string $otag Current Mustache opening tag * @param string $ctag Current Mustache closing tag * @param int $level * * @return string Generated PHP source code */ private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level) { $key = $this->block($nodes); $keystr = var_export($key, true); $id = var_export($id, true); return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key); } const BLOCK_FUNCTION = ' public function block%s($context) { $indent = $buffer = \'\';%s return $buffer; } '; /** * Generate Mustache Template inheritance block function PHP source. * * @param array $nodes Array of child tokens * * @return string key of new block function */ private function block($nodes) { $code = $this->walk($nodes, 0); $key = ucfirst(md5($code)); if (!isset($this->blocks[$key])) { $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code); } return $key; } const SECTION_CALL = ' // %s section $value = $context->%s(%s);%s $buffer .= $this->section%s($context, $indent, $value); '; const SECTION = ' private function section%s(Mustache_Context $context, $indent, $value) { $buffer = \'\'; if (%s) { $source = %s; $result = call_user_func($value, $source, %s); if (strpos($result, \'{{\') === false) { $buffer .= $result; } else { $buffer .= $this->mustache ->loadLambda((string) $result%s) ->renderInternal($context); } } elseif (!empty($value)) { $values = $this->isIterable($value) ? $value : array($value); foreach ($values as $value) { $context->push($value); %s $context->pop(); } } return $buffer; } '; /** * Generate Mustache Template section PHP source. * * @param array $nodes Array of child tokens * @param string $id Section name * @param string[] $filters Array of filters * @param int $start Section start offset * @param int $end Section end offset * @param string $otag Current Mustache opening tag * @param string $ctag Current Mustache closing tag * @param int $level * * @return string Generated section PHP source code */ private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level) { $source = var_export(substr($this->source, $start, $end - $start), true); $callable = $this->getCallable(); if ($otag !== '{{' || $ctag !== '}}') { $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true); $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag); $delims = ', ' . $delimTag; } else { $helper = '$this->lambdaHelper'; $delims = ''; } $key = ucfirst(md5($delims . "\n" . $source)); if (!isset($this->sections[$key])) { $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2)); } $method = $this->getFindMethod($id); $id = var_export($id, true); $filters = $this->getFilters($filters, $level); return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key); } const INVERTED_SECTION = ' // %s inverted section $value = $context->%s(%s);%s if (empty($value)) { %s } '; /** * Generate Mustache Template inverted section PHP source. * * @param array $nodes Array of child tokens * @param string $id Section name * @param string[] $filters Array of filters * @param int $level * * @return string Generated inverted section PHP source code */ private function invertedSection($nodes, $id, $filters, $level) { $method = $this->getFindMethod($id); $id = var_export($id, true); $filters = $this->getFilters($filters, $level); return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level)); } const PARTIAL_INDENT = ', $indent . %s'; const PARTIAL = ' if ($partial = $this->mustache->loadPartial(%s)) { $buffer .= $partial->renderInternal($context%s); } '; /** * Generate Mustache Template partial call PHP source. * * @param string $id Partial name * @param string $indent Whitespace indent to apply to partial * @param int $level * * @return string Generated partial call PHP source code */ private function partial($id, $indent, $level) { if ($indent !== '') { $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true)); } else { $indentParam = ''; } return sprintf( $this->prepare(self::PARTIAL, $level), var_export($id, true), $indentParam ); } const PARENT = ' if ($parent = $this->mustache->loadPartial(%s)) { $context->pushBlockContext(array(%s )); $buffer .= $parent->renderInternal($context, $indent); $context->popBlockContext(); } '; const PARENT_NO_CONTEXT = ' if ($parent = $this->mustache->loadPartial(%s)) { $buffer .= $parent->renderInternal($context, $indent); } '; /** * Generate Mustache Template inheritance parent call PHP source. * * @param string $id Parent tag name * @param string $indent Whitespace indent to apply to parent * @param array $children Child nodes * @param int $level * * @return string Generated PHP source code */ private function parent($id, $indent, array $children, $level) { $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs')); if (empty($realChildren)) { return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), var_export($id, true)); } return sprintf( $this->prepare(self::PARENT, $level), var_export($id, true), $this->walk($realChildren, $level + 1) ); } /** * Helper method for filtering out non-block-arg tokens. * * @param array $node * * @return bool True if $node is a block arg token */ private static function onlyBlockArgs(array $node) { return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG; } const VARIABLE = ' $value = $this->resolveValue($context->%s(%s), $context);%s $buffer .= %s%s; '; /** * Generate Mustache Template variable interpolation PHP source. * * @param string $id Variable name * @param string[] $filters Array of filters * @param bool $escape Escape the variable value for output? * @param int $level * * @return string Generated variable interpolation PHP source */ private function variable($id, $filters, $escape, $level) { $method = $this->getFindMethod($id); $id = ($method !== 'last') ? var_export($id, true) : ''; $filters = $this->getFilters($filters, $level); $value = $escape ? $this->getEscape() : '$value'; return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value); } const FILTER = ' $filter = $context->%s(%s); if (!(%s)) { throw new Mustache_Exception_UnknownFilterException(%s); } $value = call_user_func($filter, $value);%s '; /** * Generate Mustache Template variable filtering PHP source. * * @param string[] $filters Array of filters * @param int $level * * @return string Generated filter PHP source */ private function getFilters(array $filters, $level) { if (empty($filters)) { return ''; } $name = array_shift($filters); $method = $this->getFindMethod($name); $filter = ($method !== 'last') ? var_export($name, true) : ''; $callable = $this->getCallable('$filter'); $msg = var_export($name, true); return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level)); } const LINE = '$buffer .= "\n";'; const TEXT = '$buffer .= %s%s;'; /** * Generate Mustache Template output Buffer call PHP source. * * @param string $text * @param int $level * * @return string Generated output Buffer call PHP source */ private function text($text, $level) { $indentNextLine = (substr($text, -1) === "\n"); $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true)); $this->indentNextLine = $indentNextLine; return $code; } /** * Prepare PHP source code snippet for output. * * @param string $text * @param int $bonus Additional indent level (default: 0) * @param bool $prependNewline Prepend a newline to the snippet? (default: true) * @param bool $appendNewline Append a newline to the snippet? (default: false) * * @return string PHP source code snippet */ private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false) { $text = ($prependNewline ? "\n" : '') . trim($text); if ($prependNewline) { $bonus++; } if ($appendNewline) { $text .= "\n"; } return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text); } const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)'; const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)'; /** * Get the current escaper. * * @param string $value (default: '$value') * * @return string Either a custom callback, or an inline call to `htmlspecialchars` */ private function getEscape($value = '$value') { if ($this->customEscape) { return sprintf(self::CUSTOM_ESCAPE, $value); } return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true)); } /** * Select the appropriate Context `find` method for a given $id. * * The return value will be one of `find`, `findDot`, `findAnchoredDot` or `last`. * * @see Mustache_Context::find * @see Mustache_Context::findDot * @see Mustache_Context::last * * @param string $id Variable name * * @return string `find` method name */ private function getFindMethod($id) { if ($id === '.') { return 'last'; } if (isset($this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) { if (substr($id, 0, 1) === '.') { return 'findAnchoredDot'; } } if (strpos($id, '.') === false) { return 'find'; } return 'findDot'; } const IS_CALLABLE = '!is_string(%s) && is_callable(%s)'; const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)'; /** * Helper function to compile strict vs lax "is callable" logic. * * @param string $variable (default: '$value') * * @return string "is callable" logic */ private function getCallable($variable = '$value') { $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE; return sprintf($tpl, $variable, $variable); } const LINE_INDENT = '$indent . '; /** * Get the current $indent prefix to write to the buffer. * * @return string "$indent . " or "" */ private function flushIndent() { if (!$this->indentNextLine) { return ''; } $this->indentNextLine = false; return self::LINE_INDENT; } } '; const T_PARENT = '<'; const T_DELIM_CHANGE = '='; const T_ESCAPED = '_v'; const T_UNESCAPED = '{'; const T_UNESCAPED_2 = '&'; const T_TEXT = '_t'; const T_PRAGMA = '%'; const T_BLOCK_VAR = '$'; const T_BLOCK_ARG = '$arg'; // Valid token types private static $tagTypes = array( self::T_SECTION => true, self::T_INVERTED => true, self::T_END_SECTION => true, self::T_COMMENT => true, self::T_PARTIAL => true, self::T_PARENT => true, self::T_DELIM_CHANGE => true, self::T_ESCAPED => true, self::T_UNESCAPED => true, self::T_UNESCAPED_2 => true, self::T_PRAGMA => true, self::T_BLOCK_VAR => true, ); // Token properties const TYPE = 'type'; const NAME = 'name'; const OTAG = 'otag'; const CTAG = 'ctag'; const LINE = 'line'; const INDEX = 'index'; const END = 'end'; const INDENT = 'indent'; const NODES = 'nodes'; const VALUE = 'value'; const FILTERS = 'filters'; private $state; private $tagType; private $buffer; private $tokens; private $seenTag; private $line; private $otag; private $ctag; private $otagLen; private $ctagLen; /** * Scan and tokenize template source. * * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered * * @param string $text Mustache template source to tokenize * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: null) * * @return array Set of Mustache tokens */ public function scan($text, $delimiters = null) { // Setting mbstring.func_overload makes things *really* slow. // Let's do everyone a favor and scan this string as ASCII instead. // // @codeCoverageIgnoreStart $encoding = null; if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) { $encoding = mb_internal_encoding(); mb_internal_encoding('ASCII'); } // @codeCoverageIgnoreEnd $this->reset(); if ($delimiters = trim($delimiters)) { $this->setDelimiters($delimiters); } $len = strlen($text); for ($i = 0; $i < $len; $i++) { switch ($this->state) { case self::IN_TEXT: if ($this->tagChange($this->otag, $this->otagLen, $text, $i)) { $i--; $this->flushBuffer(); $this->state = self::IN_TAG_TYPE; } else { $char = $text[$i]; $this->buffer .= $char; if ($char === "\n") { $this->flushBuffer(); $this->line++; } } break; case self::IN_TAG_TYPE: $i += $this->otagLen - 1; $char = $text[$i + 1]; if (isset(self::$tagTypes[$char])) { $tag = $char; $this->tagType = $tag; } else { $tag = null; $this->tagType = self::T_ESCAPED; } if ($this->tagType === self::T_DELIM_CHANGE) { $i = $this->changeDelimiters($text, $i); $this->state = self::IN_TEXT; } elseif ($this->tagType === self::T_PRAGMA) { $i = $this->addPragma($text, $i); $this->state = self::IN_TEXT; } else { if ($tag !== null) { $i++; } $this->state = self::IN_TAG; } $this->seenTag = $i; break; default: if ($this->tagChange($this->ctag, $this->ctagLen, $text, $i)) { $token = array( self::TYPE => $this->tagType, self::NAME => trim($this->buffer), self::OTAG => $this->otag, self::CTAG => $this->ctag, self::LINE => $this->line, self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen, ); if ($this->tagType === self::T_UNESCAPED) { // Clean up `{{{ tripleStache }}}` style tokens. if ($this->ctag === '}}') { if (($i + 2 < $len) && $text[$i + 2] === '}') { $i++; } else { $msg = sprintf( 'Mismatched tag delimiters: %s on line %d', $token[self::NAME], $token[self::LINE] ); throw new Mustache_Exception_SyntaxException($msg, $token); } } else { $lastName = $token[self::NAME]; if (substr($lastName, -1) === '}') { $token[self::NAME] = trim(substr($lastName, 0, -1)); } else { $msg = sprintf( 'Mismatched tag delimiters: %s on line %d', $token[self::NAME], $token[self::LINE] ); throw new Mustache_Exception_SyntaxException($msg, $token); } } } $this->buffer = ''; $i += $this->ctagLen - 1; $this->state = self::IN_TEXT; $this->tokens[] = $token; } else { $this->buffer .= $text[$i]; } break; } } $this->flushBuffer(); // Restore the user's encoding... // @codeCoverageIgnoreStart if ($encoding) { mb_internal_encoding($encoding); } // @codeCoverageIgnoreEnd return $this->tokens; } /** * Helper function to reset tokenizer internal state. */ private function reset() { $this->state = self::IN_TEXT; $this->tagType = null; $this->buffer = ''; $this->tokens = array(); $this->seenTag = false; $this->line = 0; $this->otag = '{{'; $this->ctag = '}}'; $this->otagLen = 2; $this->ctagLen = 2; } /** * Flush the current buffer to a token. */ private function flushBuffer() { if (strlen($this->buffer) > 0) { $this->tokens[] = array( self::TYPE => self::T_TEXT, self::LINE => $this->line, self::VALUE => $this->buffer, ); $this->buffer = ''; } } /** * Change the current Mustache delimiters. Set new `otag` and `ctag` values. * * @param string $text Mustache template source * @param int $index Current tokenizer index * * @return int New index value */ private function changeDelimiters($text, $index) { $startIndex = strpos($text, '=', $index) + 1; $close = '=' . $this->ctag; $closeIndex = strpos($text, $close, $index); $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex))); $this->tokens[] = array( self::TYPE => self::T_DELIM_CHANGE, self::LINE => $this->line, ); return $closeIndex + strlen($close) - 1; } /** * Set the current Mustache `otag` and `ctag` delimiters. * * @param string $delimiters */ private function setDelimiters($delimiters) { list($otag, $ctag) = explode(' ', $delimiters); $this->otag = $otag; $this->ctag = $ctag; $this->otagLen = strlen($otag); $this->ctagLen = strlen($ctag); } /** * Add pragma token. * * Pragmas are hoisted to the front of the template, so all pragma tokens * will appear at the front of the token list. * * @param string $text * @param int $index * * @return int New index value */ private function addPragma($text, $index) { $end = strpos($text, $this->ctag, $index); $pragma = trim(substr($text, $index + 2, $end - $index - 2)); // Pragmas are hoisted to the front of the template. array_unshift($this->tokens, array( self::TYPE => self::T_PRAGMA, self::NAME => $pragma, self::LINE => 0, )); return $end + $this->ctagLen - 1; } /** * Test whether it's time to change tags. * * @param string $tag Current tag name * @param int $tagLen Current tag name length * @param string $text Mustache template source * @param int $index Current tokenizer index * * @return bool True if this is a closing section tag */ private function tagChange($tag, $tagLen, $text, $index) { return substr($text, $index, $tagLen) === $tag; } } fileName = $fileName; $this->statProps = $statProps; } /** * Get the Source key (used to generate the compiled class name). * * @throws RuntimeException when a source file cannot be read * * @return string */ public function getKey() { $chunks = array( 'fileName' => $this->fileName, ); if (!empty($this->statProps)) { if (!isset($this->stat)) { $this->stat = @stat($this->fileName); } if ($this->stat === false) { throw new RuntimeException(sprintf('Failed to read source file "%s".', $this->fileName)); } foreach ($this->statProps as $prop) { $chunks[$prop] = $this->stat[$prop]; } } return json_encode($chunks); } /** * Get the template Source. * * @return string */ public function getSource() { return file_get_contents($this->fileName); } } lineNum = -1; $this->lineTokens = 0; $this->pragmas = $this->defaultPragmas; $this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]); $this->pragmaBlocks = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]); return $this->buildTree($tokens); } /** * Enable pragmas across all templates, regardless of the presence of pragma * tags in the individual templates. * * @internal Users should set global pragmas in Mustache_Engine, not here :) * * @param string[] $pragmas */ public function setPragmas(array $pragmas) { $this->pragmas = array(); foreach ($pragmas as $pragma) { $this->enablePragma($pragma); } $this->defaultPragmas = $this->pragmas; } /** * Helper method for recursively building a parse tree. * * @throws Mustache_Exception_SyntaxException when nesting errors or mismatched section tags are encountered * * @param array &$tokens Set of Mustache tokens * @param array $parent Parent token (default: null) * * @return array Mustache Token parse tree */ private function buildTree(array &$tokens, array $parent = null) { $nodes = array(); while (!empty($tokens)) { $token = array_shift($tokens); if ($token[Mustache_Tokenizer::LINE] === $this->lineNum) { $this->lineTokens++; } else { $this->lineNum = $token[Mustache_Tokenizer::LINE]; $this->lineTokens = 0; } if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) { list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]); if (!empty($filters)) { $token[Mustache_Tokenizer::NAME] = $name; $token[Mustache_Tokenizer::FILTERS] = $filters; } } switch ($token[Mustache_Tokenizer::TYPE]) { case Mustache_Tokenizer::T_DELIM_CHANGE: $this->checkIfTokenIsAllowedInParent($parent, $token); $this->clearStandaloneLines($nodes, $tokens); break; case Mustache_Tokenizer::T_SECTION: case Mustache_Tokenizer::T_INVERTED: $this->checkIfTokenIsAllowedInParent($parent, $token); $this->clearStandaloneLines($nodes, $tokens); $nodes[] = $this->buildTree($tokens, $token); break; case Mustache_Tokenizer::T_END_SECTION: if (!isset($parent)) { $msg = sprintf( 'Unexpected closing tag: /%s on line %d', $token[Mustache_Tokenizer::NAME], $token[Mustache_Tokenizer::LINE] ); throw new Mustache_Exception_SyntaxException($msg, $token); } if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) { $msg = sprintf( 'Nesting error: %s (on line %d) vs. %s (on line %d)', $parent[Mustache_Tokenizer::NAME], $parent[Mustache_Tokenizer::LINE], $token[Mustache_Tokenizer::NAME], $token[Mustache_Tokenizer::LINE] ); throw new Mustache_Exception_SyntaxException($msg, $token); } $this->clearStandaloneLines($nodes, $tokens); $parent[Mustache_Tokenizer::END] = $token[Mustache_Tokenizer::INDEX]; $parent[Mustache_Tokenizer::NODES] = $nodes; return $parent; case Mustache_Tokenizer::T_PARTIAL: $this->checkIfTokenIsAllowedInParent($parent, $token); //store the whitespace prefix for laters! if ($indent = $this->clearStandaloneLines($nodes, $tokens)) { $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE]; } $nodes[] = $token; break; case Mustache_Tokenizer::T_PARENT: $this->checkIfTokenIsAllowedInParent($parent, $token); $nodes[] = $this->buildTree($tokens, $token); break; case Mustache_Tokenizer::T_BLOCK_VAR: if ($this->pragmaBlocks) { // BLOCKS pragma is enabled, let's do this! if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) { $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG; } $this->clearStandaloneLines($nodes, $tokens); $nodes[] = $this->buildTree($tokens, $token); } else { // pretend this was just a normal "escaped" token... $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED; // TODO: figure out how to figure out if there was a space after this dollar: $token[Mustache_Tokenizer::NAME] = '$' . $token[Mustache_Tokenizer::NAME]; $nodes[] = $token; } break; case Mustache_Tokenizer::T_PRAGMA: $this->enablePragma($token[Mustache_Tokenizer::NAME]); // no break case Mustache_Tokenizer::T_COMMENT: $this->clearStandaloneLines($nodes, $tokens); $nodes[] = $token; break; default: $nodes[] = $token; break; } } if (isset($parent)) { $msg = sprintf( 'Missing closing tag: %s opened on line %d', $parent[Mustache_Tokenizer::NAME], $parent[Mustache_Tokenizer::LINE] ); throw new Mustache_Exception_SyntaxException($msg, $parent); } return $nodes; } /** * Clear standalone line tokens. * * Returns a whitespace token for indenting partials, if applicable. * * @param array $nodes Parsed nodes * @param array $tokens Tokens to be parsed * * @return array|null Resulting indent token, if any */ private function clearStandaloneLines(array &$nodes, array &$tokens) { if ($this->lineTokens > 1) { // this is the third or later node on this line, so it can't be standalone return; } $prev = null; if ($this->lineTokens === 1) { // this is the second node on this line, so it can't be standalone // unless the previous node is whitespace. if ($prev = end($nodes)) { if (!$this->tokenIsWhitespace($prev)) { return; } } } if ($next = reset($tokens)) { // If we're on a new line, bail. if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) { return; } // If the next token isn't whitespace, bail. if (!$this->tokenIsWhitespace($next)) { return; } if (count($tokens) !== 1) { // Unless it's the last token in the template, the next token // must end in newline for this to be standalone. if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") { return; } } // Discard the whitespace suffix array_shift($tokens); } if ($prev) { // Return the whitespace prefix, if any return array_pop($nodes); } } /** * Check whether token is a whitespace token. * * True if token type is T_TEXT and value is all whitespace characters. * * @param array $token * * @return bool True if token is a whitespace token */ private function tokenIsWhitespace(array $token) { if ($token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) { return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]); } return false; } /** * Check whether a token is allowed inside a parent tag. * * @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag * * @param array|null $parent * @param array $token */ private function checkIfTokenIsAllowedInParent($parent, array $token) { if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) { throw new Mustache_Exception_SyntaxException('Illegal content in < parent tag', $token); } } /** * Split a tag name into name and filters. * * @param string $name * * @return array [Tag name, Array of filters] */ private function getNameAndFilters($name) { $filters = array_map('trim', explode('|', $name)); $name = array_shift($filters); return array($name, $filters); } /** * Enable a pragma. * * @param string $name */ private function enablePragma($name) { $this->pragmas[$name] = true; switch ($name) { case Mustache_Engine::PRAGMA_BLOCKS: $this->pragmaBlocks = true; break; case Mustache_Engine::PRAGMA_FILTERS: $this->pragmaFilters = true; break; } } } 100, self::INFO => 200, self::NOTICE => 250, self::WARNING => 300, self::ERROR => 400, self::CRITICAL => 500, self::ALERT => 550, self::EMERGENCY => 600, ); protected $level; protected $stream = null; protected $url = null; /** * @throws InvalidArgumentException if the logging level is unknown * * @param resource|string $stream Resource instance or URL * @param int $level The minimum logging level at which this handler will be triggered */ public function __construct($stream, $level = Mustache_Logger::ERROR) { $this->setLevel($level); if (is_resource($stream)) { $this->stream = $stream; } else { $this->url = $stream; } } /** * Close stream resources. */ public function __destruct() { if (is_resource($this->stream)) { fclose($this->stream); } } /** * Set the minimum logging level. * * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown * * @param int $level The minimum logging level which will be written */ public function setLevel($level) { if (!array_key_exists($level, self::$levels)) { throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level)); } $this->level = $level; } /** * Get the current minimum logging level. * * @return int */ public function getLevel() { return $this->level; } /** * Logs with an arbitrary level. * * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown * * @param mixed $level * @param string $message * @param array $context */ public function log($level, $message, array $context = array()) { if (!array_key_exists($level, self::$levels)) { throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level)); } if (self::$levels[$level] >= self::$levels[$this->level]) { $this->writeLog($level, $message, $context); } } /** * Write a record to the log. * * @throws Mustache_Exception_LogicException If neither a stream resource nor url is present * @throws Mustache_Exception_RuntimeException If the stream url cannot be opened * * @param int $level The logging level * @param string $message The log message * @param array $context The log context */ protected function writeLog($level, $message, array $context = array()) { if (!is_resource($this->stream)) { if (!isset($this->url)) { throw new Mustache_Exception_LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); } $this->stream = fopen($this->url, 'a'); if (!is_resource($this->stream)) { // @codeCoverageIgnoreStart throw new Mustache_Exception_RuntimeException(sprintf('The stream or file "%s" could not be opened.', $this->url)); // @codeCoverageIgnoreEnd } } fwrite($this->stream, self::formatLine($level, $message, $context)); } /** * Gets the name of the logging level. * * @throws InvalidArgumentException if the logging level is unknown * * @param int $level * * @return string */ protected static function getLevelName($level) { return strtoupper($level); } /** * Format a log line for output. * * @param int $level The logging level * @param string $message The log message * @param array $context The log context * * @return string */ protected static function formatLine($level, $message, array $context = array()) { return sprintf( "%s: %s\n", self::getLevelName($level), self::interpolateMessage($message, $context) ); } /** * Interpolate context values into the message placeholders. * * @param string $message * @param array $context * * @return string */ protected static function interpolateMessage($message, array $context = array()) { if (strpos($message, '{') === false) { return $message; } // build a replacement array with braces around the context keys $replace = array(); foreach ($context as $key => $val) { $replace['{' . $key . '}'] = $val; } // interpolate replacement values into the the message and return return strtr($message, $replace); } } log(Mustache_Logger::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context */ public function alert($message, array $context = array()) { $this->log(Mustache_Logger::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context */ public function critical($message, array $context = array()) { $this->log(Mustache_Logger::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context */ public function error($message, array $context = array()) { $this->log(Mustache_Logger::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context */ public function warning($message, array $context = array()) { $this->log(Mustache_Logger::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param array $context */ public function notice($message, array $context = array()) { $this->log(Mustache_Logger::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context */ public function info($message, array $context = array()) { $this->log(Mustache_Logger::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param array $context */ public function debug($message, array $context = array()) { $this->log(Mustache_Logger::DEBUG, $message, $context); } } mustache = $mustache; } /** * Mustache Template instances can be treated as a function and rendered by simply calling them. * * $m = new Mustache_Engine; * $tpl = $m->loadTemplate('Hello, {{ name }}!'); * echo $tpl(array('name' => 'World')); // "Hello, World!" * * @see Mustache_Template::render * * @param mixed $context Array or object rendering context (default: array()) * * @return string Rendered template */ public function __invoke($context = array()) { return $this->render($context); } /** * Render this template given the rendering context. * * @param mixed $context Array or object rendering context (default: array()) * * @return string Rendered template */ public function render($context = array()) { return $this->renderInternal( $this->prepareContextStack($context) ); } /** * Internal rendering method implemented by Mustache Template concrete subclasses. * * This is where the magic happens :) * * NOTE: This method is not part of the Mustache.php public API. * * @param Mustache_Context $context * @param string $indent (default: '') * * @return string Rendered template */ abstract public function renderInternal(Mustache_Context $context, $indent = ''); /** * Tests whether a value should be iterated over (e.g. in a section context). * * In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists * should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript, * Java, Python, etc. * * PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish * between between a list of things (numeric, normalized array) and a set of variables to be used as section context * (associative array). In other words, this will be iterated over: * * $items = array( * array('name' => 'foo'), * array('name' => 'bar'), * array('name' => 'baz'), * ); * * ... but this will be used as a section context block: * * $items = array( * 1 => array('name' => 'foo'), * 'banana' => array('name' => 'bar'), * 42 => array('name' => 'baz'), * ); * * @param mixed $value * * @return bool True if the value is 'iterable' */ protected function isIterable($value) { switch (gettype($value)) { case 'object': return $value instanceof Traversable; case 'array': $i = 0; foreach ($value as $k => $v) { if ($k !== $i++) { return false; } } return true; default: return false; } } /** * Helper method to prepare the Context stack. * * Adds the Mustache HelperCollection to the stack's top context frame if helpers are present. * * @param mixed $context Optional first context frame (default: null) * * @return Mustache_Context */ protected function prepareContextStack($context = null) { $stack = new Mustache_Context(); $helpers = $this->mustache->getHelpers(); if (!$helpers->isEmpty()) { $stack->push($helpers); } if (!empty($context)) { $stack->push($context); } return $stack; } /** * Resolve a context value. * * Invoke the value if it is callable, otherwise return the value. * * @param mixed $value * @param Mustache_Context $context * * @return string */ protected function resolveValue($value, Mustache_Context $context) { if (($this->strictCallables ? is_object($value) : !is_string($value)) && is_callable($value)) { return $this->mustache ->loadLambda((string) call_user_func($value)) ->renderInternal($context); } return $value; } } helperName = $helperName; $message = sprintf('Unknown helper: %s', $helperName); if (version_compare(PHP_VERSION, '5.3.0', '>=')) { parent::__construct($message, 0, $previous); } else { parent::__construct($message); // @codeCoverageIgnore } } public function getHelperName() { return $this->helperName; } } filterName = $filterName; $message = sprintf('Unknown filter: %s', $filterName); if (version_compare(PHP_VERSION, '5.3.0', '>=')) { parent::__construct($message, 0, $previous); } else { parent::__construct($message); // @codeCoverageIgnore } } public function getFilterName() { return $this->filterName; } } templateName = $templateName; $message = sprintf('Unknown template: %s', $templateName); if (version_compare(PHP_VERSION, '5.3.0', '>=')) { parent::__construct($message, 0, $previous); } else { parent::__construct($message); // @codeCoverageIgnore } } public function getTemplateName() { return $this->templateName; } } token = $token; if (version_compare(PHP_VERSION, '5.3.0', '>=')) { parent::__construct($msg, 0, $previous); } else { parent::__construct($msg); // @codeCoverageIgnore } } /** * @return array */ public function getToken() { return $this->token; } } useragent = 'X';` * * @var array */ public $options = array(); /** * Create a new session * * @param string|null $url Base URL for requests * @param array $headers Default headers for requests * @param array $data Default data for requests * @param array $options Default options for requests */ public function __construct($url = null, $headers = array(), $data = array(), $options = array()) { $this->url = $url; $this->headers = $headers; $this->data = $data; $this->options = $options; if (empty($this->options['cookies'])) { $this->options['cookies'] = new Requests_Cookie_Jar(); } } /** * Get a property's value * * @param string $key Property key * @return mixed|null Property value, null if none found */ public function __get($key) { if (isset($this->options[$key])) { return $this->options[$key]; } return null; } /** * Set a property's value * * @param string $key Property key * @param mixed $value Property value */ public function __set($key, $value) { $this->options[$key] = $value; } /** * Remove a property's value * * @param string $key Property key */ public function __isset($key) { return isset($this->options[$key]); } /** * Remove a property's value * * @param string $key Property key */ public function __unset($key) { if (isset($this->options[$key])) { unset($this->options[$key]); } } /**#@+ * @see request() * @param string $url * @param array $headers * @param array $options * @return Requests_Response */ /** * Send a GET request */ public function get($url, $headers = array(), $options = array()) { return $this->request($url, $headers, null, Requests::GET, $options); } /** * Send a HEAD request */ public function head($url, $headers = array(), $options = array()) { return $this->request($url, $headers, null, Requests::HEAD, $options); } /** * Send a DELETE request */ public function delete($url, $headers = array(), $options = array()) { return $this->request($url, $headers, null, Requests::DELETE, $options); } /**#@-*/ /**#@+ * @see request() * @param string $url * @param array $headers * @param array $data * @param array $options * @return Requests_Response */ /** * Send a POST request */ public function post($url, $headers = array(), $data = array(), $options = array()) { return $this->request($url, $headers, $data, Requests::POST, $options); } /** * Send a PUT request */ public function put($url, $headers = array(), $data = array(), $options = array()) { return $this->request($url, $headers, $data, Requests::PUT, $options); } /** * Send a PATCH request * * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the * specification recommends that should send an ETag * * @link https://tools.ietf.org/html/rfc5789 */ public function patch($url, $headers, $data = array(), $options = array()) { return $this->request($url, $headers, $data, Requests::PATCH, $options); } /**#@-*/ /** * Main interface for HTTP requests * * This method initiates a request and sends it via a transport before * parsing. * * @see Requests::request() * * @throws Requests_Exception On invalid URLs (`nonhttp`) * * @param string $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type (use Requests constants) * @param array $options Options for the request (see {@see Requests::request}) * @return Requests_Response */ public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) { $request = $this->merge_request(compact('url', 'headers', 'data', 'options')); return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); } /** * Send multiple HTTP requests simultaneously * * @see Requests::request_multiple() * * @param array $requests Requests data (see {@see Requests::request_multiple}) * @param array $options Global and default options (see {@see Requests::request}) * @return array Responses (either Requests_Response or a Requests_Exception object) */ public function request_multiple($requests, $options = array()) { foreach ($requests as $key => $request) { $requests[$key] = $this->merge_request($request, false); } $options = array_merge($this->options, $options); // Disallow forcing the type, as that's a per request setting unset($options['type']); return Requests::request_multiple($requests, $options); } /** * Merge a request's data with the default data * * @param array $request Request data (same form as {@see request_multiple}) * @param boolean $merge_options Should we merge options as well? * @return array Request data */ protected function merge_request($request, $merge_options = true) { if ($this->url !== null) { $request['url'] = Requests_IRI::absolutize($this->url, $request['url']); $request['url'] = $request['url']->uri; } if (empty($request['headers'])) { $request['headers'] = array(); } $request['headers'] = array_merge($this->headers, $request['headers']); if (empty($request['data'])) { if (is_array($this->data)) { $request['data'] = $this->data; } } elseif (is_array($request['data']) && is_array($this->data)) { $request['data'] = array_merge($this->data, $request['data']); } if ($merge_options !== false) { $request['options'] = array_merge($this->options, $request['options']); // Disallow forcing the type, as that's a per request setting unset($request['options']['type']); } return $request; } } 0 is executed later */ public function register($hook, $callback, $priority = 0) { if (!isset($this->hooks[$hook])) { $this->hooks[$hook] = array(); } if (!isset($this->hooks[$hook][$priority])) { $this->hooks[$hook][$priority] = array(); } $this->hooks[$hook][$priority][] = $callback; } /** * Dispatch a message * * @param string $hook Hook name * @param array $parameters Parameters to pass to callbacks * @return boolean Successfulness */ public function dispatch($hook, $parameters = array()) { if (empty($this->hooks[$hook])) { return false; } foreach ($this->hooks[$hook] as $priority => $hooked) { foreach ($hooked as $callback) { call_user_func_array($callback, $parameters); } } return true; } } array( 'port' => 674 ), 'dict' => array( 'port' => 2628 ), 'file' => array( 'ihost' => 'localhost' ), 'http' => array( 'port' => 80, ), 'https' => array( 'port' => 443, ), ); /** * Return the entire IRI when you try and read the object as a string * * @return string */ public function __toString() { return $this->get_iri(); } /** * Overload __set() to provide access via properties * * @param string $name Property name * @param mixed $value Property value */ public function __set($name, $value) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), $value); } elseif ( $name === 'iauthority' || $name === 'iuserinfo' || $name === 'ihost' || $name === 'ipath' || $name === 'iquery' || $name === 'ifragment' ) { call_user_func(array($this, 'set_' . substr($name, 1)), $value); } } /** * Overload __get() to provide access via properties * * @param string $name Property name * @return mixed */ public function __get($name) { // isset() returns false for null, we don't want to do that // Also why we use array_key_exists below instead of isset() $props = get_object_vars($this); if ( $name === 'iri' || $name === 'uri' || $name === 'iauthority' || $name === 'authority' ) { $method = 'get_' . $name; $return = $this->$method(); } elseif (array_key_exists($name, $props)) { $return = $this->$name; } // host -> ihost elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } // ischeme -> scheme elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } else { trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); $return = null; } if ($return === null && isset($this->normalization[$this->scheme][$name])) { return $this->normalization[$this->scheme][$name]; } else { return $return; } } /** * Overload __isset() to provide access via properties * * @param string $name Property name * @return bool */ public function __isset($name) { return (method_exists($this, 'get_' . $name) || isset($this->$name)); } /** * Overload __unset() to provide access via properties * * @param string $name Property name */ public function __unset($name) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), ''); } } /** * Create a new IRI object, from a specified string * * @param string|null $iri */ public function __construct($iri = null) { $this->set_iri($iri); } /** * Create a new IRI object by resolving a relative IRI * * Returns false if $base is not absolute, otherwise an IRI. * * @param IRI|string $base (Absolute) Base IRI * @param IRI|string $relative Relative IRI * @return IRI|false */ public static function absolutize($base, $relative) { if (!($relative instanceof Requests_IRI)) { $relative = new Requests_IRI($relative); } if (!$relative->is_valid()) { return false; } elseif ($relative->scheme !== null) { return clone $relative; } if (!($base instanceof Requests_IRI)) { $base = new Requests_IRI($base); } if ($base->scheme === null || !$base->is_valid()) { return false; } if ($relative->get_iri() !== '') { if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { $target = clone $relative; $target->scheme = $base->scheme; } else { $target = new Requests_IRI; $target->scheme = $base->scheme; $target->iuserinfo = $base->iuserinfo; $target->ihost = $base->ihost; $target->port = $base->port; if ($relative->ipath !== '') { if ($relative->ipath[0] === '/') { $target->ipath = $relative->ipath; } elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { $target->ipath = '/' . $relative->ipath; } elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; } else { $target->ipath = $relative->ipath; } $target->ipath = $target->remove_dot_segments($target->ipath); $target->iquery = $relative->iquery; } else { $target->ipath = $base->ipath; if ($relative->iquery !== null) { $target->iquery = $relative->iquery; } elseif ($base->iquery !== null) { $target->iquery = $base->iquery; } } $target->ifragment = $relative->ifragment; } } else { $target = clone $base; $target->ifragment = null; } $target->scheme_normalization(); return $target; } /** * Parse an IRI into scheme/authority/path/query/fragment segments * * @param string $iri * @return array */ protected function parse_iri($iri) { $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); $has_match = preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match); if (!$has_match) { throw new Requests_Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri); } if ($match[1] === '') { $match['scheme'] = null; } if (!isset($match[3]) || $match[3] === '') { $match['authority'] = null; } if (!isset($match[5])) { $match['path'] = ''; } if (!isset($match[6]) || $match[6] === '') { $match['query'] = null; } if (!isset($match[8]) || $match[8] === '') { $match['fragment'] = null; } return $match; } /** * Remove dot segments from a path * * @param string $input * @return string */ protected function remove_dot_segments($input) { $output = ''; while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { // A: If the input buffer begins with a prefix of "../" or "./", // then remove that prefix from the input buffer; otherwise, if (strpos($input, '../') === 0) { $input = substr($input, 3); } elseif (strpos($input, './') === 0) { $input = substr($input, 2); } // B: if the input buffer begins with a prefix of "/./" or "/.", // where "." is a complete path segment, then replace that prefix // with "/" in the input buffer; otherwise, elseif (strpos($input, '/./') === 0) { $input = substr($input, 2); } elseif ($input === '/.') { $input = '/'; } // C: if the input buffer begins with a prefix of "/../" or "/..", // where ".." is a complete path segment, then replace that prefix // with "/" in the input buffer and remove the last segment and its // preceding "/" (if any) from the output buffer; otherwise, elseif (strpos($input, '/../') === 0) { $input = substr($input, 3); $output = substr_replace($output, '', strrpos($output, '/')); } elseif ($input === '/..') { $input = '/'; $output = substr_replace($output, '', strrpos($output, '/')); } // D: if the input buffer consists only of "." or "..", then remove // that from the input buffer; otherwise, elseif ($input === '.' || $input === '..') { $input = ''; } // E: move the first path segment in the input buffer to the end of // the output buffer, including the initial "/" character (if any) // and any subsequent characters up to, but not including, the next // "/" character or the end of the input buffer elseif (($pos = strpos($input, '/', 1)) !== false) { $output .= substr($input, 0, $pos); $input = substr_replace($input, '', 0, $pos); } else { $output .= $input; $input = ''; } } return $output . $input; } /** * Replace invalid character with percent encoding * * @param string $string Input string * @param string $extra_chars Valid characters not in iunreserved or * iprivate (this is ASCII-only) * @param bool $iprivate Allow iprivate * @return string */ protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) { // Normalize as many pct-encoded sections as possible $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array(&$this, 'remove_iunreserved_percent_encoded'), $string); // Replace invalid percent characters $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); // Add unreserved and % to $extra_chars (the latter is safe because all // pct-encoded sections are now valid). $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; // Now replace any bytes that aren't allowed with their pct-encoded versions $position = 0; $strlen = strlen($string); while (($position += strspn($string, $extra_chars, $position)) < $strlen) { $value = ord($string[$position]); // Start position $start = $position; // By default we are valid $valid = true; // No one byte sequences are valid due to the while. // Two byte sequence: if (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $length = 1; $remaining = 0; } if ($remaining) { if ($position + $length <= $strlen) { for ($position++; $remaining; $position++) { $value = ord($string[$position]); // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $character |= ($value & 0x3F) << (--$remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte: else { $valid = false; $position--; break; } } } else { $position = $strlen - 1; $valid = false; } } // Percent encode anything invalid or not in ucschar if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF || ( // Everything else not in ucschar $character > 0xD7FF && $character < 0xF900 || $character < 0xA0 || $character > 0xEFFFD ) && ( // Everything not in iprivate, if it applies !$iprivate || $character < 0xE000 || $character > 0x10FFFD ) ) { // If we were a character, pretend we weren't, but rather an error. if ($valid) { $position--; } for ($j = $start; $j <= $position; $j++) { $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); $j += 2; $position += 2; $strlen += 2; } } } return $string; } /** * Callback function for preg_replace_callback. * * Removes sequences of percent encoded bytes that represent UTF-8 * encoded characters in iunreserved * * @param array $match PCRE match * @return string Replacement */ protected function remove_iunreserved_percent_encoded($match) { // As we just have valid percent encoded sequences we can just explode // and ignore the first member of the returned array (an empty string). $bytes = explode('%', $match[0]); // Initialize the new string (this is what will be returned) and that // there are no bytes remaining in the current sequence (unsurprising // at the first byte!). $string = ''; $remaining = 0; // Loop over each and every byte, and set $value to its value for ($i = 1, $len = count($bytes); $i < $len; $i++) { $value = hexdec($bytes[$i]); // If we're the first byte of sequence: if (!$remaining) { // Start position $start = $i; // By default we are valid $valid = true; // One byte sequence: if ($value <= 0x7F) { $character = $value; $length = 1; } // Two byte sequence: elseif (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $remaining = 0; } } // Continuation byte: else { // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $remaining--; $character |= ($value & 0x3F) << ($remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: else { $valid = false; $remaining = 0; $i--; } } // If we've reached the end of the current byte sequence, append it to Unicode::$data if (!$remaining) { // Percent encode anything invalid or not in iunreserved if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of iunreserved codepoints || $character < 0x2D || $character > 0xEFFFD // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF // Everything else not in iunreserved (this is all BMP) || $character === 0x2F || $character > 0x39 && $character < 0x41 || $character > 0x5A && $character < 0x61 || $character > 0x7A && $character < 0x7E || $character > 0x7E && $character < 0xA0 || $character > 0xD7FF && $character < 0xF900 ) { for ($j = $start; $j <= $i; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } else { for ($j = $start; $j <= $i; $j++) { $string .= chr(hexdec($bytes[$j])); } } } } // If we have any bytes left over they are invalid (i.e., we are // mid-way through a multi-byte sequence) if ($remaining) { for ($j = $start; $j < $len; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } return $string; } protected function scheme_normalization() { if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { $this->iuserinfo = null; } if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { $this->ihost = null; } if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { $this->port = null; } if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { $this->ipath = ''; } if (isset($this->ihost) && empty($this->ipath)) { $this->ipath = '/'; } if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { $this->iquery = null; } if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { $this->ifragment = null; } } /** * Check if the object represents a valid IRI. This needs to be done on each * call as some things change depending on another part of the IRI. * * @return bool */ public function is_valid() { $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; if ($this->ipath !== '' && ( $isauthority && $this->ipath[0] !== '/' || ( $this->scheme === null && !$isauthority && strpos($this->ipath, ':') !== false && (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) ) ) ) { return false; } return true; } /** * Set the entire IRI. Returns true on success, false on failure (if there * are any invalid characters). * * @param string $iri * @return bool */ protected function set_iri($iri) { static $cache; if (!$cache) { $cache = array(); } if ($iri === null) { return true; } if (isset($cache[$iri])) { list($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return) = $cache[$iri]; return $return; } $parsed = $this->parse_iri((string) $iri); $return = $this->set_scheme($parsed['scheme']) && $this->set_authority($parsed['authority']) && $this->set_path($parsed['path']) && $this->set_query($parsed['query']) && $this->set_fragment($parsed['fragment']); $cache[$iri] = array($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return); return $return; } /** * Set the scheme. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $scheme * @return bool */ protected function set_scheme($scheme) { if ($scheme === null) { $this->scheme = null; } elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { $this->scheme = null; return false; } else { $this->scheme = strtolower($scheme); } return true; } /** * Set the authority. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $authority * @return bool */ protected function set_authority($authority) { static $cache; if (!$cache) { $cache = array(); } if ($authority === null) { $this->iuserinfo = null; $this->ihost = null; $this->port = null; return true; } if (isset($cache[$authority])) { list($this->iuserinfo, $this->ihost, $this->port, $return) = $cache[$authority]; return $return; } $remaining = $authority; if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { $iuserinfo = substr($remaining, 0, $iuserinfo_end); $remaining = substr($remaining, $iuserinfo_end + 1); } else { $iuserinfo = null; } if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) { $port = substr($remaining, $port_start + 1); if ($port === false || $port === '') { $port = null; } $remaining = substr($remaining, 0, $port_start); } else { $port = null; } $return = $this->set_userinfo($iuserinfo) && $this->set_host($remaining) && $this->set_port($port); $cache[$authority] = array($this->iuserinfo, $this->ihost, $this->port, $return); return $return; } /** * Set the iuserinfo. * * @param string $iuserinfo * @return bool */ protected function set_userinfo($iuserinfo) { if ($iuserinfo === null) { $this->iuserinfo = null; } else { $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); $this->scheme_normalization(); } return true; } /** * Set the ihost. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $ihost * @return bool */ protected function set_host($ihost) { if ($ihost === null) { $this->ihost = null; return true; } if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) { $this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']'; } else { $this->ihost = null; return false; } } else { $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); // Lowercase, but ignore pct-encoded sections (as they should // remain uppercase). This must be done after the previous step // as that can add unescaped characters. $position = 0; $strlen = strlen($ihost); while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { if ($ihost[$position] === '%') { $position += 3; } else { $ihost[$position] = strtolower($ihost[$position]); $position++; } } $this->ihost = $ihost; } $this->scheme_normalization(); return true; } /** * Set the port. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $port * @return bool */ protected function set_port($port) { if ($port === null) { $this->port = null; return true; } if (strspn($port, '0123456789') === strlen($port)) { $this->port = (int) $port; $this->scheme_normalization(); return true; } $this->port = null; return false; } /** * Set the ipath. * * @param string $ipath * @return bool */ protected function set_path($ipath) { static $cache; if (!$cache) { $cache = array(); } $ipath = (string) $ipath; if (isset($cache[$ipath])) { $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; } else { $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); $removed = $this->remove_dot_segments($valid); $cache[$ipath] = array($valid, $removed); $this->ipath = ($this->scheme !== null) ? $removed : $valid; } $this->scheme_normalization(); return true; } /** * Set the iquery. * * @param string $iquery * @return bool */ protected function set_query($iquery) { if ($iquery === null) { $this->iquery = null; } else { $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); $this->scheme_normalization(); } return true; } /** * Set the ifragment. * * @param string $ifragment * @return bool */ protected function set_fragment($ifragment) { if ($ifragment === null) { $this->ifragment = null; } else { $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); $this->scheme_normalization(); } return true; } /** * Convert an IRI to a URI (or parts thereof) * * @param string|bool IRI to convert (or false from {@see get_iri}) * @return string|false URI if IRI is valid, false otherwise. */ protected function to_uri($string) { if (!is_string($string)) { return false; } static $non_ascii; if (!$non_ascii) { $non_ascii = implode('', range("\x80", "\xFF")); } $position = 0; $strlen = strlen($string); while (($position += strcspn($string, $non_ascii, $position)) < $strlen) { $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); $position += 3; $strlen += 2; } return $string; } /** * Get the complete IRI * * @return string */ protected function get_iri() { if (!$this->is_valid()) { return false; } $iri = ''; if ($this->scheme !== null) { $iri .= $this->scheme . ':'; } if (($iauthority = $this->get_iauthority()) !== null) { $iri .= '//' . $iauthority; } $iri .= $this->ipath; if ($this->iquery !== null) { $iri .= '?' . $this->iquery; } if ($this->ifragment !== null) { $iri .= '#' . $this->ifragment; } return $iri; } /** * Get the complete URI * * @return string */ protected function get_uri() { return $this->to_uri($this->get_iri()); } /** * Get the complete iauthority * * @return string */ protected function get_iauthority() { if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) { return null; } $iauthority = ''; if ($this->iuserinfo !== null) { $iauthority .= $this->iuserinfo . '@'; } if ($this->ihost !== null) { $iauthority .= $this->ihost; } if ($this->port !== null) { $iauthority .= ':' . $this->port; } return $iauthority; } /** * Get the complete authority * * @return string */ protected function get_authority() { $iauthority = $this->get_iauthority(); if (is_string($iauthority)) { return $this->to_uri($iauthority); } else { return $iauthority; } } } version = $curl['version_number']; $this->handle = curl_init(); curl_setopt($this->handle, CURLOPT_HEADER, false); curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); if ($this->version >= self::CURL_7_10_5) { curl_setopt($this->handle, CURLOPT_ENCODING, ''); } if (defined('CURLOPT_PROTOCOLS')) { curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); } if (defined('CURLOPT_REDIR_PROTOCOLS')) { curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); } } /** * Destructor */ public function __destruct() { if (is_resource($this->handle)) { curl_close($this->handle); } } /** * Perform a request * * @throws Requests_Exception On a cURL error (`curlerror`) * * @param string $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see Requests::response()} for documentation * @return string Raw HTTP result */ public function request($url, $headers = array(), $data = array(), $options = array()) { $this->hooks = $options['hooks']; $this->setup_handle($url, $headers, $data, $options); $options['hooks']->dispatch('curl.before_send', array(&$this->handle)); if ($options['filename'] !== false) { $this->stream_handle = fopen($options['filename'], 'wb'); } $this->response_data = ''; $this->response_bytes = 0; $this->response_byte_limit = false; if ($options['max_bytes'] !== false) { $this->response_byte_limit = $options['max_bytes']; } if (isset($options['verify'])) { if ($options['verify'] === false) { curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0); } elseif (is_string($options['verify'])) { curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']); } } if (isset($options['verifyname']) && $options['verifyname'] === false) { curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); } curl_exec($this->handle); $response = $this->response_data; $options['hooks']->dispatch('curl.after_send', array()); if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) { // Reset encoding and try again curl_setopt($this->handle, CURLOPT_ENCODING, 'none'); $this->response_data = ''; $this->response_bytes = 0; curl_exec($this->handle); $response = $this->response_data; } $this->process_response($response, $options); // Need to remove the $this reference from the curl handle. // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called. curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); return $this->headers; } /** * Send multiple requests simultaneously * * @param array $requests Request data * @param array $options Global options * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well) */ public function request_multiple($requests, $options) { // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ if (empty($requests)) { return array(); } $multihandle = curl_multi_init(); $subrequests = array(); $subhandles = array(); $class = get_class($this); foreach ($requests as $id => $request) { $subrequests[$id] = new $class(); $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); $request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id])); curl_multi_add_handle($multihandle, $subhandles[$id]); } $completed = 0; $responses = array(); $request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle)); do { $active = false; do { $status = curl_multi_exec($multihandle, $active); } while ($status === CURLM_CALL_MULTI_PERFORM); $to_process = array(); // Read the information as needed while ($done = curl_multi_info_read($multihandle)) { $key = array_search($done['handle'], $subhandles, true); if (!isset($to_process[$key])) { $to_process[$key] = $done; } } // Parse the finished requests before we start getting the new ones foreach ($to_process as $key => $done) { $options = $requests[$key]['options']; if (CURLE_OK !== $done['result']) { //get error string for handle. $reason = curl_error($done['handle']); $exception = new Requests_Exception_Transport_cURL( $reason, Requests_Exception_Transport_cURL::EASY, $done['handle'], $done['result'] ); $responses[$key] = $exception; $options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key])); } else { $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key])); } curl_multi_remove_handle($multihandle, $done['handle']); curl_close($done['handle']); if (!is_string($responses[$key])) { $options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key)); } $completed++; } } while ($active || $completed < count($subrequests)); $request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle)); curl_multi_close($multihandle); return $responses; } /** * Get the cURL handle for use in a multi-request * * @param string $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see Requests::response()} for documentation * @return resource Subrequest's cURL handle */ public function &get_subrequest_handle($url, $headers, $data, $options) { $this->setup_handle($url, $headers, $data, $options); if ($options['filename'] !== false) { $this->stream_handle = fopen($options['filename'], 'wb'); } $this->response_data = ''; $this->response_bytes = 0; $this->response_byte_limit = false; if ($options['max_bytes'] !== false) { $this->response_byte_limit = $options['max_bytes']; } $this->hooks = $options['hooks']; return $this->handle; } /** * Setup the cURL handle for the given data * * @param string $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see Requests::response()} for documentation */ protected function setup_handle($url, $headers, $data, $options) { $options['hooks']->dispatch('curl.before_request', array(&$this->handle)); // Force closing the connection for old versions of cURL (<7.22). if ( ! isset( $headers['Connection'] ) ) { $headers['Connection'] = 'close'; } $headers = Requests::flatten($headers); if (!empty($data)) { $data_format = $options['data_format']; if ($data_format === 'query') { $url = self::format_get($url, $data); $data = ''; } elseif (!is_string($data)) { $data = http_build_query($data, null, '&'); } } switch ($options['type']) { case Requests::POST: curl_setopt($this->handle, CURLOPT_POST, true); curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); break; case Requests::HEAD: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); curl_setopt($this->handle, CURLOPT_NOBODY, true); break; case Requests::TRACE: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); break; case Requests::PATCH: case Requests::PUT: case Requests::DELETE: case Requests::OPTIONS: default: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); if (!empty($data)) { curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); } } // cURL requires a minimum timeout of 1 second when using the system // DNS resolver, as it uses `alarm()`, which is second resolution only. // There's no way to detect which DNS resolver is being used from our // end, so we need to round up regardless of the supplied timeout. // // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609 $timeout = max($options['timeout'], 1); if (is_int($timeout) || $this->version < self::CURL_7_16_2) { curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout)); } else { curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000)); } if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) { curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout'])); } else { curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000)); } curl_setopt($this->handle, CURLOPT_URL, $url); curl_setopt($this->handle, CURLOPT_REFERER, $url); curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); if (!empty($headers)) { curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); } if ($options['protocol_version'] === 1.1) { curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); } else { curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); } if (true === $options['blocking']) { curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers')); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body')); curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE); } } /** * Process a response * * @param string $response Response data from the body * @param array $options Request options * @return string HTTP response data including headers */ public function process_response($response, $options) { if ($options['blocking'] === false) { $fake_headers = ''; $options['hooks']->dispatch('curl.after_request', array(&$fake_headers)); return false; } if ($options['filename'] !== false) { fclose($this->stream_handle); $this->headers = trim($this->headers); } else { $this->headers .= $response; } if (curl_errno($this->handle)) { $error = sprintf( 'cURL error %s: %s', curl_errno($this->handle), curl_error($this->handle) ); throw new Requests_Exception($error, 'curlerror', $this->handle); } $this->info = curl_getinfo($this->handle); $options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info)); return $this->headers; } /** * Collect the headers as they are received * * @param resource $handle cURL resource * @param string $headers Header string * @return integer Length of provided header */ public function stream_headers($handle, $headers) { // Why do we do this? cURL will send both the final response and any // interim responses, such as a 100 Continue. We don't need that. // (We may want to keep this somewhere just in case) if ($this->done_headers) { $this->headers = ''; $this->done_headers = false; } $this->headers .= $headers; if ($headers === "\r\n") { $this->done_headers = true; } return strlen($headers); } /** * Collect data as it's received * * @since 1.6.1 * * @param resource $handle cURL resource * @param string $data Body data * @return integer Length of provided data */ public function stream_body($handle, $data) { $this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit)); $data_length = strlen($data); // Are we limiting the response size? if ($this->response_byte_limit) { if ($this->response_bytes === $this->response_byte_limit) { // Already at maximum, move on return $data_length; } if (($this->response_bytes + $data_length) > $this->response_byte_limit) { // Limit the length $limited_length = ($this->response_byte_limit - $this->response_bytes); $data = substr($data, 0, $limited_length); } } if ($this->stream_handle) { fwrite($this->stream_handle, $data); } else { $this->response_data .= $data; } $this->response_bytes += strlen($data); return $data_length; } /** * Format a URL given GET data * * @param string $url * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query} * @return string URL with data */ protected static function format_get($url, $data) { if (!empty($data)) { $url_parts = parse_url($url); if (empty($url_parts['query'])) { $query = $url_parts['query'] = ''; } else { $query = $url_parts['query']; } $query .= '&' . http_build_query($data, null, '&'); $query = trim($query, '&'); if (empty($url_parts['query'])) { $url .= '?' . $query; } else { $url = str_replace($url_parts['query'], $query, $url); } } return $url; } /** * Whether this transport is valid * * @codeCoverageIgnore * @return boolean True if the transport is valid, false otherwise. */ public static function test($capabilities = array()) { if (!function_exists('curl_init') || !function_exists('curl_exec')) { return false; } // If needed, check that our installed curl version supports SSL if (isset($capabilities['ssl']) && $capabilities['ssl']) { $curl_version = curl_version(); if (!(CURL_VERSION_SSL & $curl_version['features'])) { return false; } } return true; } } dispatch('fsockopen.before_request'); $url_parts = parse_url($url); if (empty($url_parts)) { throw new Requests_Exception('Invalid URL.', 'invalidurl', $url); } $host = $url_parts['host']; $context = stream_context_create(); $verifyname = false; $case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers); // HTTPS support if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { $remote_socket = 'ssl://' . $host; if (!isset($url_parts['port'])) { $url_parts['port'] = 443; } $context_options = array( 'verify_peer' => true, // 'CN_match' => $host, 'capture_peer_cert' => true ); $verifyname = true; // SNI, if enabled (OpenSSL >=0.9.8j) if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) { $context_options['SNI_enabled'] = true; if (isset($options['verifyname']) && $options['verifyname'] === false) { $context_options['SNI_enabled'] = false; } } if (isset($options['verify'])) { if ($options['verify'] === false) { $context_options['verify_peer'] = false; } elseif (is_string($options['verify'])) { $context_options['cafile'] = $options['verify']; } } if (isset($options['verifyname']) && $options['verifyname'] === false) { $context_options['verify_peer_name'] = false; $verifyname = false; } stream_context_set_option($context, array('ssl' => $context_options)); } else { $remote_socket = 'tcp://' . $host; } $this->max_bytes = $options['max_bytes']; if (!isset($url_parts['port'])) { $url_parts['port'] = 80; } $remote_socket .= ':' . $url_parts['port']; set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE); $options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket)); $socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context); restore_error_handler(); if ($verifyname && !$this->verify_certificate_from_context($host, $context)) { throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match'); } if (!$socket) { if ($errno === 0) { // Connection issue throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); } throw new Requests_Exception($errstr, 'fsockopenerror', null, $errno); } $data_format = $options['data_format']; if ($data_format === 'query') { $path = self::format_get($url_parts, $data); $data = ''; } else { $path = self::format_get($url_parts, array()); } $options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url)); $request_body = ''; $out = sprintf("%s %s HTTP/%.1f\r\n", $options['type'], $path, $options['protocol_version']); if ($options['type'] !== Requests::TRACE) { if (is_array($data)) { $request_body = http_build_query($data, null, '&'); } else { $request_body = $data; } if (!empty($data)) { if (!isset($case_insensitive_headers['Content-Length'])) { $headers['Content-Length'] = strlen($request_body); } if (!isset($case_insensitive_headers['Content-Type'])) { $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; } } } if (!isset($case_insensitive_headers['Host'])) { $out .= sprintf('Host: %s', $url_parts['host']); if (( 'http' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 80 ) || ( 'https' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 443 )) { $out .= ':' . $url_parts['port']; } $out .= "\r\n"; } if (!isset($case_insensitive_headers['User-Agent'])) { $out .= sprintf("User-Agent: %s\r\n", $options['useragent']); } $accept_encoding = $this->accept_encoding(); if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) { $out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding); } $headers = Requests::flatten($headers); if (!empty($headers)) { $out .= implode($headers, "\r\n") . "\r\n"; } $options['hooks']->dispatch('fsockopen.after_headers', array(&$out)); if (substr($out, -2) !== "\r\n") { $out .= "\r\n"; } if (!isset($case_insensitive_headers['Connection'])) { $out .= "Connection: Close\r\n"; } $out .= "\r\n" . $request_body; $options['hooks']->dispatch('fsockopen.before_send', array(&$out)); fwrite($socket, $out); $options['hooks']->dispatch('fsockopen.after_send', array($out)); if (!$options['blocking']) { fclose($socket); $fake_headers = ''; $options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers)); return ''; } $timeout_sec = (int) floor($options['timeout']); if ($timeout_sec == $options['timeout']) { $timeout_msec = 0; } else { $timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS; } stream_set_timeout($socket, $timeout_sec, $timeout_msec); $response = $body = $headers = ''; $this->info = stream_get_meta_data($socket); $size = 0; $doingbody = false; $download = false; if ($options['filename']) { $download = fopen($options['filename'], 'wb'); } while (!feof($socket)) { $this->info = stream_get_meta_data($socket); if ($this->info['timed_out']) { throw new Requests_Exception('fsocket timed out', 'timeout'); } $block = fread($socket, Requests::BUFFER_SIZE); if (!$doingbody) { $response .= $block; if (strpos($response, "\r\n\r\n")) { list($headers, $block) = explode("\r\n\r\n", $response, 2); $doingbody = true; } } // Are we in body mode now? if ($doingbody) { $options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes)); $data_length = strlen($block); if ($this->max_bytes) { // Have we already hit a limit? if ($size === $this->max_bytes) { continue; } if (($size + $data_length) > $this->max_bytes) { // Limit the length $limited_length = ($this->max_bytes - $size); $block = substr($block, 0, $limited_length); } } $size += strlen($block); if ($download) { fwrite($download, $block); } else { $body .= $block; } } } $this->headers = $headers; if ($download) { fclose($download); } else { $this->headers .= "\r\n\r\n" . $body; } fclose($socket); $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info)); return $this->headers; } /** * Send multiple requests simultaneously * * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request} * @param array $options Global options, see {@see Requests::response()} for documentation * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well) */ public function request_multiple($requests, $options) { $responses = array(); $class = get_class($this); foreach ($requests as $id => $request) { try { $handler = new $class(); $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']); $request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request)); } catch (Requests_Exception $e) { $responses[$id] = $e; } if (!is_string($responses[$id])) { $request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id)); } } return $responses; } /** * Retrieve the encodings we can accept * * @return string Accept-Encoding header value */ protected static function accept_encoding() { $type = array(); if (function_exists('gzinflate')) { $type[] = 'deflate;q=1.0'; } if (function_exists('gzuncompress')) { $type[] = 'compress;q=0.5'; } $type[] = 'gzip;q=0.5'; return implode(', ', $type); } /** * Format a URL given GET data * * @param array $url_parts * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query} * @return string URL with data */ protected static function format_get($url_parts, $data) { if (!empty($data)) { if (empty($url_parts['query'])) { $url_parts['query'] = ''; } $url_parts['query'] .= '&' . http_build_query($data, null, '&'); $url_parts['query'] = trim($url_parts['query'], '&'); } if (isset($url_parts['path'])) { if (isset($url_parts['query'])) { $get = $url_parts['path'] . '?' . $url_parts['query']; } else { $get = $url_parts['path']; } } else { $get = '/'; } return $get; } /** * Error handler for stream_socket_client() * * @param int $errno Error number (e.g. E_WARNING) * @param string $errstr Error message */ public function connect_error_handler($errno, $errstr) { // Double-check we can handle it if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) { // Return false to indicate the default error handler should engage return false; } $this->connect_error .= $errstr . "\n"; return true; } /** * Verify the certificate against common name and subject alternative names * * Unfortunately, PHP doesn't check the certificate against the alternative * names, leading things like 'https://www.github.com/' to be invalid. * Instead * * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 * * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`) * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) * @param string $host Host name to verify against * @param resource $context Stream context * @return bool */ public function verify_certificate_from_context($host, $context) { $meta = stream_context_get_options($context); // If we don't have SSL options, then we couldn't make the connection at // all if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error'); } $cert = openssl_x509_parse($meta['ssl']['peer_certificate']); return Requests_SSL::verify_certificate($host, $cert); } /** * Whether this transport is valid * * @codeCoverageIgnore * @return boolean True if the transport is valid, false otherwise. */ public static function test($capabilities = array()) { if (!function_exists('fsockopen')) { return false; } // If needed, check that streams support SSL if (isset($capabilities['ssl']) && $capabilities['ssl']) { if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) { return false; } // Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156 if (defined('HHVM_VERSION')) { return false; } } return true; } } cookies = $cookies; } /** * Normalise cookie data into a Requests_Cookie * * @param string|Requests_Cookie $cookie * @return Requests_Cookie */ public function normalize_cookie($cookie, $key = null) { if ($cookie instanceof Requests_Cookie) { return $cookie; } return Requests_Cookie::parse($cookie, $key); } /** * Normalise cookie data into a Requests_Cookie * * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie_Jar::normalize_cookie} * @return Requests_Cookie */ public function normalizeCookie($cookie, $key = null) { return $this->normalize_cookie($cookie, $key); } /** * Check if the given item exists * * @param string $key Item key * @return boolean Does the item exist? */ public function offsetExists($key) { return isset($this->cookies[$key]); } /** * Get the value for the item * * @param string $key Item key * @return string Item value */ public function offsetGet($key) { if (!isset($this->cookies[$key])) { return null; } return $this->cookies[$key]; } /** * Set the given item * * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`) * * @param string $key Item name * @param string $value Item value */ public function offsetSet($key, $value) { if ($key === null) { throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset'); } $this->cookies[$key] = $value; } /** * Unset the given header * * @param string $key */ public function offsetUnset($key) { unset($this->cookies[$key]); } /** * Get an iterator for the data * * @return ArrayIterator */ public function getIterator() { return new ArrayIterator($this->cookies); } /** * Register the cookie handler with the request's hooking system * * @param Requests_Hooker $hooks Hooking system */ public function register(Requests_Hooker $hooks) { $hooks->register('requests.before_request', array($this, 'before_request')); $hooks->register('requests.before_redirect_check', array($this, 'before_redirect_check')); } /** * Add Cookie header to a request if we have any * * As per RFC 6265, cookies are separated by '; ' * * @param string $url * @param array $headers * @param array $data * @param string $type * @param array $options */ public function before_request($url, &$headers, &$data, &$type, &$options) { if (!$url instanceof Requests_IRI) { $url = new Requests_IRI($url); } if (!empty($this->cookies)) { $cookies = array(); foreach ($this->cookies as $key => $cookie) { $cookie = $this->normalize_cookie($cookie, $key); // Skip expired cookies if ($cookie->is_expired()) { continue; } if ($cookie->domain_matches($url->host)) { $cookies[] = $cookie->format_for_header(); } } $headers['Cookie'] = implode('; ', $cookies); } } /** * Parse all cookies from a response and attach them to the response * * @var Requests_Response $response */ public function before_redirect_check(Requests_Response &$return) { $url = $return->url; if (!$url instanceof Requests_IRI) { $url = new Requests_IRI($url); } $cookies = Requests_Cookie::parse_from_headers($return->headers, $url); $this->cookies = array_merge($this->cookies, $cookies); $return->cookies = $this; } }name = $name; $this->value = $value; $this->attributes = $attributes; $default_flags = array( 'creation' => time(), 'last-access' => time(), 'persistent' => false, 'host-only' => true, ); $this->flags = array_merge($default_flags, $flags); $this->reference_time = time(); if ($reference_time !== null) { $this->reference_time = $reference_time; } $this->normalize(); } /** * Check if a cookie is expired. * * Checks the age against $this->reference_time to determine if the cookie * is expired. * * @return boolean True if expired, false if time is valid. */ public function is_expired() { // RFC6265, s. 4.1.2.2: // If a cookie has both the Max-Age and the Expires attribute, the Max- // Age attribute has precedence and controls the expiration date of the // cookie. if (isset($this->attributes['max-age'])) { $max_age = $this->attributes['max-age']; return $max_age < $this->reference_time; } if (isset($this->attributes['expires'])) { $expires = $this->attributes['expires']; return $expires < $this->reference_time; } return false; } /** * Check if a cookie is valid for a given URI * * @param Requests_IRI $uri URI to check * @return boolean Whether the cookie is valid for the given URI */ public function uri_matches(Requests_IRI $uri) { if (!$this->domain_matches($uri->host)) { return false; } if (!$this->path_matches($uri->path)) { return false; } return empty($this->attributes['secure']) || $uri->scheme === 'https'; } /** * Check if a cookie is valid for a given domain * * @param string $string Domain to check * @return boolean Whether the cookie is valid for the given domain */ public function domain_matches($string) { if (!isset($this->attributes['domain'])) { // Cookies created manually; cookies created by Requests will set // the domain to the requested domain return true; } $domain_string = $this->attributes['domain']; if ($domain_string === $string) { // The domain string and the string are identical. return true; } // If the cookie is marked as host-only and we don't have an exact // match, reject the cookie if ($this->flags['host-only'] === true) { return false; } if (strlen($string) <= strlen($domain_string)) { // For obvious reasons, the string cannot be a suffix if the domain // is shorter than the domain string return false; } if (substr($string, -1 * strlen($domain_string)) !== $domain_string) { // The domain string should be a suffix of the string. return false; } $prefix = substr($string, 0, strlen($string) - strlen($domain_string)); if (substr($prefix, -1) !== '.') { // The last character of the string that is not included in the // domain string should be a %x2E (".") character. return false; } // The string should be a host name (i.e., not an IP address). return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $string); } /** * Check if a cookie is valid for a given path * * From the path-match check in RFC 6265 section 5.1.4 * * @param string $request_path Path to check * @return boolean Whether the cookie is valid for the given path */ public function path_matches($request_path) { if (empty($request_path)) { // Normalize empty path to root $request_path = '/'; } if (!isset($this->attributes['path'])) { // Cookies created manually; cookies created by Requests will set // the path to the requested path return true; } $cookie_path = $this->attributes['path']; if ($cookie_path === $request_path) { // The cookie-path and the request-path are identical. return true; } if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) { if (substr($cookie_path, -1) === '/') { // The cookie-path is a prefix of the request-path, and the last // character of the cookie-path is %x2F ("/"). return true; } if (substr($request_path, strlen($cookie_path), 1) === '/') { // The cookie-path is a prefix of the request-path, and the // first character of the request-path that is not included in // the cookie-path is a %x2F ("/") character. return true; } } return false; } /** * Normalize cookie and attributes * * @return boolean Whether the cookie was successfully normalized */ public function normalize() { foreach ($this->attributes as $key => $value) { $orig_value = $value; $value = $this->normalize_attribute($key, $value); if ($value === null) { unset($this->attributes[$key]); continue; } if ($value !== $orig_value) { $this->attributes[$key] = $value; } } return true; } /** * Parse an individual cookie attribute * * Handles parsing individual attributes from the cookie values. * * @param string $name Attribute name * @param string|boolean $value Attribute value (string value, or true if empty/flag) * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped) */ protected function normalize_attribute($name, $value) { switch (strtolower($name)) { case 'expires': // Expiration parsing, as per RFC 6265 section 5.2.1 if (is_int($value)) { return $value; } $expiry_time = strtotime($value); if ($expiry_time === false) { return null; } return $expiry_time; case 'max-age': // Expiration parsing, as per RFC 6265 section 5.2.2 if (is_int($value)) { return $value; } // Check that we have a valid age if (!preg_match('/^-?\d+$/', $value)) { return null; } $delta_seconds = (int) $value; if ($delta_seconds <= 0) { $expiry_time = 0; } else { $expiry_time = $this->reference_time + $delta_seconds; } return $expiry_time; case 'domain': // Domain normalization, as per RFC 6265 section 5.2.3 if ($value[0] === '.') { $value = substr($value, 1); } return $value; default: return $value; } } /** * Format a cookie for a Cookie header * * This is used when sending cookies to a server. * * @return string Cookie formatted for Cookie header */ public function format_for_header() { return sprintf('%s=%s', $this->name, $this->value); } /** * Format a cookie for a Cookie header * * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie::format_for_header} * @return string */ public function formatForHeader() { return $this->format_for_header(); } /** * Format a cookie for a Set-Cookie header * * This is used when sending cookies to clients. This isn't really * applicable to client-side usage, but might be handy for debugging. * * @return string Cookie formatted for Set-Cookie header */ public function format_for_set_cookie() { $header_value = $this->format_for_header(); if (!empty($this->attributes)) { $parts = array(); foreach ($this->attributes as $key => $value) { // Ignore non-associative attributes if (is_numeric($key)) { $parts[] = $value; } else { $parts[] = sprintf('%s=%s', $key, $value); } } $header_value .= '; ' . implode('; ', $parts); } return $header_value; } /** * Format a cookie for a Set-Cookie header * * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie::format_for_set_cookie} * @return string */ public function formatForSetCookie() { return $this->format_for_set_cookie(); } /** * Get the cookie value * * Attributes and other data can be accessed via methods. */ public function __toString() { return $this->value; } /** * Parse a cookie string into a cookie object * * Based on Mozilla's parsing code in Firefox and related projects, which * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265 * specifies some of this handling, but not in a thorough manner. * * @param string Cookie header value (from a Set-Cookie header) * @return Requests_Cookie Parsed cookie object */ public static function parse($string, $name = '', $reference_time = null) { $parts = explode(';', $string); $kvparts = array_shift($parts); if (!empty($name)) { $value = $string; } elseif (strpos($kvparts, '=') === false) { // Some sites might only have a value without the equals separator. // Deviate from RFC 6265 and pretend it was actually a blank name // (`=foo`) // // https://bugzilla.mozilla.org/show_bug.cgi?id=169091 $name = ''; $value = $kvparts; } else { list($name, $value) = explode('=', $kvparts, 2); } $name = trim($name); $value = trim($value); // Attribute key are handled case-insensitively $attributes = new Requests_Utility_CaseInsensitiveDictionary(); if (!empty($parts)) { foreach ($parts as $part) { if (strpos($part, '=') === false) { $part_key = $part; $part_value = true; } else { list($part_key, $part_value) = explode('=', $part, 2); $part_value = trim($part_value); } $part_key = trim($part_key); $attributes[$part_key] = $part_value; } } return new Requests_Cookie($name, $value, $attributes, array(), $reference_time); } /** * Parse all Set-Cookie headers from request headers * * @param Requests_Response_Headers $headers Headers to parse from * @param Requests_IRI|null $origin URI for comparing cookie origins * @param int|null $time Reference time for expiration calculation * @return array */ public static function parse_from_headers(Requests_Response_Headers $headers, Requests_IRI $origin = null, $time = null) { $cookie_headers = $headers->getValues('Set-Cookie'); if (empty($cookie_headers)) { return array(); } $cookies = array(); foreach ($cookie_headers as $header) { $parsed = self::parse($header, '', $time); // Default domain/path attributes if (empty($parsed->attributes['domain']) && !empty($origin)) { $parsed->attributes['domain'] = $origin->host; $parsed->flags['host-only'] = true; } else { $parsed->flags['host-only'] = false; } $path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/'); if (!$path_is_valid && !empty($origin)) { $path = $origin->path; // Default path normalization as per RFC 6265 section 5.1.4 if (substr($path, 0, 1) !== '/') { // If the uri-path is empty or if the first character of // the uri-path is not a %x2F ("/") character, output // %x2F ("/") and skip the remaining steps. $path = '/'; } elseif (substr_count($path, '/') === 1) { // If the uri-path contains no more than one %x2F ("/") // character, output %x2F ("/") and skip the remaining // step. $path = '/'; } else { // Output the characters of the uri-path from the first // character up to, but not including, the right-most // %x2F ("/"). $path = substr($path, 0, strrpos($path, '/')); } $parsed->attributes['path'] = $path; } // Reject invalid cookie domains if (!empty($origin) && !$parsed->domain_matches($origin->host)) { continue; } $cookies[$parsed->name] = $parsed; } return $cookies; } /** * Parse all Set-Cookie headers from request headers * * @codeCoverageIgnore * @deprecated Use {@see Requests_Cookie::parse_from_headers} * @return string */ public static function parseFromHeaders(Requests_Response_Headers $headers) { return self::parse_from_headers($headers); } } 0 is executed later */ public function register($hook, $callback, $priority = 0); /** * Dispatch a message * * @param string $hook Hook name * @param array $parameters Parameters to pass to callbacks * @return boolean Successfulness */ public function dispatch($hook, $parameters = array()); }data[$key])) { return null; } return $this->flatten($this->data[$key]); } /** * Set the given item * * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`) * * @param string $key Item name * @param string $value Item value */ public function offsetSet($key, $value) { if ($key === null) { throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset'); } $key = strtolower($key); if (!isset($this->data[$key])) { $this->data[$key] = array(); } $this->data[$key][] = $value; } /** * Get all values for a given header * * @param string $key * @return array Header values */ public function getValues($key) { $key = strtolower($key); if (!isset($this->data[$key])) { return null; } return $this->data[$key]; } /** * Flattens a value into a string * * Converts an array into a string by imploding values with a comma, as per * RFC2616's rules for folding headers. * * @param string|array $value Value to flatten * @return string Flattened value */ public function flatten($value) { if (is_array($value)) { $value = implode(',', $value); } return $value; } /** * Get an iterator for the data * * Converts the internal * @return ArrayIterator */ public function getIterator() { return new Requests_Utility_FilteredIterator($this->data, array($this, 'flatten')); } } user, $this->pass) = $args; } } /** * Register the necessary callbacks * * @see curl_before_send * @see fsockopen_header * @param Requests_Hooks $hooks Hook system */ public function register(Requests_Hooks &$hooks) { $hooks->register('curl.before_send', array(&$this, 'curl_before_send')); $hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header')); } /** * Set cURL parameters before the data is sent * * @param resource $handle cURL resource */ public function curl_before_send(&$handle) { curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString()); } /** * Add extra headers to the request before sending * * @param string $out HTTP header string */ public function fsockopen_header(&$out) { $out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString())); } /** * Get the authentication string (user:pass) * * @return string */ public function getAuthString() { return $this->user . ':' . $this->pass; } } 0) { if ($position + $length > $strlen) { throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } for ($position++; $remaining > 0; $position++) { $value = ord($input[$position]); // If it is invalid, count the sequence as invalid and reprocess the current byte: if (($value & 0xC0) !== 0x80) { throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } $character |= ($value & 0x3F) << (--$remaining * 6); } $position--; } if ( // Non-shortest form sequences are invalid $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF || ( // Everything else not in ucschar $character > 0xD7FF && $character < 0xF900 || $character < 0x20 || $character > 0x7E && $character < 0xA0 || $character > 0xEFFFD ) ) { throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } $codepoints[] = $character; } return $codepoints; } /** * RFC3492-compliant encoder * * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code * @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`) * * @param string $input UTF-8 encoded string to encode * @return string Punycode-encoded string */ public static function punycode_encode($input) { $output = ''; # let n = initial_n $n = self::BOOTSTRAP_INITIAL_N; # let delta = 0 $delta = 0; # let bias = initial_bias $bias = self::BOOTSTRAP_INITIAL_BIAS; # let h = b = the number of basic code points in the input $h = $b = 0; // see loop # copy them to the output in order $codepoints = self::utf8_to_codepoints($input); $extended = array(); foreach ($codepoints as $char) { if ($char < 128) { // Character is valid ASCII // TODO: this should also check if it's valid for a URL $output .= chr($char); $h++; } // Check if the character is non-ASCII, but below initial n // This never occurs for Punycode, so ignore in coverage // @codeCoverageIgnoreStart elseif ($char < $n) { throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char); } // @codeCoverageIgnoreEnd else { $extended[$char] = true; } } $extended = array_keys($extended); sort($extended); $b = $h; # [copy them] followed by a delimiter if b > 0 if (strlen($output) > 0) { $output .= '-'; } # {if the input contains a non-basic code point < n then fail} # while h < length(input) do begin while ($h < count($codepoints)) { # let m = the minimum code point >= n in the input $m = array_shift($extended); //printf('next code point to insert is %s' . PHP_EOL, dechex($m)); # let delta = delta + (m - n) * (h + 1), fail on overflow $delta += ($m - $n) * ($h + 1); # let n = m $n = $m; # for each code point c in the input (in order) do begin for ($num = 0; $num < count($codepoints); $num++) { $c = $codepoints[$num]; # if c < n then increment delta, fail on overflow if ($c < $n) { $delta++; } # if c == n then begin elseif ($c === $n) { # let q = delta $q = $delta; # for k = base to infinity in steps of base do begin for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) { # let t = tmin if k <= bias {+ tmin}, or # tmax if k >= bias + tmax, or k - bias otherwise if ($k <= ($bias + self::BOOTSTRAP_TMIN)) { $t = self::BOOTSTRAP_TMIN; } elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) { $t = self::BOOTSTRAP_TMAX; } else { $t = $k - $bias; } # if q < t then break if ($q < $t) { break; } # output the code point for digit t + ((q - t) mod (base - t)) $digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t)); $output .= self::digit_to_char($digit); # let q = (q - t) div (base - t) $q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t)); # end } # output the code point for digit q $output .= self::digit_to_char($q); # let bias = adapt(delta, h + 1, test h equals b?) $bias = self::adapt($delta, $h + 1, $h === $b); # let delta = 0 $delta = 0; # increment h $h++; # end } # end } # increment delta and n $delta++; $n++; # end } return $output; } /** * Convert a digit to its respective character * * @see https://tools.ietf.org/html/rfc3492#section-5 * @throws Requests_Exception On invalid digit (`idna.invalid_digit`) * * @param int $digit Digit in the range 0-35 * @return string Single character corresponding to digit */ protected static function digit_to_char($digit) { // @codeCoverageIgnoreStart // As far as I know, this never happens, but still good to be sure. if ($digit < 0 || $digit > 35) { throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit); } // @codeCoverageIgnoreEnd $digits = 'abcdefghijklmnopqrstuvwxyz0123456789'; return substr($digits, $digit, 1); } /** * Adapt the bias * * @see https://tools.ietf.org/html/rfc3492#section-6.1 * @param int $delta * @param int $numpoints * @param bool $firsttime * @return int New bias */ protected static function adapt($delta, $numpoints, $firsttime) { # function adapt(delta,numpoints,firsttime): # if firsttime then let delta = delta div damp if ($firsttime) { $delta = floor($delta / self::BOOTSTRAP_DAMP); } # else let delta = delta div 2 else { $delta = floor($delta / 2); } # let delta = delta + (delta div numpoints) $delta += floor($delta / $numpoints); # let k = 0 $k = 0; # while delta > ((base - tmin) * tmax) div 2 do begin $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2); while ($delta > $max) { # let delta = delta div (base - tmin) $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN)); # let k = k + base $k += self::BOOTSTRAP_BASE; # end } # return k + (((base - tmin + 1) * delta) div (delta + skew)) return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW)); } }proxy = $args; } elseif (is_array($args)) { if (count($args) == 1) { list($this->proxy) = $args; } elseif (count($args) == 3) { list($this->proxy, $this->user, $this->pass) = $args; $this->use_authentication = true; } else { throw new Requests_Exception('Invalid number of arguments', 'proxyhttpbadargs'); } } } /** * Register the necessary callbacks * * @since 1.6 * @see curl_before_send * @see fsockopen_remote_socket * @see fsockopen_remote_host_path * @see fsockopen_header * @param Requests_Hooks $hooks Hook system */ public function register(Requests_Hooks &$hooks) { $hooks->register('curl.before_send', array(&$this, 'curl_before_send')); $hooks->register('fsockopen.remote_socket', array(&$this, 'fsockopen_remote_socket')); $hooks->register('fsockopen.remote_host_path', array(&$this, 'fsockopen_remote_host_path')); if ($this->use_authentication) { $hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header')); } } /** * Set cURL parameters before the data is sent * * @since 1.6 * @param resource $handle cURL resource */ public function curl_before_send(&$handle) { curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); curl_setopt($handle, CURLOPT_PROXY, $this->proxy); if ($this->use_authentication) { curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string()); } } /** * Alter remote socket information before opening socket connection * * @since 1.6 * @param string $remote_socket Socket connection string */ public function fsockopen_remote_socket(&$remote_socket) { $remote_socket = $this->proxy; } /** * Alter remote path before getting stream data * * @since 1.6 * @param string $path Path to send in HTTP request string ("GET ...") * @param string $url Full URL we're requesting */ public function fsockopen_remote_host_path(&$path, $url) { $path = $url; } /** * Add extra headers to the request before sending * * @since 1.6 * @param string $out HTTP header string */ public function fsockopen_header(&$out) { $out .= sprintf("Proxy-Authorization: Basic %s\r\n", base64_encode($this->get_auth_string())); } /** * Get the authentication string (user:pass) * * @since 1.6 * @return string */ public function get_auth_string() { return $this->user . ':' . $this->pass; } }type = $type; $this->data = $data; } /** * Like {@see getCode()}, but a string code. * * @codeCoverageIgnore * @return string */ public function getType() { return $this->type; } /** * Gives any relevant data * * @codeCoverageIgnore * @return mixed */ public function getData() { return $this->data; } } $value) { $this->offsetSet($key, $value); } } /** * Check if the given item exists * * @param string $key Item key * @return boolean Does the item exist? */ public function offsetExists($key) { $key = strtolower($key); return isset($this->data[$key]); } /** * Get the value for the item * * @param string $key Item key * @return string Item value */ public function offsetGet($key) { $key = strtolower($key); if (!isset($this->data[$key])) { return null; } return $this->data[$key]; } /** * Set the given item * * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`) * * @param string $key Item name * @param string $value Item value */ public function offsetSet($key, $value) { if ($key === null) { throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset'); } $key = strtolower($key); $this->data[$key] = $value; } /** * Unset the given header * * @param string $key */ public function offsetUnset($key) { unset($this->data[strtolower($key)]); } /** * Get an iterator for the data * * @return ArrayIterator */ public function getIterator() { return new ArrayIterator($this->data); } /** * Get the headers as an array * * @return array Header data */ public function getAll() { return $this->data; } } callback = $callback; } /** * Get the current item's value after filtering * * @return string */ public function current() { $value = parent::current(); $value = call_user_func($this->callback, $value); return $value; } } FF01:0:0:0:0:0:0:101 * ::1 -> 0:0:0:0:0:0:0:1 * * @author Alexander Merz * @author elfrink at introweb dot nl * @author Josh Peck * @copyright 2003-2005 The PHP Group * @license http://www.opensource.org/licenses/bsd-license.php * @param string $ip An IPv6 address * @return string The uncompressed IPv6 address */ public static function uncompress($ip) { if (substr_count($ip, '::') !== 1) { return $ip; } list($ip1, $ip2) = explode('::', $ip); $c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':'); $c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':'); if (strpos($ip2, '.') !== false) { $c2++; } // :: if ($c1 === -1 && $c2 === -1) { $ip = '0:0:0:0:0:0:0:0'; } // ::xxx else if ($c1 === -1) { $fill = str_repeat('0:', 7 - $c2); $ip = str_replace('::', $fill, $ip); } // xxx:: else if ($c2 === -1) { $fill = str_repeat(':0', 7 - $c1); $ip = str_replace('::', $fill, $ip); } // xxx::xxx else { $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); $ip = str_replace('::', $fill, $ip); } return $ip; } /** * Compresses an IPv6 address * * RFC 4291 allows you to compress consecutive zero pieces in an address to * '::'. This method expects a valid IPv6 address and compresses consecutive * zero pieces to '::'. * * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 * 0:0:0:0:0:0:0:1 -> ::1 * * @see uncompress() * @param string $ip An IPv6 address * @return string The compressed IPv6 address */ public static function compress($ip) { // Prepare the IP to be compressed $ip = self::uncompress($ip); $ip_parts = self::split_v6_v4($ip); // Replace all leading zeros $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); // Find bunches of zeros if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) { $max = 0; $pos = null; foreach ($matches[0] as $match) { if (strlen($match[0]) > $max) { $max = strlen($match[0]); $pos = $match[1]; } } $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); } if ($ip_parts[1] !== '') { return implode(':', $ip_parts); } else { return $ip_parts[0]; } } /** * Splits an IPv6 address into the IPv6 and IPv4 representation parts * * RFC 4291 allows you to represent the last two parts of an IPv6 address * using the standard IPv4 representation * * Example: 0:0:0:0:0:0:13.1.68.3 * 0:0:0:0:0:FFFF:129.144.52.38 * * @param string $ip An IPv6 address * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part */ protected static function split_v6_v4($ip) { if (strpos($ip, '.') !== false) { $pos = strrpos($ip, ':'); $ipv6_part = substr($ip, 0, $pos); $ipv4_part = substr($ip, $pos + 1); return array($ipv6_part, $ipv4_part); } else { return array($ip, ''); } } /** * Checks an IPv6 address * * Checks if the given IP is a valid IPv6 address * * @param string $ip An IPv6 address * @return bool true if $ip is a valid IPv6 address */ public static function check_ipv6($ip) { $ip = self::uncompress($ip); list($ipv6, $ipv4) = self::split_v6_v4($ip); $ipv6 = explode(':', $ipv6); $ipv4 = explode('.', $ipv4); if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) { foreach ($ipv6 as $ipv6_part) { // The section can't be empty if ($ipv6_part === '') { return false; } // Nor can it be over four characters if (strlen($ipv6_part) > 4) { return false; } // Remove leading zeros (this is safe because of the above) $ipv6_part = ltrim($ipv6_part, '0'); if ($ipv6_part === '') { $ipv6_part = '0'; } // Check the value is valid $value = hexdec($ipv6_part); if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) { return false; } } if (count($ipv4) === 4) { foreach ($ipv4 as $ipv4_part) { $value = (int) $ipv4_part; if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) { return false; } } } return true; } else { return false; } } } headers = new Requests_Response_Headers(); $this->cookies = new Requests_Cookie_Jar(); } /** * Response body * * @var string */ public $body = ''; /** * Raw HTTP data from the transport * * @var string */ public $raw = ''; /** * Headers, as an associative array * * @var Requests_Response_Headers Array-like object representing headers */ public $headers = array(); /** * Status code, false if non-blocking * * @var integer|boolean */ public $status_code = false; /** * Protocol version, false if non-blocking * @var float|boolean */ public $protocol_version = false; /** * Whether the request succeeded or not * * @var boolean */ public $success = false; /** * Number of redirects the request used * * @var integer */ public $redirects = 0; /** * URL requested * * @var string */ public $url = ''; /** * Previous requests (from redirects) * * @var array Array of Requests_Response objects */ public $history = array(); /** * Cookies from the request * * @var Requests_Cookie_Jar Array-like object representing a cookie jar */ public $cookies = array(); /** * Is the response a redirect? * * @return boolean True if redirect (3xx status), false if not. */ public function is_redirect() { $code = $this->status_code; return in_array($code, array(300, 301, 302, 303, 307)) || $code > 307 && $code < 400; } /** * Throws an exception if the request was not successful * * @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`) * @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404}) * @param boolean $allow_redirects Set to false to throw on a 3xx as well */ public function throw_for_status($allow_redirects = true) { if ($this->is_redirect()) { if (!$allow_redirects) { throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this); } } elseif (!$this->success) { $exception = Requests_Exception_HTTP::get_class($this->status_code); throw new $exception(null, $this); } } } type = $type; } if ($code !== null) { $this->code = $code; } if ($message !== null) { $this->reason = $message; } $message = sprintf('%d %s', $this->code, $this->reason); parent::__construct($message, $this->type, $data, $this->code); } /** * Get the error message */ public function getReason() { return $this->reason; } } code = $data->status_code; } parent::__construct($reason, $data); } }reason = $reason; } $message = sprintf('%d %s', $this->code, $this->reason); parent::__construct($message, 'httpresponse', $data, $this->code); } /** * Get the status message */ public function getReason() { return $this->reason; } /** * Get the correct exception class for a given error code * * @param int|bool $code HTTP status code, or false if unavailable * @return string Exception class name to use */ public static function get_class($code) { if (!$code) { return 'Requests_Exception_HTTP_Unknown'; } $class = sprintf('Requests_Exception_HTTP_%d', $code); if (class_exists($class)) { return $class; } return 'Requests_Exception_HTTP_Unknown'; } }dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options)); if (!empty($options['transport'])) { $transport = $options['transport']; if (is_string($options['transport'])) { $transport = new $transport(); } } else { $need_ssl = (0 === stripos($url, 'https://')); $capabilities = array('ssl' => $need_ssl); $transport = self::get_transport($capabilities); } $response = $transport->request($url, $headers, $data, $options); $options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options)); return self::parse_response($response, $url, $headers, $data, $options); } /** * Send multiple HTTP requests simultaneously * * The `$requests` parameter takes an associative or indexed array of * request fields. The key of each request can be used to match up the * request with the returned data, or with the request passed into your * `multiple.request.complete` callback. * * The request fields value is an associative array with the following keys: * * - `url`: Request URL Same as the `$url` parameter to * {@see Requests::request} * (string, required) * - `headers`: Associative array of header fields. Same as the `$headers` * parameter to {@see Requests::request} * (array, default: `array()`) * - `data`: Associative array of data fields or a string. Same as the * `$data` parameter to {@see Requests::request} * (array|string, default: `array()`) * - `type`: HTTP request type (use Requests constants). Same as the `$type` * parameter to {@see Requests::request} * (string, default: `Requests::GET`) * - `cookies`: Associative array of cookie name to value, or cookie jar. * (array|Requests_Cookie_Jar) * * If the `$options` parameter is specified, individual requests will * inherit options from it. This can be used to use a single hooking system, * or set all the types to `Requests::POST`, for example. * * In addition, the `$options` parameter takes the following global options: * * - `complete`: A callback for when a request is complete. Takes two * parameters, a Requests_Response/Requests_Exception reference, and the * ID from the request array (Note: this can also be overridden on a * per-request basis, although that's a little silly) * (callback) * * @param array $requests Requests data (see description for more information) * @param array $options Global and default options (see {@see Requests::request}) * @return array Responses (either Requests_Response or a Requests_Exception object) */ public static function request_multiple($requests, $options = array()) { $options = array_merge(self::get_default_options(true), $options); if (!empty($options['hooks'])) { $options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple')); if (!empty($options['complete'])) { $options['hooks']->register('multiple.request.complete', $options['complete']); } } foreach ($requests as $id => &$request) { if (!isset($request['headers'])) { $request['headers'] = array(); } if (!isset($request['data'])) { $request['data'] = array(); } if (!isset($request['type'])) { $request['type'] = self::GET; } if (!isset($request['options'])) { $request['options'] = $options; $request['options']['type'] = $request['type']; } else { if (empty($request['options']['type'])) { $request['options']['type'] = $request['type']; } $request['options'] = array_merge($options, $request['options']); } self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']); // Ensure we only hook in once if ($request['options']['hooks'] !== $options['hooks']) { $request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple')); if (!empty($request['options']['complete'])) { $request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']); } } } unset($request); if (!empty($options['transport'])) { $transport = $options['transport']; if (is_string($options['transport'])) { $transport = new $transport(); } } else { $transport = self::get_transport(); } $responses = $transport->request_multiple($requests, $options); foreach ($responses as $id => &$response) { // If our hook got messed with somehow, ensure we end up with the // correct response if (is_string($response)) { $request = $requests[$id]; self::parse_multiple($response, $request); $request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id)); } } return $responses; } /** * Get the default options * * @see Requests::request() for values returned by this method * @param boolean $multirequest Is this a multirequest? * @return array Default option values */ protected static function get_default_options($multirequest = false) { $defaults = array( 'timeout' => 10, 'connect_timeout' => 10, 'useragent' => 'php-requests/' . self::VERSION, 'protocol_version' => 1.1, 'redirected' => 0, 'redirects' => 10, 'follow_redirects' => true, 'blocking' => true, 'type' => self::GET, 'filename' => false, 'auth' => false, 'proxy' => false, 'cookies' => false, 'max_bytes' => false, 'idn' => true, 'hooks' => null, 'transport' => null, 'verify' => Requests::get_certificate_path(), 'verifyname' => true, ); if ($multirequest !== false) { $defaults['complete'] = null; } return $defaults; } /** * Get default certificate path. * * @return string Default certificate path. */ public static function get_certificate_path() { if ( ! empty( Requests::$certificate_path ) ) { return Requests::$certificate_path; } return dirname(__FILE__) . '/Requests/Transport/cacert.pem'; } /** * Set default certificate path. * * @param string $path Certificate path, pointing to a PEM file. */ public static function set_certificate_path( $path ) { Requests::$certificate_path = $path; } /** * Set the default values * * @param string $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type * @param array $options Options for the request * @return array $options */ protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) { if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) { throw new Requests_Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url); } if (empty($options['hooks'])) { $options['hooks'] = new Requests_Hooks(); } if (is_array($options['auth'])) { $options['auth'] = new Requests_Auth_Basic($options['auth']); } if ($options['auth'] !== false) { $options['auth']->register($options['hooks']); } if (is_string($options['proxy']) || is_array($options['proxy'])) { $options['proxy'] = new Requests_Proxy_HTTP($options['proxy']); } if ($options['proxy'] !== false) { $options['proxy']->register($options['hooks']); } if (is_array($options['cookies'])) { $options['cookies'] = new Requests_Cookie_Jar($options['cookies']); } elseif (empty($options['cookies'])) { $options['cookies'] = new Requests_Cookie_Jar(); } if ($options['cookies'] !== false) { $options['cookies']->register($options['hooks']); } if ($options['idn'] !== false) { $iri = new Requests_IRI($url); $iri->host = Requests_IDNAEncoder::encode($iri->ihost); $url = $iri->uri; } // Massage the type to ensure we support it. $type = strtoupper($type); if (!isset($options['data_format'])) { if (in_array($type, array(self::HEAD, self::GET, self::DELETE))) { $options['data_format'] = 'query'; } else { $options['data_format'] = 'body'; } } } /** * HTTP response parser * * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`) * @throws Requests_Exception On missing head/body separator (`noversion`) * @throws Requests_Exception On missing head/body separator (`toomanyredirects`) * * @param string $headers Full response text including headers and body * @param string $url Original request URL * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects * @return Requests_Response */ protected static function parse_response($headers, $url, $req_headers, $req_data, $options) { $return = new Requests_Response(); if (!$options['blocking']) { return $return; } $return->raw = $headers; $return->url = $url; if (!$options['filename']) { if (($pos = strpos($headers, "\r\n\r\n")) === false) { // Crap! throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator'); } $headers = substr($return->raw, 0, $pos); $return->body = substr($return->raw, $pos + strlen("\n\r\n\r")); } else { $return->body = ''; } // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3) $headers = str_replace("\r\n", "\n", $headers); // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2) $headers = preg_replace('/\n[ \t]/', ' ', $headers); $headers = explode("\n", $headers); preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches); if (empty($matches)) { throw new Requests_Exception('Response could not be parsed', 'noversion', $headers); } $return->protocol_version = (float) $matches[1]; $return->status_code = (int) $matches[2]; if ($return->status_code >= 200 && $return->status_code < 300) { $return->success = true; } foreach ($headers as $header) { list($key, $value) = explode(':', $header, 2); $value = trim($value); preg_replace('#(\s+)#i', ' ', $value); $return->headers[$key] = $value; } if (isset($return->headers['transfer-encoding'])) { $return->body = self::decode_chunked($return->body); unset($return->headers['transfer-encoding']); } if (isset($return->headers['content-encoding'])) { $return->body = self::decompress($return->body); } //fsockopen and cURL compatibility if (isset($return->headers['connection'])) { unset($return->headers['connection']); } $options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options)); if ($return->is_redirect() && $options['follow_redirects'] === true) { if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) { if ($return->status_code === 303) { $options['type'] = self::GET; } $options['redirected']++; $location = $return->headers['location']; if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) { // relative redirect, for compatibility make it absolute $location = Requests_IRI::absolutize($url, $location); $location = $location->uri; } $hook_args = array( &$location, &$req_headers, &$req_data, &$options, $return ); $options['hooks']->dispatch('requests.before_redirect', $hook_args); $redirected = self::request($location, $req_headers, $req_data, $options['type'], $options); $redirected->history[] = $return; return $redirected; } elseif ($options['redirected'] >= $options['redirects']) { throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return); } } $return->redirects = $options['redirected']; $options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options)); return $return; } /** * Callback for `transport.internal.parse_response` * * Internal use only. Converts a raw HTTP response to a Requests_Response * while still executing a multiple request. * * @param string $response Full response text including headers and body (will be overwritten with Response instance) * @param array $request Request data as passed into {@see Requests::request_multiple()} * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object */ public static function parse_multiple(&$response, $request) { try { $url = $request['url']; $headers = $request['headers']; $data = $request['data']; $options = $request['options']; $response = self::parse_response($response, $url, $headers, $data, $options); } catch (Requests_Exception $e) { $response = $e; } } /** * Decoded a chunked body as per RFC 2616 * * @see https://tools.ietf.org/html/rfc2616#section-3.6.1 * @param string $data Chunked body * @return string Decoded body */ protected static function decode_chunked($data) { if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { return $data; } $decoded = ''; $encoded = $data; while (true) { $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all return $data; } $length = hexdec(trim($matches[1])); if ($length === 0) { // Ignore trailer headers return $decoded; } $chunk_length = strlen($matches[0]); $decoded .= substr($encoded, $chunk_length, $length); $encoded = substr($encoded, $chunk_length + $length + 2); if (trim($encoded) === '0' || empty($encoded)) { return $decoded; } } // We'll never actually get down here // @codeCoverageIgnoreStart } // @codeCoverageIgnoreEnd /** * Convert a key => value array to a 'key: value' array for headers * * @param array $array Dictionary of header values * @return array List of headers */ public static function flatten($array) { $return = array(); foreach ($array as $key => $value) { $return[] = sprintf('%s: %s', $key, $value); } return $return; } /** * Convert a key => value array to a 'key: value' array for headers * * @codeCoverageIgnore * @deprecated Misspelling of {@see Requests::flatten} * @param array $array Dictionary of header values * @return array List of headers */ public static function flattern($array) { return self::flatten($array); } /** * Decompress an encoded body * * Implements gzip, compress and deflate. Guesses which it is by attempting * to decode. * * @param string $data Compressed data in one of the above formats * @return string Decompressed string */ public static function decompress($data) { if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") { // Not actually compressed. Probably cURL ruining this for us. return $data; } if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) { return $decoded; } elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) { return $decoded; } elseif (($decoded = self::compatible_gzinflate($data)) !== false) { return $decoded; } elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) { return $decoded; } return $data; } /** * Decompression of deflated string while staying compatible with the majority of servers. * * Certain Servers will return deflated data with headers which PHP's gzinflate() * function cannot handle out of the box. The following function has been created from * various snippets on the gzinflate() PHP documentation. * * Warning: Magic numbers within. Due to the potential different formats that the compressed * data may be returned in, some "magic offsets" are needed to ensure proper decompression * takes place. For a simple progmatic way to determine the magic offset in use, see: * https://core.trac.wordpress.org/ticket/18273 * * @since 2.8.1 * @link https://core.trac.wordpress.org/ticket/18273 * @link https://secure.php.net/manual/en/function.gzinflate.php#70875 * @link https://secure.php.net/manual/en/function.gzinflate.php#77336 * * @param string $gzData String to decompress. * @return string|bool False on failure. */ public static function compatible_gzinflate($gzData) { // Compressed data might contain a full zlib header, if so strip it for // gzinflate() if (substr($gzData, 0, 3) == "\x1f\x8b\x08") { $i = 10; $flg = ord(substr($gzData, 3, 1)); if ($flg > 0) { if ($flg & 4) { list($xlen) = unpack('v', substr($gzData, $i, 2)); $i = $i + 2 + $xlen; } if ($flg & 8) { $i = strpos($gzData, "\0", $i) + 1; } if ($flg & 16) { $i = strpos($gzData, "\0", $i) + 1; } if ($flg & 2) { $i = $i + 2; } } $decompressed = self::compatible_gzinflate(substr($gzData, $i)); if (false !== $decompressed) { return $decompressed; } } // If the data is Huffman Encoded, we must first strip the leading 2 // byte Huffman marker for gzinflate() // The response is Huffman coded by many compressors such as // java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's // System.IO.Compression.DeflateStream. // // See https://decompres.blogspot.com/ for a quick explanation of this // data type $huffman_encoded = false; // low nibble of first byte should be 0x08 list(, $first_nibble) = unpack('h', $gzData); // First 2 bytes should be divisible by 0x1F list(, $first_two_bytes) = unpack('n', $gzData); if (0x08 == $first_nibble && 0 == ($first_two_bytes % 0x1F)) { $huffman_encoded = true; } if ($huffman_encoded) { if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) { return $decompressed; } } if ("\x50\x4b\x03\x04" == substr($gzData, 0, 4)) { // ZIP file format header // Offset 6: 2 bytes, General-purpose field // Offset 26: 2 bytes, filename length // Offset 28: 2 bytes, optional field length // Offset 30: Filename field, followed by optional field, followed // immediately by data list(, $general_purpose_flag) = unpack('v', substr($gzData, 6, 2)); // If the file has been compressed on the fly, 0x08 bit is set of // the general purpose field. We can use this to differentiate // between a compressed document, and a ZIP file $zip_compressed_on_the_fly = (0x08 == (0x08 & $general_purpose_flag)); if (!$zip_compressed_on_the_fly) { // Don't attempt to decode a compressed zip file return $gzData; } // Determine the first byte of data, based on the above ZIP header // offsets: $first_file_start = array_sum(unpack('v2', substr($gzData, 26, 4))); if (false !== ($decompressed = @gzinflate(substr($gzData, 30 + $first_file_start)))) { return $decompressed; } return false; } // Finally fall back to straight gzinflate if (false !== ($decompressed = @gzinflate($gzData))) { return $decompressed; } // Fallback for all above failing, not expected, but included for // debugging and preventing regressions and to track stats if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) { return $decompressed; } return false; } public static function match_domain($host, $reference) { // Check for a direct match if ($host === $reference) { return true; } // Calculate the valid wildcard match if the host is not an IP address // Also validates that the host has 3 parts or more, as per Firefox's // ruleset. $parts = explode('.', $host); if (ip2long($host) === false && count($parts) >= 3) { $parts[0] = '*'; $wildcard = implode('.', $parts); if ($wildcard === $reference) { return true; } } return false; } } ' . PHP_EOL; echo PHP_EOL; echo ' version:' . PHP_EOL; echo ' Version of the package, in the form of major.minor.bug' . PHP_EOL; echo PHP_EOL; echo ' stability:' . PHP_EOL; echo ' One of alpha, beta, stable' . PHP_EOL; die(); } if (!isset($argv[2])) { die('You must provide the stability (alpha, beta, or stable)'); } $context = array( 'date' => gmdate('Y-m-d'), 'time' => gmdate('H:m:00'), 'version' => $argv[1], 'api_version' => $argv[1], 'stability' => $argv[2], 'api_stability' => $argv[2], ); $context['files'] = ''; $path = realpath(dirname(__FILE__).'/../library/Requests'); foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { if (preg_match('/\.php$/', $file)) { $name = str_replace($path . DIRECTORY_SEPARATOR, '', $file); $name = str_replace(DIRECTORY_SEPARATOR, '/', $name); $context['files'][] = "\t\t\t\t\t" . ''; } } $context['files'] = implode("\n", $context['files']); $template = file_get_contents(dirname(__FILE__).'/../package.xml.tpl'); $content = preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', 'replace_parameters', $template); file_put_contents(dirname(__FILE__).'/../package.xml', $content); function replace_parameters($matches) { global $context; return isset($context[$matches[1]]) ? $context[$matches[1]] : null; } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\CaBundle; use Psr\Log\LoggerInterface; use Symfony\Component\Process\PhpProcess; /** * @author Chris Smith * @author Jordi Boggiano */ class CaBundle { private static $caPath; private static $caFileValidity = array(); private static $useOpensslParse; /** * Returns the system CA bundle path, or a path to the bundled one * * This method was adapted from Sslurp. * https://github.com/EvanDotPro/Sslurp * * (c) Evan Coury * * For the full copyright and license information, please see below: * * Copyright (c) 2013, Evan Coury * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @param LoggerInterface $logger optional logger for information about which CA files were loaded * @return string path to a CA bundle file or directory */ public static function getSystemCaRootBundlePath(LoggerInterface $logger = null) { if (self::$caPath !== null) { return self::$caPath; } // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. $envCertFile = getenv('SSL_CERT_FILE'); if ($envCertFile && is_readable($envCertFile) && static::validateCaFile($envCertFile, $logger)) { return self::$caPath = $envCertFile; } // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. $envCertDir = getenv('SSL_CERT_DIR'); if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) { return self::$caPath = $envCertDir; } $configured = ini_get('openssl.cafile'); if ($configured && strlen($configured) > 0 && is_readable($configured) && static::validateCaFile($configured, $logger)) { return self::$caPath = $configured; } $configured = ini_get('openssl.capath'); if ($configured && is_dir($configured) && is_readable($configured)) { return self::$caPath = $configured; } $caBundlePaths = array( '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) '/usr/ssl/certs/ca-bundle.crt', // Cygwin '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? '/etc/ssl/cert.pem', // OpenBSD '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package ); foreach ($caBundlePaths as $caBundle) { if (@is_readable($caBundle) && static::validateCaFile($caBundle, $logger)) { return self::$caPath = $caBundle; } } foreach ($caBundlePaths as $caBundle) { $caBundle = dirname($caBundle); if (@is_dir($caBundle) && glob($caBundle.'/*')) { return self::$caPath = $caBundle; } } return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort } /** * Returns the path to the bundled CA file * * In case you don't want to trust the user or the system, you can use this directly * * @return string path to a CA bundle file */ public static function getBundledCaBundlePath() { return __DIR__.'/../res/cacert.pem'; } /** * Validates a CA file using opensl_x509_parse only if it is safe to use * * @param string $filename * @param LoggerInterface $logger optional logger for information about which CA files were loaded * * @return bool */ public static function validateCaFile($filename, LoggerInterface $logger = null) { static $warned = false; if (isset(self::$caFileValidity[$filename])) { return self::$caFileValidity[$filename]; } $contents = file_get_contents($filename); // assume the CA is valid if php is vulnerable to // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html if (!static::isOpensslParseSafe()) { if (!$warned && $logger) { $logger->warning(sprintf( 'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.', PHP_VERSION )); $warned = true; } $isValid = !empty($contents); } else { $isValid = (bool) openssl_x509_parse($contents); } if ($logger) { $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid')); } return self::$caFileValidity[$filename] = $isValid; } /** * Test if it is safe to use the PHP function openssl_x509_parse(). * * This checks if OpenSSL extensions is vulnerable to remote code execution * via the exploit documented as CVE-2013-6420. * * @return bool */ public static function isOpensslParseSafe() { if (null !== self::$useOpensslParse) { return self::$useOpensslParse; } if (PHP_VERSION_ID >= 50600) { return self::$useOpensslParse = true; } // Vulnerable: // PHP 5.3.0 - PHP 5.3.27 // PHP 5.4.0 - PHP 5.4.22 // PHP 5.5.0 - PHP 5.5.6 if ( (PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328) || (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423) || (PHP_VERSION_ID < 50600 && PHP_VERSION_ID >= 50507) ) { // This version of PHP has the fix for CVE-2013-6420 applied. return self::$useOpensslParse = true; } if (defined('PHP_WINDOWS_VERSION_BUILD')) { // Windows is probably insecure in this case. return self::$useOpensslParse = false; } $compareDistroVersionPrefix = function ($prefix, $fixedVersion) { $regex = '{^'.preg_quote($prefix).'([0-9]+)$}'; if (preg_match($regex, PHP_VERSION, $m)) { return ((int) $m[1]) >= $fixedVersion; } return false; }; // Hard coded list of PHP distributions with the fix backported. if ( $compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze) || $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy) || $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise) ) { return self::$useOpensslParse = true; } // Symfony Process component is missing so we assume it is unsafe at this point if (!class_exists('Symfony\Component\Process\PhpProcess')) { return self::$useOpensslParse = false; } // This is where things get crazy, because distros backport security // fixes the chances are on NIX systems the fix has been applied but // it's not possible to verify that from the PHP version. // // To verify exec a new PHP process and run the issue testcase with // known safe input that replicates the bug. // Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415 // changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593 $cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K'; $script = <<<'EOT' error_reporting(-1); $info = openssl_x509_parse(base64_decode('%s')); var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']); EOT; $script = '<'."?php\n".sprintf($script, $cert); try { $process = new PhpProcess($script); $process->mustRun(); } catch (\Exception $e) { // In the case of any exceptions just accept it is not possible to // determine the safety of openssl_x509_parse and bail out. return self::$useOpensslParse = false; } $output = preg_split('{\r?\n}', trim($process->getOutput())); $errorOutput = trim($process->getErrorOutput()); if ( count($output) === 3 && $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION) && $output[1] === 'string(27) "stefan.esser@sektioneins.de"' && $output[2] === 'int(-1)' && preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput) ) { // This PHP has the fix backported probably by a distro security team. return self::$useOpensslParse = true; } return self::$useOpensslParse = false; } /** * Resets the static caches */ public static function reset() { self::$caFileValidity = array(); self::$caPath = null; self::$useOpensslParse = null; } } $baseDir . '/php/commands/src/CLI_Command.php', 'Cache_Command' => $vendorDir . '/wp-cli/cache-command/src/Cache_Command.php', 'Capabilities_Command' => $vendorDir . '/wp-cli/role-command/src/Capabilities_Command.php', 'Checksum_Command' => $vendorDir . '/wp-cli/checksum-command/src/Checksum_Command.php', 'Comment_Command' => $vendorDir . '/wp-cli/entity-command/src/Comment_Command.php', 'Comment_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Comment_Meta_Command.php', 'Config_Command' => $vendorDir . '/wp-cli/config-command/src/Config_Command.php', 'Core_Command' => $vendorDir . '/wp-cli/core-command/src/Core_Command.php', 'Core_Language_Command' => $vendorDir . '/wp-cli/language-command/src/Core_Language_Command.php', 'Cron_Command' => $vendorDir . '/wp-cli/cron-command/src/Cron_Command.php', 'Cron_Event_Command' => $vendorDir . '/wp-cli/cron-command/src/Cron_Event_Command.php', 'Cron_Schedule_Command' => $vendorDir . '/wp-cli/cron-command/src/Cron_Schedule_Command.php', 'DB_Command' => $vendorDir . '/wp-cli/db-command/src/DB_Command.php', 'EvalFile_Command' => $vendorDir . '/wp-cli/eval-command/src/EvalFile_Command.php', 'Eval_Command' => $vendorDir . '/wp-cli/eval-command/src/Eval_Command.php', 'Export_Command' => $vendorDir . '/wp-cli/export-command/src/Export_Command.php', 'Import_Command' => $vendorDir . '/wp-cli/import-command/src/Import_Command.php', 'Media_Command' => $vendorDir . '/wp-cli/media-command/src/Media_Command.php', 'Menu_Command' => $vendorDir . '/wp-cli/entity-command/src/Menu_Command.php', 'Menu_Item_Command' => $vendorDir . '/wp-cli/entity-command/src/Menu_Item_Command.php', 'Menu_Location_Command' => $vendorDir . '/wp-cli/entity-command/src/Menu_Location_Command.php', 'Network_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Network_Meta_Command.php', 'Option_Command' => $vendorDir . '/wp-cli/entity-command/src/Option_Command.php', 'Package_Command' => $vendorDir . '/wp-cli/package-command/src/Package_Command.php', 'Plugin_Command' => $vendorDir . '/wp-cli/extension-command/src/Plugin_Command.php', 'Post_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Command.php', 'Post_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Meta_Command.php', 'Post_Term_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Term_Command.php', 'Post_Type_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Type_Command.php', 'Rewrite_Command' => $vendorDir . '/wp-cli/rewrite-command/src/Rewrite_Command.php', 'Role_Command' => $vendorDir . '/wp-cli/role-command/src/Role_Command.php', 'Scaffold_Command' => $vendorDir . '/wp-cli/scaffold-command/src/Scaffold_Command.php', 'Search_Replace_Command' => $vendorDir . '/wp-cli/search-replace-command/src/Search_Replace_Command.php', 'Server_Command' => $vendorDir . '/wp-cli/server-command/src/Server_Command.php', 'Shell_Command' => $vendorDir . '/wp-cli/shell-command/src/Shell_Command.php', 'Sidebar_Command' => $vendorDir . '/wp-cli/widget-command/src/Sidebar_Command.php', 'Site_Command' => $vendorDir . '/wp-cli/entity-command/src/Site_Command.php', 'Site_Option_Command' => $vendorDir . '/wp-cli/entity-command/src/Site_Option_Command.php', 'Super_Admin_Command' => $vendorDir . '/wp-cli/super-admin-command/src/Super_Admin_Command.php', 'Taxonomy_Command' => $vendorDir . '/wp-cli/entity-command/src/Taxonomy_Command.php', 'Term_Command' => $vendorDir . '/wp-cli/entity-command/src/Term_Command.php', 'Term_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Term_Meta_Command.php', 'Theme_Command' => $vendorDir . '/wp-cli/extension-command/src/Theme_Command.php', 'Theme_Mod_Command' => $vendorDir . '/wp-cli/extension-command/src/Theme_Mod_Command.php', 'Transient_Command' => $vendorDir . '/wp-cli/cache-command/src/Transient_Command.php', 'User_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Command.php', 'User_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Meta_Command.php', 'User_Session_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Session_Command.php', 'User_Term_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Term_Command.php', 'WP_CLI\\CommandWithDBObject' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/CommandWithDBObject.php', 'WP_CLI\\CommandWithMeta' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/CommandWithMeta.php', 'WP_CLI\\CommandWithTerms' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/CommandWithTerms.php', 'WP_CLI\\CommandWithTranslation' => $vendorDir . '/wp-cli/language-command/src/WP_CLI/CommandWithTranslation.php', 'WP_CLI\\CommandWithUpgrade' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/CommandWithUpgrade.php', 'WP_CLI\\CoreUpgrader' => $vendorDir . '/wp-cli/core-command/src/WP_CLI/CoreUpgrader.php', 'WP_CLI\\DestructivePluginUpgrader' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/DestructivePluginUpgrader.php', 'WP_CLI\\DestructiveThemeUpgrader' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/DestructiveThemeUpgrader.php', 'WP_CLI\\Entity\\NonExistentKeyException' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/Entity/NonExistentKeyException.php', 'WP_CLI\\Entity\\RecursiveDataStructureTraverser' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/Entity/RecursiveDataStructureTraverser.php', 'WP_CLI\\Entity\\Utils' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/Entity/Utils.php', 'WP_CLI\\Fetchers\\Comment' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/Fetchers/Comment.php', 'WP_CLI\\Fetchers\\Plugin' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/Fetchers/Plugin.php', 'WP_CLI\\Fetchers\\Post' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/Fetchers/Post.php', 'WP_CLI\\Fetchers\\Site' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/Fetchers/Site.php', 'WP_CLI\\Fetchers\\Theme' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/Fetchers/Theme.php', 'WP_CLI\\Fetchers\\User' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/Fetchers/User.php', 'WP_CLI\\Inflector' => $vendorDir . '/wp-cli/scaffold-command/src/WP_CLI/Inflector.php', 'WP_CLI\\LanguagePackUpgrader' => $vendorDir . '/wp-cli/language-command/src/WP_CLI/LanguagePackUpgrader.php', 'WP_CLI\\NonDestructiveCoreUpgrader' => $vendorDir . '/wp-cli/core-command/src/WP_CLI/NonDestructiveCoreUpgrader.php', 'WP_CLI\\REPL' => $vendorDir . '/wp-cli/shell-command/src/WP_CLI/REPL.php', 'WP_CLI\\SearchReplacer' => $vendorDir . '/wp-cli/search-replace-command/src/WP_CLI/SearchReplacer.php', 'WP_Export_Base_Writer' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Base_Writer.php', 'WP_Export_Exception' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Exception.php', 'WP_Export_File_Writer' => $vendorDir . '/wp-cli/export-command/src/WP_Export_File_Writer.php', 'WP_Export_Oxymel' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Oxymel.php', 'WP_Export_Query' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Query.php', 'WP_Export_Returner' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Returner.php', 'WP_Export_Split_Files_Writer' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Split_Files_Writer.php', 'WP_Export_Stdout_Writer' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Stdout_Writer.php', 'WP_Export_Term_Exception' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Term_Exception.php', 'WP_Export_WXR_Formatter' => $vendorDir . '/wp-cli/export-command/src/WP_Export_WXR_Formatter.php', 'WP_Export_XML_Over_HTTP' => $vendorDir . '/wp-cli/export-command/src/WP_Export_XML_Over_HTTP.php', 'WP_Iterator_Exception' => $vendorDir . '/wp-cli/export-command/src/WP_Iterator_Exception.php', 'WP_Map_Iterator' => $vendorDir . '/wp-cli/export-command/src/WP_Map_Iterator.php', 'WP_Post_IDs_Iterator' => $vendorDir . '/wp-cli/export-command/src/WP_Post_IDs_Iterator.php', 'Widget_Command' => $vendorDir . '/wp-cli/widget-command/src/Widget_Command.php', ); __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '8cd2fca4db21bffce1ad0612f7caeec4' => __DIR__ . '/..' . '/ramsey/array_column/src/array_column.php', 'b66d29757fcb2fb7a9608d068e3716b0' => __DIR__ . '/..' . '/wp-cli/checksum-command/checksum-command.php', '5deaf6ce9c8bbdfb65104c7e938d5875' => __DIR__ . '/..' . '/wp-cli/config-command/config-command.php', '7654e00bf0e632580764400bd8293a9c' => __DIR__ . '/..' . '/wp-cli/cron-command/cron-command.php', 'f958dca3f412fd7975da1700912a9321' => __DIR__ . '/..' . '/wp-cli/eval-command/eval-command.php', '5c6ec5cff8f9d625772c8ed147f6b894' => __DIR__ . '/..' . '/wp-cli/export-command/export-command.php', '30cbb6e4122dc988e494c6b9c0438233' => __DIR__ . '/..' . '/wp-cli/import-command/import-command.php', 'ace0d205db7f4135ec32132a0076d555' => __DIR__ . '/..' . '/wp-cli/language-command/language-command.php', '3937806105cc8e221b8fa8db5b70d2f2' => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/includes/functions.php', 'f399c1c8d0c787d5c94c09884cdd9762' => __DIR__ . '/..' . '/wp-cli/rewrite-command/rewrite-command.php', '080fadd667195d055c5a23386f270261' => __DIR__ . '/..' . '/wp-cli/role-command/role-command.php', '9f04dd0aa5d67ec75a75c88c345a079e' => __DIR__ . '/..' . '/wp-cli/server-command/server-command.php', '129d58fa8151374aceb8571bcaa97504' => __DIR__ . '/..' . '/wp-cli/shell-command/shell-command.php', '8519779bbb65eeb842af2f629ce7b6f8' => __DIR__ . '/..' . '/wp-cli/super-admin-command/super-admin-command.php', '1f05372afcc7d0c51a305cef1d56dd01' => __DIR__ . '/..' . '/wp-cli/widget-command/widget-command.php', '8a0ad02df6a5087f2c380f8fd52db273' => __DIR__ . '/..' . '/wp-cli/cache-command/cache-command.php', '5e099d3cac677dd2bec1003ea7707745' => __DIR__ . '/..' . '/wp-cli/media-command/media-command.php', 'be01b9b16925dcb22165c40b46681ac6' => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib/cli/cli.php', '68c39b88215b6cf7a0da164166670ef9' => __DIR__ . '/..' . '/wp-cli/core-command/core-command.php', 'f3f0199a3ecd9f501d0a3b361bd2f61c' => __DIR__ . '/..' . '/wp-cli/entity-command/entity-command.php', 'ba366f96f4fddbdef61ad7a862b44f61' => __DIR__ . '/..' . '/wp-cli/package-command/package-command.php', 'c65f753375faee349b7adc48c2ee7cc2' => __DIR__ . '/..' . '/wp-cli/db-command/db-command.php', '3f201033d5aceb2293314273be88f7c6' => __DIR__ . '/..' . '/wp-cli/extension-command/extension-command.php', 'd979c11fe80ba96ae3037b43429fe546' => __DIR__ . '/..' . '/wp-cli/scaffold-command/scaffold-command.php', '8ecb13f8bbc22b1b34d12b14ec01077a' => __DIR__ . '/..' . '/wp-cli/search-replace-command/search-replace-command.php', ); public static $prefixLengthsPsr4 = array ( 'W' => array ( 'WP_CLI\\AutoloadSplitter\\' => 24, 'WP_CLI\\' => 7, ), 'S' => array ( 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Component\\Yaml\\' => 23, 'Symfony\\Component\\Translation\\' => 30, 'Symfony\\Component\\Process\\' => 26, 'Symfony\\Component\\Finder\\' => 25, 'Symfony\\Component\\Filesystem\\' => 29, 'Symfony\\Component\\EventDispatcher\\' => 34, 'Symfony\\Component\\DependencyInjection\\' => 38, 'Symfony\\Component\\Debug\\' => 24, 'Symfony\\Component\\Console\\' => 26, 'Symfony\\Component\\Config\\' => 25, 'Seld\\PharUtils\\' => 15, 'Seld\\JsonLint\\' => 14, 'Seld\\CliPrompt\\' => 15, ), 'P' => array ( 'Psr\\Log\\' => 8, 'PHPCompatibility\\' => 17, ), 'M' => array ( 'Mustangostang\\' => 14, ), 'J' => array ( 'JsonSchema\\' => 11, ), 'D' => array ( 'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 55, ), 'C' => array ( 'Composer\\Spdx\\' => 14, 'Composer\\Semver\\' => 16, 'Composer\\CaBundle\\' => 18, 'Composer\\' => 9, ), ); public static $prefixDirsPsr4 = array ( 'WP_CLI\\AutoloadSplitter\\' => array ( 0 => __DIR__ . '/..' . '/wp-cli/autoload-splitter/src', ), 'WP_CLI\\' => array ( 0 => __DIR__ . '/..' . '/wp-cli/shell-command/src/WP_CLI', 1 => __DIR__ . '/..' . '/wp-cli/entity-command/src/WP_CLI', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Component\\Yaml\\' => array ( ), 'Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), 'Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', ), 'Symfony\\Component\\Filesystem\\' => array ( 0 => __DIR__ . '/..' . '/symfony/filesystem', ), 'Symfony\\Component\\EventDispatcher\\' => array ( ), 'Symfony\\Component\\Debug\\' => array ( ), 'Symfony\\Component\\Config\\' => array ( ), 'Seld\\JsonLint\\' => array ( 0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint', ), 'Seld\\CliPrompt\\' => array ( 0 => __DIR__ . '/..' . '/seld/cli-prompt/src', ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), 'PHPCompatibility\\' => array ( ), 'Mustangostang\\' => array ( 0 => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/src', ), 'JsonSchema\\' => array ( 0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema', ), 'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => array ( ), 'Composer\\Spdx\\' => array ( ), 'Composer\\Semver\\' => array ( 0 => __DIR__ . '/..' . '/composer/semver/src', ), 'Composer\\CaBundle\\' => array ( 0 => __DIR__ . '/..' . '/composer/ca-bundle/src', ), 'Composer\\' => array ( 0 => __DIR__ . '/..' . '/composer/composer/src/Composer', ), ); public static $fallbackDirsPsr4 = array ( 0 => __DIR__ . '/../..' . '/php/commands/src', 1 => __DIR__ . '/..' . '/wp-cli/checksum-command/src', 2 => __DIR__ . '/..' . '/wp-cli/config-command/src', 3 => __DIR__ . '/..' . '/wp-cli/cron-command/src', 4 => __DIR__ . '/..' . '/wp-cli/eval-command/src', 5 => __DIR__ . '/..' . '/wp-cli/export-command/src', 6 => __DIR__ . '/..' . '/wp-cli/import-command/src', 7 => __DIR__ . '/..' . '/wp-cli/language-command/src', 8 => __DIR__ . '/..' . '/wp-cli/rewrite-command/src', 9 => __DIR__ . '/..' . '/wp-cli/role-command/src', 10 => __DIR__ . '/..' . '/wp-cli/server-command/src', 11 => __DIR__ . '/..' . '/wp-cli/shell-command/src', 12 => __DIR__ . '/..' . '/wp-cli/super-admin-command/src', 13 => __DIR__ . '/..' . '/wp-cli/widget-command/src', 14 => __DIR__ . '/..' . '/wp-cli/cache-command/src', 15 => __DIR__ . '/..' . '/wp-cli/media-command/src', 16 => __DIR__ . '/..' . '/wp-cli/core-command/src', 17 => __DIR__ . '/..' . '/wp-cli/entity-command/src', 18 => __DIR__ . '/..' . '/wp-cli/package-command/src', 19 => __DIR__ . '/..' . '/wp-cli/db-command/src', 20 => __DIR__ . '/..' . '/wp-cli/extension-command/src', 21 => __DIR__ . '/..' . '/wp-cli/scaffold-command/src', 22 => __DIR__ . '/..' . '/wp-cli/search-replace-command/src', ); public static $prefixesPsr0 = array ( 'c' => array ( 'cli' => array ( 0 => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib', ), ), 'W' => array ( 'WP_CLI' => array ( 0 => __DIR__ . '/../..' . '/php', ), ), 'R' => array ( 'Requests' => array ( 0 => __DIR__ . '/..' . '/rmccue/requests/library', ), ), 'O' => array ( 'Oxymel' => array ( 0 => __DIR__ . '/..' . '/nb/oxymel', ), ), 'M' => array ( 'Mustache' => array ( 0 => __DIR__ . '/..' . '/mustache/mustache/src', ), ), 'B' => array ( 'Behat\\Gherkin' => array ( 0 => __DIR__ . '/..' . '/behat/gherkin/src', ), 'Behat\\Behat' => array ( 0 => __DIR__ . '/..' . '/behat/behat/src', ), ), ); public static $classMap = array ( ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit676e4dc925e4d952efb2d5f8c1115412::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit676e4dc925e4d952efb2d5f8c1115412::$prefixDirsPsr4; $loader->fallbackDirsPsr4 = ComposerStaticInit676e4dc925e4d952efb2d5f8c1115412::$fallbackDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit676e4dc925e4d952efb2d5f8c1115412::$prefixesPsr0; $loader->classMap = ComposerStaticInit676e4dc925e4d952efb2d5f8c1115412::$classMap; }, null, ClassLoader::class); } } array($vendorDir . '/wp-cli/autoload-splitter/src'), 'WP_CLI\\' => array($vendorDir . '/wp-cli/shell-command/src/WP_CLI', $vendorDir . '/wp-cli/entity-command/src/WP_CLI'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), 'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'), 'Seld\\CliPrompt\\' => array($vendorDir . '/seld/cli-prompt/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Mustangostang\\' => array($vendorDir . '/wp-cli/mustangostang-spyc/src'), 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), 'Composer\\' => array($vendorDir . '/composer/composer/src/Composer'), '' => array($baseDir . '/php/commands/src', $vendorDir . '/wp-cli/checksum-command/src', $vendorDir . '/wp-cli/config-command/src', $vendorDir . '/wp-cli/cron-command/src', $vendorDir . '/wp-cli/eval-command/src', $vendorDir . '/wp-cli/export-command/src', $vendorDir . '/wp-cli/import-command/src', $vendorDir . '/wp-cli/language-command/src', $vendorDir . '/wp-cli/rewrite-command/src', $vendorDir . '/wp-cli/role-command/src', $vendorDir . '/wp-cli/server-command/src', $vendorDir . '/wp-cli/shell-command/src', $vendorDir . '/wp-cli/super-admin-command/src', $vendorDir . '/wp-cli/widget-command/src', $vendorDir . '/wp-cli/cache-command/src', $vendorDir . '/wp-cli/media-command/src', $vendorDir . '/wp-cli/core-command/src', $vendorDir . '/wp-cli/entity-command/src', $vendorDir . '/wp-cli/package-command/src', $vendorDir . '/wp-cli/db-command/src', $vendorDir . '/wp-cli/extension-command/src', $vendorDir . '/wp-cli/scaffold-command/src', $vendorDir . '/wp-cli/search-replace-command/src'), ); * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Config\JsonConfigSource; use Composer\Json\JsonFile; use Composer\IO\IOInterface; use Composer\Package\Archiver; use Composer\Package\Version\VersionGuesser; use Composer\Repository\RepositoryManager; use Composer\Repository\RepositoryFactory; use Composer\Repository\WritableRepositoryInterface; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\Util\Silencer; use Composer\Plugin\PluginEvents; use Composer\EventDispatcher\Event; use Seld\JsonLint\DuplicateKeyException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Output\ConsoleOutput; use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Version\VersionParser; use Composer\Downloader\TransportException; use Seld\JsonLint\JsonParser; /** * Creates a configured instance of composer. * * @author Ryan Weaver * @author Jordi Boggiano * @author Igor Wiedler * @author Nils Adermann */ class Factory { /** * @throws \RuntimeException * @return string */ protected static function getHomeDir() { $home = getenv('COMPOSER_HOME'); if ($home) { return $home; } if (Platform::isWindows()) { if (!getenv('APPDATA')) { throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly'); } return rtrim(strtr(getenv('APPDATA'), '\\', '/'), '/') . '/Composer'; } $userDir = self::getUserDir(); if (is_dir($userDir . '/.composer')) { return $userDir . '/.composer'; } if (self::useXdg()) { // XDG Base Directory Specifications $xdgConfig = getenv('XDG_CONFIG_HOME') ?: $userDir . '/.config'; return $xdgConfig . '/composer'; } return $userDir . '/.composer'; } /** * @param string $home * @return string */ protected static function getCacheDir($home) { $cacheDir = getenv('COMPOSER_CACHE_DIR'); if ($cacheDir) { return $cacheDir; } $homeEnv = getenv('COMPOSER_HOME'); if ($homeEnv) { return $homeEnv . '/cache'; } if (Platform::isWindows()) { if ($cacheDir = getenv('LOCALAPPDATA')) { $cacheDir .= '/Composer'; } else { $cacheDir = $home . '/cache'; } return rtrim(strtr($cacheDir, '\\', '/'), '/'); } $userDir = self::getUserDir(); if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) { return $home . '/cache'; } if (self::useXdg()) { $xdgCache = getenv('XDG_CACHE_HOME') ?: $userDir . '/.cache'; return $xdgCache . '/composer'; } return $home . '/cache'; } /** * @param string $home * @return string */ protected static function getDataDir($home) { $homeEnv = getenv('COMPOSER_HOME'); if ($homeEnv) { return $homeEnv; } if (Platform::isWindows()) { return strtr($home, '\\', '/'); } $userDir = self::getUserDir(); if ($home !== $userDir . '/.composer' && self::useXdg()) { $xdgData = getenv('XDG_DATA_HOME') ?: $userDir . '/.local/share'; return $xdgData . '/composer'; } return $home; } /** * @param IOInterface|null $io * @return Config */ public static function createConfig(IOInterface $io = null, $cwd = null) { $cwd = $cwd ?: getcwd(); $config = new Config(true, $cwd); // determine and add main dirs to the config $home = self::getHomeDir(); $config->merge(array('config' => array( 'home' => $home, 'cache-dir' => self::getCacheDir($home), 'data-dir' => self::getDataDir($home), ))); $htaccessProtect = (bool) $config->get('htaccess-protect'); if ($htaccessProtect) { // Protect directory against web access. Since HOME could be // the www-data's user home and be web-accessible it is a // potential security risk $dirs = array($config->get('home'), $config->get('cache-dir'), $config->get('data-dir')); foreach ($dirs as $dir) { if (!file_exists($dir . '/.htaccess')) { if (!is_dir($dir)) { Silencer::call('mkdir', $dir, 0777, true); } Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all'); } } } // load global config $file = new JsonFile($config->get('home').'/config.json'); if ($file->exists()) { if ($io && $io->isDebug()) { $io->writeError('Loading config file ' . $file->getPath()); } $config->merge($file->read()); } $config->setConfigSource(new JsonConfigSource($file)); // load global auth file $file = new JsonFile($config->get('home').'/auth.json'); if ($file->exists()) { if ($io && $io->isDebug()) { $io->writeError('Loading config file ' . $file->getPath()); } $config->merge(array('config' => $file->read())); } $config->setAuthConfigSource(new JsonConfigSource($file, true)); // load COMPOSER_AUTH environment variable if set if ($composerAuthEnv = getenv('COMPOSER_AUTH')) { $authData = json_decode($composerAuthEnv, true); if (is_null($authData)) { throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); } if ($io && $io->isDebug()) { $io->writeError('Loading auth config from COMPOSER_AUTH'); } $config->merge(array('config' => $authData)); } return $config; } public static function getComposerFile() { return trim(getenv('COMPOSER')) ?: './composer.json'; } public static function createAdditionalStyles() { return array( 'highlight' => new OutputFormatterStyle('red'), 'warning' => new OutputFormatterStyle('black', 'yellow'), ); } /** * Creates a ConsoleOutput instance * * @return ConsoleOutput */ public static function createOutput() { $styles = self::createAdditionalStyles(); $formatter = new OutputFormatter(null, $styles); return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter); } /** * @deprecated Use Composer\Repository\RepositoryFactory::defaultRepos instead */ public static function createDefaultRepositories(IOInterface $io = null, Config $config = null, RepositoryManager $rm = null) { return RepositoryFactory::defaultRepos($io, $config, $rm); } /** * Creates a Composer instance * * @param IOInterface $io IO instance * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will * read from the default filename * @param bool $disablePlugins Whether plugins should not be loaded * @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer) * @throws \InvalidArgumentException * @throws \UnexpectedValueException * @return Composer */ public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true) { $cwd = $cwd ?: getcwd(); // load Composer configuration if (null === $localConfig) { $localConfig = static::getComposerFile(); } if (is_string($localConfig)) { $composerFile = $localConfig; $file = new JsonFile($localConfig, null, $io); if (!$file->exists()) { if ($localConfig === './composer.json' || $localConfig === 'composer.json') { $message = 'Composer could not find a composer.json file in '.$cwd; } else { $message = 'Composer could not find the config file: '.$localConfig; } $instructions = 'To initialize a project, please create a composer.json file as described in the https://getcomposer.org/ "Getting Started" section'; throw new \InvalidArgumentException($message.PHP_EOL.$instructions); } $file->validateSchema(JsonFile::LAX_SCHEMA); $jsonParser = new JsonParser; try { $jsonParser->parse(file_get_contents($localConfig), JsonParser::DETECT_KEY_CONFLICTS); } catch (DuplicateKeyException $e) { $details = $e->getDetails(); $io->writeError('Key '.$details['key'].' is a duplicate in '.$localConfig.' at line '.$details['line'].''); } $localConfig = $file->read(); } // Load config and override with local config/auth config $config = static::createConfig($io, $cwd); $config->merge($localConfig); if (isset($composerFile)) { $io->writeError('Loading config file ' . $composerFile, true, IOInterface::DEBUG); $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io))); $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io); if ($localAuthFile->exists()) { $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG); $config->merge(array('config' => $localAuthFile->read())); $config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true)); } } $vendorDir = $config->get('vendor-dir'); // initialize composer $composer = new Composer(); $composer->setConfig($config); if ($fullLoad) { // load auth configs into the IO instance $io->loadConfiguration($config); } $rfs = self::createRemoteFilesystem($io, $config); // initialize event dispatcher $dispatcher = new EventDispatcher($composer, $io); $composer->setEventDispatcher($dispatcher); // initialize repository manager $rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs); $composer->setRepositoryManager($rm); // load local repository $this->addLocalRepository($io, $rm, $vendorDir); // force-set the version of the global package if not defined as // guessing it adds no value and only takes time if (!$fullLoad && !isset($localConfig['version'])) { $localConfig['version'] = '1.0.0'; } // load package $parser = new VersionParser; $guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser); $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser); $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); $composer->setPackage($package); // initialize installation manager $im = $this->createInstallationManager(); $composer->setInstallationManager($im); if ($fullLoad) { // initialize download manager $dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs); $composer->setDownloadManager($dm); // initialize autoload generator $generator = new AutoloadGenerator($dispatcher, $io); $composer->setAutoloadGenerator($generator); // initialize archive manager $am = $this->createArchiveManager($config, $dm); $composer->setArchiveManager($am); } // add installers to the manager (must happen after download manager is created since they read it out of $composer) $this->createDefaultInstallers($im, $composer, $io); if ($fullLoad) { $globalComposer = null; if (realpath($config->get('home')) !== $cwd) { $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins); } $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins); $composer->setPluginManager($pm); $pm->loadInstalledPlugins(); } // init locker if possible if ($fullLoad && isset($composerFile)) { $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile)); $composer->setLocker($locker); } if ($fullLoad) { $initEvent = new Event(PluginEvents::INIT); $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent); // once everything is initialized we can // purge packages from local repos if they have been deleted on the filesystem if ($rm->getLocalRepository()) { $this->purgePackages($rm->getLocalRepository(), $im); } } return $composer; } /** * @param IOInterface $io IO instance * @param bool $disablePlugins Whether plugins should not be loaded * @return Composer */ public static function createGlobal(IOInterface $io, $disablePlugins = false) { $factory = new static(); return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, true); } /** * @param Repository\RepositoryManager $rm * @param string $vendorDir */ protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir) { $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io))); } /** * @param Config $config * @return Composer|null */ protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, $fullLoad = false) { $composer = null; try { $composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad); } catch (\Exception $e) { $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG); } return $composer; } /** * @param IO\IOInterface $io * @param Config $config * @param EventDispatcher $eventDispatcher * @return Downloader\DownloadManager */ public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) { $cache = null; if ($config->get('cache-files-ttl') > 0) { $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); } $dm = new Downloader\DownloadManager($io); switch ($preferred = $config->get('preferred-install')) { case 'dist': $dm->setPreferDist(true); break; case 'source': $dm->setPreferSource(true); break; case 'auto': default: // noop break; } if (is_array($preferred)) { $dm->setPreferences($preferred); } $executor = new ProcessExecutor($io); $fs = new Filesystem($executor); $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $executor, $fs)); $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $executor, $fs)); $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs)); $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs)); $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config)); $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache, $rfs)); $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache, $rfs)); $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache, $rfs)); $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache, $rfs)); return $dm; } /** * @param Config $config The configuration * @param Downloader\DownloadManager $dm Manager use to download sources * @return Archiver\ArchiveManager */ public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null) { if (null === $dm) { $io = new IO\NullIO(); $io->loadConfiguration($config); $dm = $this->createDownloadManager($io, $config); } $am = new Archiver\ArchiveManager($dm); $am->addArchiver(new Archiver\ZipArchiver); $am->addArchiver(new Archiver\PharArchiver); return $am; } /** * @param IOInterface $io * @param Composer $composer * @param Composer $globalComposer * @param bool $disablePlugins * @return Plugin\PluginManager */ protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false) { return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins); } /** * @return Installer\InstallationManager */ protected function createInstallationManager() { return new Installer\InstallationManager(); } /** * @param Installer\InstallationManager $im * @param Composer $composer * @param IO\IOInterface $io */ protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io) { $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null)); $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library')); $im->addInstaller(new Installer\PluginInstaller($io, $composer)); $im->addInstaller(new Installer\MetapackageInstaller($io)); } /** * @param WritableRepositoryInterface $repo repository to purge packages from * @param Installer\InstallationManager $im manager to check whether packages are still installed */ protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im) { foreach ($repo->getPackages() as $package) { if (!$im->isPackageInstalled($repo, $package)) { $repo->removePackage($package); } } } /** * @param IOInterface $io IO instance * @param mixed $config either a configuration array or a filename to read from, if null it will read from * the default filename * @param bool $disablePlugins Whether plugins should not be loaded * @return Composer */ public static function create(IOInterface $io, $config = null, $disablePlugins = false) { $factory = new static(); return $factory->createComposer($io, $config, $disablePlugins); } /** * @param IOInterface $io IO instance * @param Config $config Config instance * @param array $options Array of options passed directly to RemoteFilesystem constructor * @return RemoteFilesystem */ public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array()) { static $warned = false; $disableTls = false; if ($config && $config->get('disable-tls') === true) { if (!$warned) { $io->write('You are running Composer with SSL/TLS protection disabled.'); } $warned = true; $disableTls = true; } elseif (!extension_loaded('openssl')) { throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. ' . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } $remoteFilesystemOptions = array(); if ($disableTls === false) { if ($config && $config->get('cafile')) { $remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile'); } if ($config && $config->get('capath')) { $remoteFilesystemOptions['ssl']['capath'] = $config->get('capath'); } $remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options); } try { $remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls); } catch (TransportException $e) { if (false !== strpos($e->getMessage(), 'cafile')) { $io->write('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); $io->write('A valid CA certificate file is required for SSL/TLS protection.'); if (PHP_VERSION_ID < 50600) { $io->write('It is recommended you upgrade to PHP 5.6+ which can detect your system CA file automatically.'); } $io->write('You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } throw $e; } return $remoteFilesystem; } /** * @return bool */ private static function useXdg() { foreach (array_keys($_SERVER) as $key) { if (substr($key, 0, 4) === 'XDG_') { return true; } } return false; } /** * @throws \RuntimeException * @return string */ private static function getUserDir() { $home = getenv('HOME'); if (!$home) { throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly'); } return rtrim(strtr($home, '\\', '/'), '/'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; /** * Formats json strings used for php < 5.4 because the json_encode doesn't * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE * in these versions * * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class JsonFormatter { /** * This code is based on the function found at: * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ * * Originally licensed under MIT by Dave Perrett * * * @param string $json * @param bool $unescapeUnicode Un escape unicode * @param bool $unescapeSlashes Un escape slashes * @return string */ public static function format($json, $unescapeUnicode, $unescapeSlashes) { $result = ''; $pos = 0; $strLen = strlen($json); $indentStr = ' '; $newLine = "\n"; $outOfQuotes = true; $buffer = ''; $noescape = true; for ($i = 0; $i < $strLen; $i++) { // Grab the next character in the string $char = substr($json, $i, 1); // Are we inside a quoted string? if ('"' === $char && $noescape) { $outOfQuotes = !$outOfQuotes; } if (!$outOfQuotes) { $buffer .= $char; $noescape = '\\' === $char ? !$noescape : true; continue; } elseif ('' !== $buffer) { if ($unescapeSlashes) { $buffer = str_replace('\\/', '/', $buffer); } if ($unescapeUnicode && function_exists('mb_convert_encoding')) { // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha $buffer = preg_replace_callback('/(\\\\+)u([0-9a-f]{4})/i', function ($match) { $l = strlen($match[1]); if ($l % 2) { return str_repeat('\\', $l - 1) . mb_convert_encoding( pack('H*', $match[2]), 'UTF-8', 'UCS-2BE' ); } return $match[0]; }, $buffer); } $result .= $buffer.$char; $buffer = ''; continue; } if (':' === $char) { // Add a space after the : character $char .= ' '; } elseif (('}' === $char || ']' === $char)) { $pos--; $prevChar = substr($json, $i - 1, 1); if ('{' !== $prevChar && '[' !== $prevChar) { // If this character is the end of an element, // output a new line and indent the next line $result .= $newLine; for ($j = 0; $j < $pos; $j++) { $result .= $indentStr; } } else { // Collapse empty {} and [] $result = rtrim($result); } } $result .= $char; // If the last character was the beginning of an element, // output a new line and indent the next line if (',' === $char || '{' === $char || '[' === $char) { $result .= $newLine; if ('{' === $char || '[' === $char) { $pos++; } for ($j = 0; $j < $pos; $j++) { $result .= $indentStr; } } } return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; use Exception; /** * @author Jordi Boggiano */ class JsonValidationException extends Exception { protected $errors; public function __construct($message, $errors = array(), Exception $previous = null) { $this->errors = $errors; parent::__construct($message, 0, $previous); } public function getErrors() { return $this->errors; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; use JsonSchema\Validator; use Seld\JsonLint\JsonParser; use Seld\JsonLint\ParsingException; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; /** * Reads/writes json files. * * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class JsonFile { const LAX_SCHEMA = 1; const STRICT_SCHEMA = 2; const JSON_UNESCAPED_SLASHES = 64; const JSON_PRETTY_PRINT = 128; const JSON_UNESCAPED_UNICODE = 256; private $path; private $rfs; private $io; /** * Initializes json file reader/parser. * * @param string $path path to a lockfile * @param RemoteFilesystem $rfs required for loading http/https json files * @param IOInterface $io * @throws \InvalidArgumentException */ public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null) { $this->path = $path; if (null === $rfs && preg_match('{^https?://}i', $path)) { throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed'); } $this->rfs = $rfs; $this->io = $io; } /** * @return string */ public function getPath() { return $this->path; } /** * Checks whether json file exists. * * @return bool */ public function exists() { return is_file($this->path); } /** * Reads json file. * * @throws \RuntimeException * @return mixed */ public function read() { try { if ($this->rfs) { $json = $this->rfs->getContents($this->path, $this->path, false); } else { if ($this->io && $this->io->isDebug()) { $this->io->writeError('Reading ' . $this->path); } $json = file_get_contents($this->path); } } catch (TransportException $e) { throw new \RuntimeException($e->getMessage(), 0, $e); } catch (\Exception $e) { throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage()); } return static::parseJson($json, $this->path); } /** * Writes json file. * * @param array $hash writes hash into json file * @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) * @throws \UnexpectedValueException|\Exception */ public function write(array $hash, $options = 448) { $dir = dirname($this->path); if (!is_dir($dir)) { if (file_exists($dir)) { throw new \UnexpectedValueException( $dir.' exists and is not a directory.' ); } if (!@mkdir($dir, 0777, true)) { throw new \UnexpectedValueException( $dir.' does not exist and could not be created.' ); } } $retries = 3; while ($retries--) { try { file_put_contents($this->path, static::encode($hash, $options). ($options & self::JSON_PRETTY_PRINT ? "\n" : '')); break; } catch (\Exception $e) { if ($retries) { usleep(500000); continue; } throw $e; } } } /** * Validates the schema of the current json file according to composer-schema.json rules * * @param int $schema a JsonFile::*_SCHEMA constant * @throws JsonValidationException * @return bool true on success */ public function validateSchema($schema = self::STRICT_SCHEMA) { $content = file_get_contents($this->path); $data = json_decode($content); if (null === $data && 'null' !== $content) { self::validateSyntax($content, $this->path); } $schemaFile = __DIR__ . '/../../../res/composer-schema.json'; // Prepend with file:// only when not using a special schema already (e.g. in the phar) if (false === strpos($schemaFile, '://')) { $schemaFile = 'file://' . $schemaFile; } $schemaData = (object) array('$ref' => $schemaFile); if ($schema === self::LAX_SCHEMA) { $schemaData->additionalProperties = true; $schemaData->required = array(); } $validator = new Validator(); $validator->check($data, $schemaData); // TODO add more validation like check version constraints and such, perhaps build that into the arrayloader? if (!$validator->isValid()) { $errors = array(); foreach ((array) $validator->getErrors() as $error) { $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message']; } throw new JsonValidationException('"'.$this->path.'" does not match the expected JSON schema', $errors); } return true; } /** * Encodes an array into (optionally pretty-printed) JSON * * @param mixed $data Data to encode into a formatted JSON string * @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) * @return string Encoded json */ public static function encode($data, $options = 448) { if (PHP_VERSION_ID >= 50400) { $json = json_encode($data, $options); if (false === $json) { self::throwEncodeError(json_last_error()); } // compact brackets to follow recent php versions if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512) || (defined('JSON_C_VERSION') && version_compare(phpversion('json'), '1.3.6', '<'))) { $json = preg_replace('/\[\s+\]/', '[]', $json); $json = preg_replace('/\{\s+\}/', '{}', $json); } return $json; } $json = json_encode($data); if (false === $json) { self::throwEncodeError(json_last_error()); } $prettyPrint = (bool) ($options & self::JSON_PRETTY_PRINT); $unescapeUnicode = (bool) ($options & self::JSON_UNESCAPED_UNICODE); $unescapeSlashes = (bool) ($options & self::JSON_UNESCAPED_SLASHES); if (!$prettyPrint && !$unescapeUnicode && !$unescapeSlashes) { return $json; } $result = JsonFormatter::format($json, $unescapeUnicode, $unescapeSlashes); return $result; } /** * Throws an exception according to a given code with a customized message * * @param int $code return code of json_last_error function * @throws \RuntimeException */ private static function throwEncodeError($code) { switch ($code) { case JSON_ERROR_DEPTH: $msg = 'Maximum stack depth exceeded'; break; case JSON_ERROR_STATE_MISMATCH: $msg = 'Underflow or the modes mismatch'; break; case JSON_ERROR_CTRL_CHAR: $msg = 'Unexpected control character found'; break; case JSON_ERROR_UTF8: $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; break; default: $msg = 'Unknown error'; } throw new \RuntimeException('JSON encoding failed: '.$msg); } /** * Parses json string and returns hash. * * @param string $json json string * @param string $file the json file * * @return mixed */ public static function parseJson($json, $file = null) { if (null === $json) { return; } $data = json_decode($json, true); if (null === $data && JSON_ERROR_NONE !== json_last_error()) { self::validateSyntax($json, $file); } return $data; } /** * Validates the syntax of a JSON string * * @param string $json * @param string $file * @throws \UnexpectedValueException * @throws JsonValidationException * @throws ParsingException * @return bool true on success */ protected static function validateSyntax($json, $file = null) { $parser = new JsonParser(); $result = $parser->lint($json); if (null === $result) { if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) { throw new \UnexpectedValueException('"'.$file.'" is not UTF-8, could not parse as JSON'); } return true; } throw new ParsingException('"'.$file.'" does not contain valid JSON'."\n".$result->getMessage(), $result->getDetails()); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; use Composer\Repository\PlatformRepository; /** * @author Jordi Boggiano */ class JsonManipulator { private static $DEFINES = '(?(DEFINE) (? -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) (? true | false | null ) (? " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ) (? \[ (?: (?&json) \s* (?: , (?&json) \s* )* )? \s* \] ) (? \s* (?&string) \s* : (?&json) \s* ) (? \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) (? \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) ) )'; private $contents; private $newline; private $indent; public function __construct($contents) { $contents = trim($contents); if ($contents === '') { $contents = '{}'; } if (!$this->pregMatch('#^\{(.*)\}$#s', $contents)) { throw new \InvalidArgumentException('The json file must be an object ({})'); } $this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n"; $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents; $this->detectIndenting(); } public function getContents() { return $this->contents . $this->newline; } public function addLink($type, $package, $constraint, $sortPackages = false) { $decoded = JsonFile::parseJson($this->contents); // no link of that type yet if (!isset($decoded[$type])) { return $this->addMainKey($type, array($package => $constraint)); } $regex = '{'.self::$DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. '(?P'.preg_quote(JsonFile::encode($type)).'\s*:\s*)(?P(?&json))(?P.*)}sx'; if (!$this->pregMatch($regex, $this->contents, $matches)) { return false; } $links = $matches['value']; // try to find existing link $packageRegex = str_replace('/', '\\\\?/', preg_quote($package)); $regex = '{'.self::$DEFINES.'"(?P'.$packageRegex.')"(\s*:\s*)(?&string)}ix'; if ($this->pregMatch($regex, $links, $packageMatches)) { // update existing link $existingPackage = $packageMatches['package']; $packageRegex = str_replace('/', '\\\\?/', preg_quote($existingPackage)); $links = preg_replace_callback('{'.self::$DEFINES.'"'.$packageRegex.'"(?P\s*:\s*)(?&string)}ix', function ($m) use ($existingPackage, $constraint) { return JsonFile::encode(str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"'; }, $links); } else { if ($this->pregMatch('#^\s*\{\s*\S+.*?(\s*\}\s*)$#s', $links, $match)) { // link missing but non empty links $links = preg_replace( '{'.preg_quote($match[1]).'$}', // addcslashes is used to double up backslashes/$ since preg_replace resolves them as back references otherwise, see #1588 addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1], '\\$'), $links ); } else { // links empty $links = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $this->newline . $this->indent . '}'; } } if (true === $sortPackages) { $requirements = json_decode($links, true); $this->sortPackages($requirements); $links = $this->format($requirements); } $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end']; return true; } /** * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically. * * @link https://getcomposer.org/doc/02-libraries.md#platform-packages * * @param array $packages */ private function sortPackages(array &$packages = array()) { $prefix = function ($requirement) { if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $requirement)) { return preg_replace( array( '/^php/', '/^hhvm/', '/^ext/', '/^lib/', '/^\D/', ), array( '0-$0', '1-$0', '2-$0', '3-$0', '4-$0', ), $requirement ); } return '5-'.$requirement; }; uksort($packages, function ($a, $b) use ($prefix) { return strnatcmp($prefix($a), $prefix($b)); }); } public function addRepository($name, $config) { return $this->addSubNode('repositories', $name, $config); } public function removeRepository($name) { return $this->removeSubNode('repositories', $name); } public function addConfigSetting($name, $value) { return $this->addSubNode('config', $name, $value); } public function removeConfigSetting($name) { return $this->removeSubNode('config', $name); } public function addProperty($name, $value) { if (substr($name, 0, 6) === 'extra.') { return $this->addSubNode('extra', substr($name, 6), $value); } return $this->addMainKey($name, $value); } public function removeProperty($name) { if (substr($name, 0, 6) === 'extra.') { return $this->removeSubNode('extra', substr($name, 6)); } return $this->removeMainKey($name); } public function addSubNode($mainNode, $name, $value) { $decoded = JsonFile::parseJson($this->contents); $subName = null; if (in_array($mainNode, array('config', 'extra')) && false !== strpos($name, '.')) { list($name, $subName) = explode('.', $name, 2); } // no main node yet if (!isset($decoded[$mainNode])) { if ($subName !== null) { $this->addMainKey($mainNode, array($name => array($subName => $value))); } else { $this->addMainKey($mainNode, array($name => $value)); } return true; } // main node content not match-able $nodeRegex = '{'.self::$DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; try { if (!$this->pregMatch($nodeRegex, $this->contents, $match)) { return false; } } catch (\RuntimeException $e) { if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { return false; } throw $e; } $children = $match['content']; // invalid match due to un-regexable content, abort if (!@json_decode($children)) { return false; } $that = $this; // child exists $childRegex = '{'.self::$DEFINES.'(?P"'.preg_quote($name).'"\s*:\s*)(?P(?&json))(?P,?)}x'; if ($this->pregMatch($childRegex, $children, $matches)) { $children = preg_replace_callback($childRegex, function ($matches) use ($name, $subName, $value, $that) { if ($subName !== null) { $curVal = json_decode($matches['content'], true); if (!is_array($curVal)) { $curVal = array(); } $curVal[$subName] = $value; $value = $curVal; } return $matches['start'] . $that->format($value, 1) . $matches['end']; }, $children); } else { $this->pregMatch('#^{ \s*? (?P\S+.*?)? (?P\s*) }$#sx', $children, $match); $whitespace = ''; if (!empty($match['trailingspace'])) { $whitespace = $match['trailingspace']; } if (!empty($match['content'])) { if ($subName !== null) { $value = array($subName => $value); } // child missing but non empty children $children = preg_replace( '#'.$whitespace.'}$#', addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}', '\\$'), $children ); } else { if ($subName !== null) { $value = array($subName => $value); } // children present but empty $children = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}'; } } $this->contents = preg_replace_callback($nodeRegex, function ($m) use ($children) { return $m['start'] . $children . $m['end']; }, $this->contents); return true; } public function removeSubNode($mainNode, $name) { $decoded = JsonFile::parseJson($this->contents); // no node or empty node if (empty($decoded[$mainNode])) { return true; } // no node content match-able $nodeRegex = '{'.self::$DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; try { if (!$this->pregMatch($nodeRegex, $this->contents, $match)) { return false; } } catch (\RuntimeException $e) { if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { return false; } throw $e; } $children = $match['content']; // invalid match due to un-regexable content, abort if (!@json_decode($children, true)) { return false; } $subName = null; if (in_array($mainNode, array('config', 'extra')) && false !== strpos($name, '.')) { list($name, $subName) = explode('.', $name, 2); } // no node to remove if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) { return true; } // try and find a match for the subkey if ($this->pregMatch('{"'.preg_quote($name).'"\s*:}i', $children)) { // find best match for the value of "name" if (preg_match_all('{'.self::$DEFINES.'"'.preg_quote($name).'"\s*:\s*(?:(?&json))}x', $children, $matches)) { $bestMatch = ''; foreach ($matches[0] as $match) { if (strlen($bestMatch) < strlen($match)) { $bestMatch = $match; } } $childrenClean = preg_replace('{,\s*'.preg_quote($bestMatch).'}i', '', $children, -1, $count); if (1 !== $count) { $childrenClean = preg_replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count); if (1 !== $count) { return false; } } } } else { $childrenClean = $children; } // no child data left, $name was the only key in $this->pregMatch('#^{ \s*? (?P\S+.*?)? (?P\s*) }$#sx', $childrenClean, $match); if (empty($match['content'])) { $newline = $this->newline; $indent = $this->indent; $this->contents = preg_replace_callback($nodeRegex, function ($matches) use ($indent, $newline) { return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end']; }, $this->contents); // we have a subname, so we restore the rest of $name if ($subName !== null) { $curVal = json_decode($children, true); unset($curVal[$name][$subName]); $this->addSubNode($mainNode, $name, $curVal[$name]); } return true; } $that = $this; $this->contents = preg_replace_callback($nodeRegex, function ($matches) use ($that, $name, $subName, $childrenClean) { if ($subName !== null) { $curVal = json_decode($matches['content'], true); unset($curVal[$name][$subName]); $childrenClean = $that->format($curVal, 0); } return $matches['start'] . $childrenClean . $matches['end']; }, $this->contents); return true; } public function addMainKey($key, $content) { $decoded = JsonFile::parseJson($this->contents); $content = $this->format($content); // key exists already $regex = '{'.self::$DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))(?P.*)}sx'; if (isset($decoded[$key]) && $this->pregMatch($regex, $this->contents, $matches)) { // invalid match due to un-regexable content, abort if (!@json_decode('{'.$matches['key'].'}')) { return false; } $this->contents = $matches['start'] . JsonFile::encode($key).': '.$content . $matches['end']; return true; } // append at the end of the file and keep whitespace if ($this->pregMatch('#[^{\s](\s*)\}$#', $this->contents, $match)) { $this->contents = preg_replace( '#'.$match[1].'\}$#', addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\$'), $this->contents ); return true; } // append at the end of the file $this->contents = preg_replace( '#\}$#', addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\$'), $this->contents ); return true; } public function removeMainKey($key) { $decoded = JsonFile::parseJson($this->contents); if (!isset($decoded[$key])) { return true; } // key exists already $regex = '{'.self::$DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))\s*,?\s*(?P.*)}sx'; if ($this->pregMatch($regex, $this->contents, $matches)) { // invalid match due to un-regexable content, abort if (!@json_decode('{'.$matches['removal'].'}')) { return false; } // check that we are not leaving a dangling comma on the previous line if the last line was removed if (preg_match('#,\s*$#', $matches['start']) && preg_match('#^\}$#', $matches['end'])) { $matches['start'] = rtrim(preg_replace('#,(\s*)$#', '$1', $matches['start']), $this->indent); } $this->contents = $matches['start'] . $matches['end']; if (preg_match('#^\{\s*\}\s*$#', $this->contents)) { $this->contents = "{\n}"; } return true; } return false; } public function format($data, $depth = 0) { if (is_array($data)) { reset($data); if (is_numeric(key($data))) { foreach ($data as $key => $val) { $data[$key] = $this->format($val, $depth + 1); } return '['.implode(', ', $data).']'; } $out = '{' . $this->newline; $elems = array(); foreach ($data as $key => $val) { $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode($key). ': '.$this->format($val, $depth + 1); } return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}'; } return JsonFile::encode($data); } protected function detectIndenting() { if ($this->pregMatch('{^([ \t]+)"}m', $this->contents, $match)) { $this->indent = $match[1]; } else { $this->indent = ' '; } } protected function pregMatch($re, $str, &$matches = array()) { $count = preg_match($re, $str, $matches); if ($count === false) { switch (preg_last_error()) { case PREG_NO_ERROR: throw new \RuntimeException('Failed to execute regex: PREG_NO_ERROR', PREG_NO_ERROR); case PREG_INTERNAL_ERROR: throw new \RuntimeException('Failed to execute regex: PREG_INTERNAL_ERROR', PREG_INTERNAL_ERROR); case PREG_BACKTRACK_LIMIT_ERROR: throw new \RuntimeException('Failed to execute regex: PREG_BACKTRACK_LIMIT_ERROR', PREG_BACKTRACK_LIMIT_ERROR); case PREG_RECURSION_LIMIT_ERROR: throw new \RuntimeException('Failed to execute regex: PREG_RECURSION_LIMIT_ERROR', PREG_RECURSION_LIMIT_ERROR); case PREG_BAD_UTF8_ERROR: throw new \RuntimeException('Failed to execute regex: PREG_BAD_UTF8_ERROR', PREG_BAD_UTF8_ERROR); case PREG_BAD_UTF8_OFFSET_ERROR: throw new \RuntimeException('Failed to execute regex: PREG_BAD_UTF8_OFFSET_ERROR', PREG_BAD_UTF8_OFFSET_ERROR); case 6: // PREG_JIT_STACKLIMIT_ERROR if (PHP_VERSION_ID > 70000) { throw new \RuntimeException('Failed to execute regex: PREG_JIT_STACKLIMIT_ERROR', 6); } // fallthrough default: throw new \RuntimeException('Failed to execute regex: Unknown error'); } } return $count; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; /** * @author Jordi Boggiano */ class ScriptExecutionException extends \RuntimeException { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; /** * The base event class * * @author Nils Adermann */ class Event { /** * @var string This event's name */ protected $name; /** * @var array Arguments passed by the user, these will be forwarded to CLI script handlers */ protected $args; /** * @var array Flags usable in PHP script handlers */ protected $flags; /** * @var bool Whether the event should not be passed to more listeners */ private $propagationStopped = false; /** * Constructor. * * @param string $name The event name * @param array $args Arguments passed by the user * @param array $flags Optional flags to pass data not as argument */ public function __construct($name, array $args = array(), array $flags = array()) { $this->name = $name; $this->args = $args; $this->flags = $flags; } /** * Returns the event's name. * * @return string The event name */ public function getName() { return $this->name; } /** * Returns the event's arguments. * * @return array The event arguments */ public function getArguments() { return $this->args; } /** * Returns the event's flags. * * @return array The event flags */ public function getFlags() { return $this->flags; } /** * Checks if stopPropagation has been called * * @return bool Whether propagation has been stopped */ public function isPropagationStopped() { return $this->propagationStopped; } /** * Prevents the event from being passed to further listeners */ public function stopPropagation() { $this->propagationStopped = true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; /** * An EventSubscriber knows which events it is interested in. * * If an EventSubscriber is added to an EventDispatcher, the manager invokes * {@link getSubscribedEvents} and registers the subscriber as a listener for all * returned events. * * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Bernhard Schussek */ interface EventSubscriberInterface { /** * Returns an array of event names this subscriber wants to listen to. * * The array keys are event names and the value can be: * * * The method name to call (priority defaults to 0) * * An array composed of the method name to call and the priority * * An array of arrays composed of the method names to call and respective * priorities, or 0 if unset * * For instance: * * * array('eventName' => 'methodName') * * array('eventName' => array('methodName', $priority)) * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) * * @return array The event names to listen to */ public static function getSubscribedEvents(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\Installer\InstallerEvent; use Composer\IO\IOInterface; use Composer\Composer; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Repository\CompositeRepository; use Composer\Script; use Composer\Installer\PackageEvent; use Composer\Installer\BinaryInstaller; use Composer\Util\ProcessExecutor; use Composer\Script\Event as ScriptEvent; use Symfony\Component\Process\PhpExecutableFinder; /** * The Event Dispatcher. * * Example in command: * $dispatcher = new EventDispatcher($this->getComposer(), $this->getApplication()->getIO()); * // ... * $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD); * // ... * * @author François Pluchino * @author Jordi Boggiano * @author Nils Adermann */ class EventDispatcher { protected $composer; protected $io; protected $loader; protected $process; protected $listeners; private $eventStack; /** * Constructor. * * @param Composer $composer The composer instance * @param IOInterface $io The IOInterface instance * @param ProcessExecutor $process */ public function __construct(Composer $composer, IOInterface $io, ProcessExecutor $process = null) { $this->composer = $composer; $this->io = $io; $this->process = $process ?: new ProcessExecutor($io); $this->eventStack = array(); } /** * Dispatch an event * * @param string $eventName An event name * @param Event $event * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatch($eventName, Event $event = null) { if (null === $event) { $event = new Event($eventName); } return $this->doDispatch($event); } /** * Dispatch a script event. * * @param string $eventName The constant in ScriptEvents * @param bool $devMode * @param array $additionalArgs Arguments passed by the user * @param array $flags Optional flags to pass data not as argument * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatchScript($eventName, $devMode = false, $additionalArgs = array(), $flags = array()) { return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags)); } /** * Dispatch a package event. * * @param string $eventName The constant in PackageEvents * @param bool $devMode Whether or not we are in dev mode * @param PolicyInterface $policy The policy * @param Pool $pool The pool * @param CompositeRepository $installedRepo The installed repository * @param Request $request The request * @param array $operations The list of operations * @param OperationInterface $operation The package being installed/updated/removed * * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatchPackageEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation) { return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations, $operation)); } /** * Dispatch a installer event. * * @param string $eventName The constant in InstallerEvents * @param bool $devMode Whether or not we are in dev mode * @param PolicyInterface $policy The policy * @param Pool $pool The pool * @param CompositeRepository $installedRepo The installed repository * @param Request $request The request * @param array $operations The list of operations * * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) { return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations)); } /** * Triggers the listeners of an event. * * @param Event $event The event object to pass to the event handlers/listeners. * @throws \RuntimeException|\Exception * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ protected function doDispatch(Event $event) { $pathStr = 'PATH'; if (!isset($_SERVER[$pathStr]) && isset($_SERVER['Path'])) { $pathStr = 'Path'; } // add the bin dir to the PATH to make local binaries of deps usable in scripts $binDir = $this->composer->getConfig()->get('bin-dir'); if (is_dir($binDir)) { $binDir = realpath($binDir); if (isset($_SERVER[$pathStr]) && !preg_match('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $_SERVER[$pathStr])) { $_SERVER[$pathStr] = $binDir.PATH_SEPARATOR.getenv($pathStr); putenv($pathStr.'='.$_SERVER[$pathStr]); } } $listeners = $this->getListeners($event); $this->pushEvent($event); $return = 0; foreach ($listeners as $callable) { if (!is_string($callable) && is_callable($callable)) { $event = $this->checkListenerExpectedEvent($callable, $event); $return = false === call_user_func($callable, $event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE); $scriptName = substr($callable, 1); $args = $event->getArguments(); $flags = $event->getFlags(); if (substr($callable, 0, 10) === '@composer ') { $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($callable, 9); if (0 !== ($exitCode = $this->process->execute($exec))) { $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); } } else { if (!$this->getListeners(new Event($scriptName))) { $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable), true, IOInterface::QUIET); } $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); } } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); $methodName = substr($callable, strpos($callable, '::') + 2); if (!class_exists($className)) { $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); continue; } if (!is_callable($callable)) { $this->io->writeError('Method '.$callable.' is not callable, can not call '.$event->getName().' script', true, IOInterface::QUIET); continue; } try { $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; } catch (\Exception $e) { $message = "Script %s handling the %s event terminated with an exception"; $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); throw $e; } } else { $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor', 'escape'), $event->getArguments())); $exec = $callable . ($args === '' ? '' : ' '.$args); if ($this->io->isVerbose()) { $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec)); } else { $this->io->writeError(sprintf('> %s', $exec)); } $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); if ($possibleLocalBinaries) { foreach ($possibleLocalBinaries as $localExec) { if (preg_match('{\b'.preg_quote($callable).'$}', $localExec)) { $caller = BinaryInstaller::determineBinaryCaller($localExec); $exec = preg_replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec); break; } } } if (substr($exec, 0, 5) === '@php ') { $exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5); } if (0 !== ($exitCode = $this->process->execute($exec))) { $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); } } if ($event->isPropagationStopped()) { break; } } $this->popEvent(); return $return; } protected function getPhpExecCommand() { $finder = new PhpExecutableFinder(); $phpPath = $finder->find(); if (!$phpPath) { throw new \RuntimeException('Failed to locate PHP binary to execute '.$scriptName); } $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')); $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')); $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit')); return ProcessExecutor::escape($phpPath) . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag; } /** * @param string $className * @param string $methodName * @param Event $event Event invoking the PHP callable */ protected function executeEventPhpScript($className, $methodName, Event $event) { $event = $this->checkListenerExpectedEvent(array($className, $methodName), $event); if ($this->io->isVerbose()) { $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName)); } else { $this->io->writeError(sprintf('> %s::%s', $className, $methodName)); } return $className::$methodName($event); } /** * @param mixed $target * @param Event $event * @return Event */ protected function checkListenerExpectedEvent($target, Event $event) { if (in_array($event->getName(), array( 'init', 'command', 'pre-file-download', ), true)) { return $event; } try { $reflected = new \ReflectionParameter($target, 0); } catch (\Exception $e) { return $event; } $typehint = $reflected->getClass(); if (!$typehint instanceof \ReflectionClass) { return $event; } $expected = $typehint->getName(); // BC support if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') { trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); $event = new \Composer\Script\CommandEvent( $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getArguments() ); } if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') { trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); $event = new \Composer\Script\PackageEvent( $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getPolicy(), $event->getPool(), $event->getInstalledRepo(), $event->getRequest(), $event->getOperations(), $event->getOperation() ); } if (!$event instanceof $expected && $expected === 'Composer\Script\Event') { trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); $event = new \Composer\Script\Event( $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getArguments(), $event->getFlags() ); } return $event; } private function serializeCallback($cb) { if (is_array($cb) && count($cb) === 2) { if (is_object($cb[0])) { $cb[0] = get_class($cb[0]); } if (is_string($cb[0]) && is_string($cb[1])) { $cb = implode('::', $cb); } } if (is_string($cb)) { return $cb; } return var_export($cb, true); } /** * Add a listener for a particular event * * @param string $eventName The event name - typically a constant * @param callable $listener A callable expecting an event argument * @param int $priority A higher value represents a higher priority */ public function addListener($eventName, $listener, $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; } /** * Adds object methods as listeners for the events in getSubscribedEvents * * @see EventSubscriberInterface * * @param EventSubscriberInterface $subscriber */ 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); } } } } /** * Retrieves all listeners for a given event * * @param Event $event * @return array All listeners: callables and scripts */ protected function getListeners(Event $event) { $scriptListeners = $this->getScriptListeners($event); if (!isset($this->listeners[$event->getName()][0])) { $this->listeners[$event->getName()][0] = array(); } krsort($this->listeners[$event->getName()]); $listeners = $this->listeners; $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners); return call_user_func_array('array_merge', $listeners[$event->getName()]); } /** * Checks if an event has listeners registered * * @param Event $event * @return bool */ public function hasEventListeners(Event $event) { $listeners = $this->getListeners($event); return count($listeners) > 0; } /** * Finds all listeners defined as scripts in the package * * @param Event $event Event object * @return array Listeners */ protected function getScriptListeners(Event $event) { $package = $this->composer->getPackage(); $scripts = $package->getScripts(); if (empty($scripts[$event->getName()])) { return array(); } if ($this->loader) { $this->loader->unregister(); } $generator = $this->composer->getAutoloadGenerator(); if ($event instanceof ScriptEvent) { $generator->setDevMode($event->isDevMode()); } $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(); $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages); $map = $generator->parseAutoloads($packageMap, $package); $this->loader = $generator->createLoader($map); $this->loader->register(); return $scripts[$event->getName()]; } /** * Checks if string given references a class path and method * * @param string $callable * @return bool */ protected function isPhpScript($callable) { return false === strpos($callable, ' ') && false !== strpos($callable, '::'); } /** * Checks if string given references a composer run-script * * @param string $callable * @return bool */ protected function isComposerScript($callable) { return '@' === substr($callable, 0, 1) && '@php ' !== substr($callable, 0, 5); } /** * Push an event to the stack of active event * * @param Event $event * @throws \RuntimeException * @return number */ protected function pushEvent(Event $event) { $eventName = $event->getName(); if (in_array($eventName, $this->eventStack)) { throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName)); } return array_push($this->eventStack, $eventName); } /** * Pops the active event from the stack * * @return mixed */ protected function popEvent() { return array_pop($this->eventStack); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\SelfUpdate; /** * @author Jordi Boggiano */ class Keys { public static function fingerprint($path) { $hash = strtoupper(hash('sha256', preg_replace('{\s}', '', file_get_contents($path)))); return implode(' ', array( substr($hash, 0, 8), substr($hash, 8, 8), substr($hash, 16, 8), substr($hash, 24, 8), '', // Extra space substr($hash, 32, 8), substr($hash, 40, 8), substr($hash, 48, 8), substr($hash, 56, 8), )); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\SelfUpdate; use Composer\Util\RemoteFilesystem; use Composer\Config; use Composer\Json\JsonFile; /** * @author Jordi Boggiano */ class Versions { private $rfs; private $config; private $channel; public function __construct(Config $config, RemoteFilesystem $rfs) { $this->rfs = $rfs; $this->config = $config; } public function getChannel() { if ($this->channel) { return $this->channel; } $channelFile = $this->config->get('home').'/update-channel'; if (file_exists($channelFile)) { $channel = trim(file_get_contents($channelFile)); if (in_array($channel, array('stable', 'preview', 'snapshot'), true)) { return $this->channel = $channel; } } return $this->channel = 'stable'; } public function setChannel($channel) { if (!in_array($channel, array('stable', 'preview', 'snapshot'), true)) { throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: stable, preview, snapshot'); } $channelFile = $this->config->get('home').'/update-channel'; $this->channel = $channel; file_put_contents($channelFile, $channel.PHP_EOL); } public function getLatest() { $protocol = extension_loaded('openssl') ? 'https' : 'http'; $versions = JsonFile::parseJson($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/versions', false)); foreach ($versions[$this->getChannel()] as $version) { if ($version['min-php'] <= PHP_VERSION_ID) { return $version; } } throw new \LogicException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\DefaultPolicy; use Composer\Json\JsonFile; use Composer\Package\CompletePackageInterface; use Composer\Package\Version\VersionParser; use Composer\Package\BasePackage; use Composer\Package\Version\VersionSelector; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\PackageInterface; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Util\Platform; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\ComposerRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryFactory; use Composer\Spdx\SpdxLicenses; use Composer\Composer; use Composer\Semver\Semver; /** * @author Robert Schönthal * @author Jordi Boggiano * @author Jérémy Romey */ class ShowCommand extends BaseCommand { /** @var VersionParser */ protected $versionParser; protected $colors; /** @var Pool */ private $pool; protected function configure() { $this ->setName('show') ->setAliases(array('info')) ->setDescription('Shows information about packages.') ->setDefinition(array( new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'), new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'), new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'), new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'), new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'), new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'), new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'), new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'), new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'), new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), )) ->setHelp(<<versionParser = new VersionParser; if ($input->getOption('tree')) { $this->initStyles($output); } $composer = $this->getComposer(false); $io = $this->getIO(); if ($input->getOption('installed')) { $io->writeError('You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.'); } if ($input->getOption('outdated')) { $input->setOption('latest', true); } if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)'); return 1; } if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) { $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)'); return 1; } $format = $input->getOption('format'); if (!in_array($format, array('text', 'json'))) { $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); return 1; } // init repos $platformOverrides = array(); if ($composer) { $platformOverrides = $composer->getConfig()->get('platform') ?: array(); } $platformRepo = new PlatformRepository(array(), $platformOverrides); $phpVersion = $platformRepo->findPackage('php', '*')->getVersion(); if ($input->getOption('self')) { $package = $this->getComposer()->getPackage(); $repos = $installedRepo = new ArrayRepository(array($package)); } elseif ($input->getOption('platform')) { $repos = $installedRepo = $platformRepo; } elseif ($input->getOption('available')) { $installedRepo = $platformRepo; if ($composer) { $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); } else { $defaultRepos = RepositoryFactory::defaultRepos($io); $repos = new CompositeRepository($defaultRepos); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); } } elseif ($input->getOption('all') && $composer) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); } elseif ($input->getOption('all')) { $defaultRepos = RepositoryFactory::defaultRepos($io); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); $installedRepo = $platformRepo; $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); } else { $repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository(); } if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); } if ($input->getOption('latest') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "latest" option'); $input->setOption('latest', false); } $packageFilter = $input->getArgument('package'); // show single package or single version if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) { if ('json' === $format) { $io->writeError('Format "json" is only supported for package listings, falling back to format "text"'); } if (empty($package)) { list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version')); if (empty($package)) { $options = $input->getOptions(); if (!isset($options['working-dir']) || !file_exists('composer.json')) { throw new \InvalidArgumentException('Package ' . $packageFilter . ' not found'); } $io->writeError('Package ' . $packageFilter . ' not found in ' . $options['working-dir'] . '/composer.json'); return 1; } } else { $versions = array($package->getPrettyVersion() => $package->getVersion()); } if ($input->getOption('tree')) { $this->displayPackageTree($package, $installedRepo, $repos); } else { $latestPackage = null; if ($input->getOption('latest')) { $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion); } $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); $this->printLinks($package, 'requires'); $this->printLinks($package, 'devRequires', 'requires (dev)'); if ($package->getSuggests()) { $io->write("\nsuggests"); foreach ($package->getSuggests() as $suggested => $reason) { $io->write($suggested . ' ' . $reason . ''); } } $this->printLinks($package, 'provides'); $this->printLinks($package, 'conflicts'); $this->printLinks($package, 'replaces'); } return; } // show tree view if requested if ($input->getOption('tree')) { if ('json' === $format) { $io->writeError('Format "json" is only supported for package listings, falling back to format "text"'); } $rootRequires = $this->getRootRequires(); foreach ($installedRepo->getPackages() as $package) { if (in_array($package->getName(), $rootRequires, true)) { $this->displayPackageTree($package, $installedRepo, $repos); } } return 0; } if ($repos instanceof CompositeRepository) { $repos = $repos->getRepositories(); } elseif (!is_array($repos)) { $repos = array($repos); } // list packages $packages = array(); if (null !== $packageFilter) { $packageFilter = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i'; } $packageListFilter = array(); if ($input->getOption('direct')) { $packageListFilter = $this->getRootRequires(); } list($width) = $this->getApplication()->getTerminalDimensions(); if (null === $width) { // In case the width is not detected, we're probably running the command // outside of a real terminal, use space without a limit $width = PHP_INT_MAX; } if (Platform::isWindows()) { $width--; } else { $width = max(80, $width); } if ($input->getOption('path') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "path" option'); $input->setOption('path', false); } foreach ($repos as $repo) { if ($repo === $platformRepo) { $type = 'platform'; } elseif ( $repo === $installedRepo || ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true)) ) { $type = 'installed'; } else { $type = 'available'; } if ($repo instanceof ComposerRepository && $repo->hasProviders()) { foreach ($repo->getProviderNames() as $name) { if (!$packageFilter || preg_match($packageFilter, $name)) { $packages[$type][$name] = $name; } } } else { foreach ($repo->getPackages() as $package) { if (!isset($packages[$type][$package->getName()]) || !is_object($packages[$type][$package->getName()]) || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<') ) { if (!$packageFilter || preg_match($packageFilter, $package->getName())) { if (!$packageListFilter || in_array($package->getName(), $packageListFilter, true)) { $packages[$type][$package->getName()] = $package; } } } } } } $showAllTypes = $input->getOption('all'); $showLatest = $input->getOption('latest'); $showMinorOnly = $input->getOption('minor-only'); $indent = $showAllTypes ? ' ' : ''; $latestPackages = array(); $exitCode = 0; $viewData = array(); $viewMetaData = array(); foreach (array('platform' => true, 'available' => false, 'installed' => true) as $type => $showVersion) { if (isset($packages[$type])) { ksort($packages[$type]); $nameLength = $versionLength = $latestLength = 0; foreach ($packages[$type] as $package) { if (is_object($package)) { $nameLength = max($nameLength, strlen($package->getPrettyName())); if ($showVersion) { $versionLength = max($versionLength, strlen($package->getFullPrettyVersion())); if ($showLatest) { $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion, $showMinorOnly); if ($latestPackage === false) { continue; } $latestPackages[$package->getPrettyName()] = $latestPackage; $latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion())); } } } else { $nameLength = max($nameLength, strlen($package)); } } $writePath = !$input->getOption('name-only') && $input->getOption('path'); $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion; $writeLatest = $writeVersion && $showLatest; $writeDescription = !$input->getOption('name-only') && !$input->getOption('path'); $hasOutdatedPackages = false; $viewData[$type] = array(); $viewMetaData[$type] = array( 'nameLength' => $nameLength, 'versionLength' => $versionLength, 'latestLength' => $latestLength, ); foreach ($packages[$type] as $package) { $packageViewData = array(); if (is_object($package)) { $latestPackage = null; if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { $latestPackage = $latestPackages[$package->getPrettyName()]; } if ($input->getOption('outdated') && $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) { continue; } elseif ($input->getOption('outdated') || $input->getOption('strict')) { $hasOutdatedPackages = true; } $packageViewData['name'] = $package->getPrettyName(); if ($writeVersion) { $packageViewData['version'] = $package->getFullPrettyVersion(); } if ($writeLatest && $latestPackage) { $packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); } if ($writeDescription) { $packageViewData['description'] = $package->getDescription(); } if ($writePath) { $packageViewData['path'] = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"); } if ($latestPackage && $latestPackage->isAbandoned()) { $replacement = (is_string($latestPackage->getReplacementPackage())) ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $packageWarning = sprintf( 'Package %s is abandoned, you should avoid using it. %s.', $package->getPrettyName(), $replacement ); $packageViewData['warning'] = $packageWarning; } } else { $packageViewData['name'] = $package; } $viewData[$type][] = $packageViewData; } if ($input->getOption('strict') && $hasOutdatedPackages) { $exitCode = 1; break; } } } if ('json' === $format) { $io->write(JsonFile::encode($viewData)); } else { foreach ($viewData as $type => $packages) { $nameLength = $viewMetaData[$type]['nameLength']; $versionLength = $viewMetaData[$type]['versionLength']; $latestLength = $viewMetaData[$type]['latestLength']; $writeVersion = $nameLength + $versionLength + 3 <= $width; $writeLatest = $nameLength + $versionLength + $latestLength + 3 <= $width; $writeDescription = $nameLength + $versionLength + $latestLength + 24 <= $width; if ($writeLatest && !$io->isDecorated()) { $latestLength += 2; } if ($showAllTypes) { if ('available' === $type) { $io->write('' . $type . ':'); } else { $io->write('' . $type . ':'); } } foreach ($packages as $package) { $io->write($indent . str_pad($package['name'], $nameLength, ' '), false); if (isset($package['version']) && $writeVersion) { $io->write(' ' . str_pad($package['version'], $versionLength, ' '), false); } if (isset($package['latest']) && $writeLatest) { $latestVersion = $package['latest']; $updateStatus = $package['latest-status']; $style = $this->updateStatusToVersionStyle($updateStatus); if (!$io->isDecorated()) { $latestVersion = str_replace(array('up-to-date', 'semver-safe-update', 'update-possible'), array('=', '!', '~'), $updateStatus) . ' ' . $latestVersion; } $io->write(' <' . $style . '>' . str_pad($latestVersion, $latestLength, ' ') . '', false); } if (isset($package['description']) && $writeDescription) { $description = strtok($package['description'], "\r\n"); $remaining = $width - $nameLength - $versionLength - 4; if ($writeLatest) { $remaining -= $latestLength; } if (strlen($description) > $remaining) { $description = substr($description, 0, $remaining - 3) . '...'; } $io->write(' ' . $description, false); } if (isset($package['path'])) { $io->write(' ' . $package['path'], false); } $io->write(''); if (isset($package['warning'])) { $io->write('' . $package['warning'] . ''); } } if ($showAllTypes) { $io->write(''); } } } return $exitCode; } protected function getRootRequires() { $rootPackage = $this->getComposer()->getPackage(); return array_map( 'strtolower', array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires())) ); } protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package) { return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package)); } /** * finds a package by name and version if provided * * @param RepositoryInterface $installedRepo * @param RepositoryInterface $repos * @param string $name * @param ConstraintInterface|string $version * @throws \InvalidArgumentException * @return array array(CompletePackageInterface, array of versions) */ protected function getPackage(RepositoryInterface $installedRepo, RepositoryInterface $repos, $name, $version = null) { $name = strtolower($name); $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version; $policy = new DefaultPolicy(); $pool = new Pool('dev'); $pool->addRepository($repos); $matchedPackage = null; $versions = array(); $matches = $pool->whatProvides($name, $constraint); foreach ($matches as $index => $package) { // skip providers/replacers if ($package->getName() !== $name) { unset($matches[$index]); continue; } // select an exact match if it is in the installed repo and no specific version was required if (null === $version && $installedRepo->hasPackage($package)) { $matchedPackage = $package; } $versions[$package->getPrettyVersion()] = $package->getVersion(); $matches[$index] = $package->getId(); } // select preferred package according to policy rules if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) { $matchedPackage = $pool->literalToPackage($preferred[0]); } return array($matchedPackage, $versions); } /** * Prints package metadata. * * @param CompletePackageInterface $package * @param array $versions * @param RepositoryInterface $installedRepo */ protected function printMeta(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $latestPackage = null) { $io = $this->getIO(); $io->write('name : ' . $package->getPrettyName()); $io->write('descrip. : ' . $package->getDescription()); $io->write('keywords : ' . implode(', ', $package->getKeywords() ?: array())); $this->printVersions($package, $versions, $installedRepo); if ($latestPackage) { $style = $this->getVersionStyle($latestPackage, $package); $io->write('latest : <'.$style.'>' . $latestPackage->getPrettyVersion() . ''); } else { $latestPackage = $package; } $io->write('type : ' . $package->getType()); $this->printLicenses($package); $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); $io->write('names : ' . implode(', ', $package->getNames())); if ($latestPackage->isAbandoned()) { $replacement = ($latestPackage->getReplacementPackage() !== null) ? ' The author suggests using the ' . $latestPackage->getReplacementPackage(). ' package instead.' : null; $io->writeError( sprintf('Attention: This package is abandoned and no longer maintained.%s', $replacement) ); } if ($package->getSupport()) { $io->write("\nsupport"); foreach ($package->getSupport() as $type => $value) { $io->write('' . $type . ' : '.$value); } } if ($package->getAutoload()) { $io->write("\nautoload"); foreach ($package->getAutoload() as $type => $autoloads) { $io->write('' . $type . ''); if ($type === 'psr-0') { foreach ($autoloads as $name => $path) { $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'psr-4') { foreach ($autoloads as $name => $path) { $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'classmap') { $io->write(implode(', ', $autoloads)); } } if ($package->getIncludePaths()) { $io->write('include-path'); $io->write(implode(', ', $package->getIncludePaths())); } } } /** * Prints all available versions of this package and highlights the installed one if any. * * @param CompletePackageInterface $package * @param array $versions * @param RepositoryInterface $installedRepo */ protected function printVersions(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo) { uasort($versions, 'version_compare'); $versions = array_keys(array_reverse($versions)); // highlight installed version if ($installedRepo->hasPackage($package)) { $installedVersion = $package->getPrettyVersion(); $key = array_search($installedVersion, $versions); if (false !== $key) { $versions[$key] = '* ' . $installedVersion . ''; } } $versions = implode(', ', $versions); $this->getIO()->write('versions : ' . $versions); } /** * print link objects * * @param CompletePackageInterface $package * @param string $linkType * @param string $title */ protected function printLinks(CompletePackageInterface $package, $linkType, $title = null) { $title = $title ?: $linkType; $io = $this->getIO(); if ($links = $package->{'get'.ucfirst($linkType)}()) { $io->write("\n" . $title . ""); foreach ($links as $link) { $io->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); } } } /** * Prints the licenses of a package with metadata * * @param CompletePackageInterface $package */ protected function printLicenses(CompletePackageInterface $package) { $spdxLicenses = new SpdxLicenses(); $licenses = $package->getLicense(); $io = $this->getIO(); foreach ($licenses as $licenseId) { $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url if (!$license) { $out = $licenseId; } else { // is license OSI approved? if ($license[1] === true) { $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]); } else { $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]); } } $io->write('license : ' . $out); } } /** * Init styles for tree * * @param OutputInterface $output */ protected function initStyles(OutputInterface $output) { $this->colors = array( 'green', 'yellow', 'cyan', 'magenta', 'blue', ); foreach ($this->colors as $color) { $style = new OutputFormatterStyle($color); $output->getFormatter()->setStyle($color, $style); } } /** * Display the tree * * @param PackageInterface|string $package * @param RepositoryInterface $installedRepo * @param RepositoryInterface $distantRepos */ protected function displayPackageTree(PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos) { $io = $this->getIO(); $io->write(sprintf('%s', $package->getPrettyName()), false); $io->write(' ' . $package->getPrettyVersion(), false); $io->write(' ' . strtok($package->getDescription(), "\r\n")); if (is_object($package)) { $requires = $package->getRequires(); $treeBar = '├'; $j = 0; $total = count($requires); foreach ($requires as $requireName => $require) { $j++; if ($j == 0) { $this->writeTreeLine($treeBar); } if ($j == $total) { $treeBar = '└'; } $level = 1; $color = $this->colors[$level]; $info = sprintf('%s──<%s>%s %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint()); $this->writeTreeLine($info); $treeBar = str_replace('└', ' ', $treeBar); $packagesInTree = array($package->getName(), $requireName); $this->displayTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree, $treeBar, $level + 1); } } } /** * Display a package tree * * @param string $name * @param PackageInterface|string $package * @param RepositoryInterface $installedRepo * @param RepositoryInterface $distantRepos * @param array $packagesInTree * @param string $previousTreeBar * @param int $level */ protected function displayTree($name, $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos, array $packagesInTree, $previousTreeBar = '├', $level = 1) { $previousTreeBar = str_replace('├', '│', $previousTreeBar); list($package, $versions) = $this->getPackage($installedRepo, $distantRepos, $name, $package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()); if (is_object($package)) { $requires = $package->getRequires(); $treeBar = $previousTreeBar . ' ├'; $i = 0; $total = count($requires); foreach ($requires as $requireName => $require) { $currentTree = $packagesInTree; $i++; if ($i == $total) { $treeBar = $previousTreeBar . ' └'; } $colorIdent = $level % count($this->colors); $color = $this->colors[$colorIdent]; $circularWarn = in_array($requireName, $currentTree) ? '(circular dependency aborted here)' : ''; $info = rtrim(sprintf('%s──<%s>%s %s %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint(), $circularWarn)); $this->writeTreeLine($info); $treeBar = str_replace('└', ' ', $treeBar); if (!in_array($requireName, $currentTree)) { $currentTree[] = $requireName; $this->displayTree($requireName, $require, $installedRepo, $distantRepos, $currentTree, $treeBar, $level + 1); } } } } private function updateStatusToVersionStyle($updateStatus) { // 'up-to-date' is printed green // 'semver-safe-update' is printed red // 'update-possible' is printed yellow return str_replace(array('up-to-date', 'semver-safe-update', 'update-possible'), array('info', 'highlight', 'comment'), $updateStatus); } private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package) { if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) { return 'up-to-date'; } $constraint = $package->getVersion(); if (0 !== strpos($constraint, 'dev-')) { $constraint = '^'.$constraint; } if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) { // it needs an immediate semver-compliant upgrade return 'semver-safe-update'; } // it needs an upgrade but has potential BC breaks so is not urgent return 'update-possible'; } private function writeTreeLine($line) { $io = $this->getIO(); if (!$io->isDecorated()) { $line = str_replace(array('└', '├', '──', '│'), array('`-', '|-', '-', '|'), $line); } $io->write($line); } /** * Given a package, this finds the latest package matching it * * @param PackageInterface $package * @param Composer $composer * @param string $phpVersion * @param bool $minorOnly * * @return PackageInterface|null */ private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false) { // find the latest version allowed in this pool $name = $package->getName(); $versionSelector = new VersionSelector($this->getPool($composer)); $stability = $composer->getPackage()->getMinimumStability(); $flags = $composer->getPackage()->getStabilityFlags(); if (isset($flags[$name])) { $stability = array_search($flags[$name], BasePackage::$stabilities, true); } $bestStability = $stability; if ($composer->getPackage()->getPreferStable()) { $bestStability = $package->getStability(); } $targetVersion = null; if (0 === strpos($package->getVersion(), 'dev-')) { $targetVersion = $package->getVersion(); } if ($targetVersion === null && $minorOnly) { $targetVersion = '^' . $package->getVersion(); } return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability); } private function getPool(Composer $composer) { if (!$this->pool) { $this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); $this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); } return $this->pool; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\Package\Version\VersionParser; 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; /** * Base implementation for commands mapping dependency relationships. * * @author Niels Keurentjes */ class BaseDependencyCommand extends BaseCommand { const ARGUMENT_PACKAGE = 'package'; const ARGUMENT_CONSTRAINT = 'constraint'; const OPTION_RECURSIVE = 'recursive'; const OPTION_TREE = 'tree'; protected $colors; /** * Set common options and arguments. */ protected function configure() { $this->setDefinition(array( new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'), new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'), new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), )); } /** * Execute the command. * * @param InputInterface $input * @param OutputInterface $output * @param bool $inverted Whether to invert matching process (why-not vs why behaviour) * @return int|null Exit code of the operation. */ protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false) { // Emit command event on startup $composer = $this->getComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); // Prepare repositories and set up a pool $platformOverrides = $composer->getConfig()->get('platform') ?: array(); $repository = new CompositeRepository(array( new ArrayRepository(array($composer->getPackage())), $composer->getRepositoryManager()->getLocalRepository(), new PlatformRepository(array(), $platformOverrides), )); $pool = new Pool(); $pool->addRepository($repository); // Parse package name and constraint list($needle, $textConstraint) = array_pad( explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)), 2, $input->getArgument(self::ARGUMENT_CONSTRAINT) ); // Find packages that are or provide the requested package first $packages = $pool->whatProvides($needle); if (empty($packages)) { throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); } // If the version we ask for is not installed then we need to locate it in remote repos and add it. // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages. if (!$repository->findPackage($needle, $textConstraint)) { $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO())); if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { $repository->addRepository(new ArrayRepository(array(clone $match))); } } // Include replaced packages for inverted lookups as they are then the actual starting point to consider $needles = array($needle); if ($inverted) { foreach ($packages as $package) { $needles = array_merge($needles, array_map(function (Link $link) { return $link->getTarget(); }, $package->getReplaces())); } } // Parse constraint if one was supplied if ('*' !== $textConstraint) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($textConstraint); } else { $constraint = null; } // Parse rendering options $renderTree = $input->getOption(self::OPTION_TREE); $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE); // Resolve dependencies $results = $repository->getDependents($needles, $constraint, $inverted, $recursive); if (empty($results)) { $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; $this->getIO()->writeError(sprintf('There is no installed package depending on "%s"%s', $needle, $extra)); } elseif ($renderTree) { $this->initStyles($output); $root = $packages[0]; $this->getIO()->write(sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription())); $this->printTree($results); } else { $this->printTable($output, $results); } return 0; } /** * Assembles and prints a bottom-up table of the dependencies. * * @param OutputInterface $output * @param array $results */ protected function printTable(OutputInterface $output, $results) { $table = array(); $doubles = array(); do { $queue = array(); $rows = array(); foreach ($results as $result) { /** * @var PackageInterface $package * @var Link $link */ list($package, $link, $children) = $result; $unique = (string) $link; if (isset($doubles[$unique])) { continue; } $doubles[$unique] = true; $version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion(); $rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())); if ($children) { $queue = array_merge($queue, $children); } } $results = $queue; $table = array_merge($rows, $table); } while (!empty($results)); // Render table $renderer = new Table($output); $renderer->setStyle('compact'); $renderer->getStyle()->setVerticalBorderChar(''); $renderer->getStyle()->setCellRowContentFormat('%s '); $renderer->setRows($table)->render(); } /** * Init styles for tree * * @param OutputInterface $output */ protected function initStyles(OutputInterface $output) { $this->colors = array( 'green', 'yellow', 'cyan', 'magenta', 'blue', ); foreach ($this->colors as $color) { $style = new OutputFormatterStyle($color); $output->getFormatter()->setStyle($color, $style); } } /** * Recursively prints a tree of the selected results. * * @param array $results Results to be printed at this level. * @param string $prefix Prefix of the current tree level. * @param int $level Current level of recursion. */ protected function printTree($results, $prefix = '', $level = 1) { $count = count($results); $idx = 0; foreach ($results as $result) { /** * @var PackageInterface $package * @var Link $link * @var array|bool $children */ list($package, $link, $children) = $result; $color = $this->colors[$level % count($this->colors)]; $prevColor = $this->colors[($level - 1) % count($this->colors)]; $isLast = (++$idx == $count); $versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion(); $packageText = rtrim(sprintf('<%s>%s %s', $color, $package->getPrettyName(), $versionText)); $linkText = sprintf('%s <%s>%s %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint()); $circularWarn = $children === false ? '(circular dependency aborted here)' : ''; $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn))); if ($children) { $this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1); } } } private function writeTreeLine($line) { $io = $this->getIO(); if (!$io->isDecorated()) { $line = str_replace(array('└', '├', '──', '│'), array('`-', '|-', '-', '|'), $line); } $io->write($line); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Factory; use Composer\Installer; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Package\Version\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; /** * @author Jérémy Romey * @author Jordi Boggiano */ class RequireCommand extends InitCommand { protected function configure() { $this ->setName('require') ->setDescription('Adds required packages to your composer.json and installs them.') ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package name optionally including a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'), new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), )) ->setHelp(<<getIO(); $newlyCreated = !file_exists($file); if ($newlyCreated && !file_put_contents($file, "{\n}\n")) { $io->writeError(''.$file.' could not be created.'); return 1; } if (!is_readable($file)) { $io->writeError(''.$file.' is not readable.'); return 1; } if (!is_writable($file)) { $io->writeError(''.$file.' is not writable.'); return 1; } if (filesize($file) === 0) { file_put_contents($file, "{\n}\n"); } $json = new JsonFile($file); $composerBackup = file_get_contents($json->getPath()); $composer = $this->getComposer(true, $input->getOption('no-plugins')); $repos = $composer->getRepositoryManager()->getRepositories(); $platformOverrides = $composer->getConfig()->get('platform') ?: array(); // initialize $this->repos as it is used by the parent InitCommand $this->repos = new CompositeRepository(array_merge( array(new PlatformRepository(array(), $platformOverrides)), $repos )); if ($composer->getPackage()->getPreferStable()) { $preferredStability = 'stable'; } else { $preferredStability = $composer->getPackage()->getMinimumStability(); } $phpVersion = $this->repos->findPackage('php', '*')->getVersion(); $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability); $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; $requirements = $this->formatRequirements($requirements); // validate requirements format $versionParser = new VersionParser(); foreach ($requirements as $constraint) { $versionParser->parseConstraints($constraint); } $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'); if (!$this->updateFileCleanly($json, $requirements, $requireKey, $removeKey, $sortPackages)) { $composerDefinition = $json->read(); foreach ($requirements as $package => $version) { $composerDefinition[$requireKey][$package] = $version; unset($composerDefinition[$removeKey][$package]); } $json->write($composerDefinition); } $io->writeError(''.$file.' has been '.($newlyCreated ? 'created' : 'updated').''); if ($input->getOption('no-update')) { return 0; } $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); // Update packages $this->resetComposer(); $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $install = Installer::create($io, $composer); $install ->setVerbose($input->getOption('verbose')) ->setPreferSource($input->getOption('prefer-source')) ->setPreferDist($input->getOption('prefer-dist')) ->setDevMode($updateDevMode) ->setRunScripts(!$input->getOption('no-scripts')) ->setSkipSuggest($input->getOption('no-suggest')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setApcuAutoloader($apcu) ->setUpdate(true) ->setUpdateWhitelist(array_keys($requirements)) ->setWhitelistDependencies($input->getOption('update-with-dependencies')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setPreferStable($input->getOption('prefer-stable')) ->setPreferLowest($input->getOption('prefer-lowest')) ; $exception = null; try { $status = $install->run(); } catch (\Exception $exception) { $status = 1; } if ($status !== 0) { if ($newlyCreated) { $io->writeError("\n".'Installation failed, deleting '.$file.'.'); unlink($json->getPath()); } else { $io->writeError("\n".'Installation failed, reverting '.$file.' to its original content.'); file_put_contents($json->getPath(), $composerBackup); } } if ($exception) { throw $exception; } return $status; } private function updateFileCleanly($json, array $new, $requireKey, $removeKey, $sortPackages) { $contents = file_get_contents($json->getPath()); $manipulator = new JsonManipulator($contents); foreach ($new as $package => $constraint) { if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) { return false; } if (!$manipulator->removeSubNode($removeKey, $package)) { return false; } } file_put_contents($json->getPath(), $manipulator->getContents()); return true; } protected function interact(InputInterface $input, OutputInterface $output) { return; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Config\JsonConfigSource; use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Json\JsonFile; use Composer\Factory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** * @author Pierre du Plessis * @author Jordi Boggiano */ class RemoveCommand extends BaseCommand { protected function configure() { $this ->setName('remove') ->setDescription('Removes a package from the require or require-dev.') ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies. (Deprecrated, is now default behavior)'), new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), )) ->setHelp(<<remove command removes a package from the current list of installed packages php composer.phar remove EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $packages = $input->getArgument('packages'); $packages = array_map('strtolower', $packages); $file = Factory::getComposerFile(); $jsonFile = new JsonFile($file); $composer = $jsonFile->read(); $composerBackup = file_get_contents($jsonFile->getPath()); $json = new JsonConfigSource($jsonFile); $type = $input->getOption('dev') ? 'require-dev' : 'require'; $altType = !$input->getOption('dev') ? 'require-dev' : 'require'; $io = $this->getIO(); if ($input->getOption('update-with-dependencies')) { $io->writeError('You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.'); } // make sure name checks are done case insensitively foreach (array('require', 'require-dev') as $linkType) { if (isset($composer[$linkType])) { foreach ($composer[$linkType] as $name => $version) { $composer[$linkType][strtolower($name)] = $name; } } } foreach ($packages as $package) { if (isset($composer[$type][$package])) { $json->removeLink($type, $composer[$type][$package]); } elseif (isset($composer[$altType][$package])) { $io->writeError(''.$composer[$altType][$package].' could not be found in '.$type.' but it is present in '.$altType.''); if ($io->isInteractive()) { if ($io->askConfirmation('Do you want to remove it from '.$altType.' [yes]? ', true)) { $json->removeLink($altType, $composer[$altType][$package]); } } } else { $io->writeError(''.$package.' is not required in your composer.json and has not been removed'); } } if ($input->getOption('no-update')) { return 0; } // Update packages $this->resetComposer(); $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $install = Installer::create($io, $composer); $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); $install ->setVerbose($input->getOption('verbose')) ->setDevMode($updateDevMode) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setApcuAutoloader($apcu) ->setUpdate(true) ->setUpdateWhitelist($packages) ->setWhitelistDependencies(!$input->getOption('no-update-with-dependencies')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setRunScripts(!$input->getOption('no-scripts')) ; $exception = null; try { $status = $install->run(); } catch (\Exception $exception) { $status = 1; } if ($status !== 0) { $io->writeError("\n".'Removal failed, reverting '.$file.' to its original content.'); file_put_contents($jsonFile->getPath(), $composerBackup); } if ($exception) { throw $exception; } return $status; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Factory; use Composer\Config; use Composer\Downloader\TransportException; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Util\ConfigValidator; use Composer\Util\IniHelper; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\Util\StreamContextFactory; use Composer\SelfUpdate\Keys; use Composer\SelfUpdate\Versions; use Composer\IO\NullIO; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class DiagnoseCommand extends BaseCommand { /** @var RemoteFileSystem */ protected $rfs; /** @var ProcessExecutor */ protected $process; /** @var int */ protected $exitCode = 0; protected function configure() { $this ->setName('diagnose') ->setDescription('Diagnoses the system to identify common errors.') ->setHelp(<<diagnose command checks common errors to help debugging problems. The process exit code will be 1 in case of warnings and 2 for errors. EOT ) ; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $composer = $this->getComposer(false); $io = $this->getIO(); if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $io->write('Checking composer.json: ', false); $this->outputResult($this->checkComposerSchema()); } if ($composer) { $config = $composer->getConfig(); } else { $config = Factory::createConfig(); } $config->merge(array('config' => array('secure-http' => false))); $config->prohibitUrlByConfig('http://packagist.org', new NullIO); $this->rfs = Factory::createRemoteFilesystem($io, $config); $this->process = new ProcessExecutor($io); $io->write('Checking platform settings: ', false); $this->outputResult($this->checkPlatform()); $io->write('Checking git settings: ', false); $this->outputResult($this->checkGit()); $io->write('Checking http connectivity to packagist: ', false); $this->outputResult($this->checkHttp('http', $config)); $io->write('Checking https connectivity to packagist: ', false); $this->outputResult($this->checkHttp('https', $config)); $opts = stream_context_get_options(StreamContextFactory::getContext('http://example.org')); if (!empty($opts['http']['proxy'])) { $io->write('Checking HTTP proxy: ', false); $this->outputResult($this->checkHttpProxy()); $io->write('Checking HTTP proxy support for request_fulluri: ', false); $this->outputResult($this->checkHttpProxyFullUriRequestParam()); $io->write('Checking HTTPS proxy support for request_fulluri: ', false); $this->outputResult($this->checkHttpsProxyFullUriRequestParam()); } if ($oauth = $config->get('github-oauth')) { foreach ($oauth as $domain => $token) { $io->write('Checking '.$domain.' oauth access: ', false); $this->outputResult($this->checkGithubOauth($domain, $token)); } } else { $io->write('Checking github.com rate limit: ', false); try { $rate = $this->getGithubRateLimit('github.com'); $this->outputResult(true); if (10 > $rate['remaining']) { $io->write('WARNING'); $io->write(sprintf( 'Github has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . PHP_EOL . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL . ' https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens', $rate['remaining'], $rate['limit'] )); } } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { $this->outputResult('The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it'); } else { $this->outputResult($e); } } } $io->write('Checking disk free space: ', false); $this->outputResult($this->checkDiskSpace($config)); if ('phar:' === substr(__FILE__, 0, 5)) { $io->write('Checking pubkeys: ', false); $this->outputResult($this->checkPubKeys($config)); $io->write('Checking composer version: ', false); $this->outputResult($this->checkVersion($config)); } return $this->exitCode; } private function checkComposerSchema() { $validator = new ConfigValidator($this->getIO()); list($errors, , $warnings) = $validator->validate(Factory::getComposerFile()); if ($errors || $warnings) { $messages = array( 'error' => $errors, 'warning' => $warnings, ); $output = ''; foreach ($messages as $style => $msgs) { foreach ($msgs as $msg) { $output .= '<' . $style . '>' . $msg . '' . PHP_EOL; } } return rtrim($output); } return true; } private function checkGit() { $this->process->execute('git config color.ui', $output); if (strtolower(trim($output)) === 'always') { return 'Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.'; } return true; } private function checkHttp($proto, Config $config) { $disableTls = false; $result = array(); if ($proto === 'https' && $config->get('disable-tls') === true) { $disableTls = true; $result[] = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; } if ($proto === 'https' && !extension_loaded('openssl') && !$disableTls) { $result[] = 'Composer is configured to use SSL/TLS protection but the openssl extension is not available.'; } try { $this->rfs->getContents('packagist.org', $proto . '://packagist.org/packages.json', false); } catch (TransportException $e) { if (false !== strpos($e->getMessage(), 'cafile')) { $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; $result[] = 'Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'; $result[] = 'You can alternatively disable this error, at your own risk, by enabling the \'disable-tls\' option.'; } else { array_unshift($result, '[' . get_class($e) . '] ' . $e->getMessage()); } } if (count($result) > 0) { return $result; } return true; } private function checkHttpProxy() { $protocol = extension_loaded('openssl') ? 'https' : 'http'; try { $json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://packagist.org/packages.json', false), true); $hash = reset($json['provider-includes']); $hash = $hash['sha256']; $path = str_replace('%hash%', $hash, key($json['provider-includes'])); $provider = $this->rfs->getContents('packagist.org', $protocol . '://packagist.org/'.$path, false); if (hash('sha256', $provider) !== $hash) { return 'It seems that your proxy is modifying http traffic on the fly'; } } catch (\Exception $e) { return $e; } return true; } /** * Due to various proxy servers configurations, some servers can't handle non-standard HTTP "http_proxy_request_fulluri" parameter, * and will return error 500/501 (as not implemented), see discussion @ https://github.com/composer/composer/pull/1825. * This method will test, if you need to disable this parameter via setting extra environment variable in your system. * * @return bool|string */ private function checkHttpProxyFullUriRequestParam() { $url = 'http://packagist.org/packages.json'; try { $this->rfs->getContents('packagist.org', $url, false); } catch (TransportException $e) { try { $this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false))); } catch (TransportException $e) { return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')'; } return 'It seems there is a problem with your proxy server, try setting the "HTTP_PROXY_REQUEST_FULLURI" and "HTTPS_PROXY_REQUEST_FULLURI" environment variables to "false"'; } return true; } /** * Due to various proxy servers configurations, some servers can't handle non-standard HTTP "http_proxy_request_fulluri" parameter, * and will return error 500/501 (as not implemented), see discussion @ https://github.com/composer/composer/pull/1825. * This method will test, if you need to disable this parameter via setting extra environment variable in your system. * * @return bool|string */ private function checkHttpsProxyFullUriRequestParam() { if (!extension_loaded('openssl')) { return 'You need the openssl extension installed for this check'; } $url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0'; try { $this->rfs->getContents('github.com', $url, false); } catch (TransportException $e) { try { $this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false))); } catch (TransportException $e) { return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')'; } return 'It seems there is a problem with your proxy server, try setting the "HTTPS_PROXY_REQUEST_FULLURI" environment variable to "false"'; } return true; } private function checkGithubOauth($domain, $token) { $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); try { $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; return $this->rfs->getContents($domain, $url, false, array( 'retry-auth-failure' => false, )) ? true : 'Unexpected error'; } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { return 'The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it'; } return $e; } } /** * @param string $domain * @param string $token * @throws TransportException * @return array */ private function getGithubRateLimit($domain, $token = null) { if ($token) { $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); } $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; $json = $this->rfs->getContents($domain, $url, false, array('retry-auth-failure' => false)); $data = json_decode($json, true); return $data['resources']['core']; } private function checkDiskSpace($config) { $minSpaceFree = 1024 * 1024; if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) ) { return 'The disk hosting '.$dir.' is full'; } return true; } private function checkPubKeys($config) { $home = $config->get('home'); $errors = array(); $io = $this->getIO(); if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) { $io->write(''); } if (file_exists($home.'/keys.tags.pub')) { $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub')); } else { $errors[] = 'Missing pubkey for tags verification'; } if (file_exists($home.'/keys.dev.pub')) { $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub')); } else { $errors[] = 'Missing pubkey for dev verification'; } if ($errors) { $errors[] = 'Run composer self-update --update-keys to set them up'; } return $errors ?: true; } private function checkVersion($config) { $versionsUtil = new Versions($config, $this->rfs); $latest = $versionsUtil->getLatest(); if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') { return 'You are not running the latest '.$versionsUtil->getChannel().' version, run `composer self-update` to update ('.Composer::VERSION.' => '.$latest['version'].')'; } return true; } /** * @param bool|string|\Exception $result */ private function outputResult($result) { $io = $this->getIO(); if (true === $result) { $io->write('OK'); return; } $hadError = false; if ($result instanceof \Exception) { $result = '['.get_class($result).'] '.$result->getMessage().''; } if (!$result) { // falsey results should be considered as an error, even if there is nothing to output $hadError = true; } else { if (!is_array($result)) { $result = array($result); } foreach ($result as $message) { if (false !== strpos($message, '')) { $hadError = true; } } } if ($hadError) { $io->write('FAIL'); $this->exitCode = 2; } else { $io->write('WARNING'); $this->exitCode = 1; } if ($result) { foreach ($result as $message) { $io->write($message); } } } private function checkPlatform() { $output = ''; $out = function ($msg, $style) use (&$output) { $output .= '<'.$style.'>'.$msg.''.PHP_EOL; }; // code below taken from getcomposer.org/installer, any changes should be made there and replicated here $errors = array(); $warnings = array(); $displayIniMessage = false; $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage(); $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; if (!function_exists('json_decode')) { $errors['json'] = true; } if (!extension_loaded('Phar')) { $errors['phar'] = true; } if (!extension_loaded('filter')) { $errors['filter'] = true; } if (!extension_loaded('hash')) { $errors['hash'] = true; } if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { $errors['iconv_mbstring'] = true; } if (!ini_get('allow_url_fopen')) { $errors['allow_url_fopen'] = true; } if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { $errors['ioncube'] = ioncube_loader_version(); } if (PHP_VERSION_ID < 50302) { $errors['php'] = PHP_VERSION; } if (!isset($errors['php']) && PHP_VERSION_ID < 50304) { $warnings['php'] = PHP_VERSION; } if (!extension_loaded('openssl')) { $errors['openssl'] = true; } if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { $warnings['openssl_version'] = true; } if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { $warnings['apc_cli'] = true; } if (!extension_loaded('zlib')) { $warnings['zlib'] = true; } ob_start(); phpinfo(INFO_GENERAL); $phpinfo = ob_get_clean(); if (preg_match('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { $configure = $match[1]; if (false !== strpos($configure, '--enable-sigchild')) { $warnings['sigchild'] = true; } if (false !== strpos($configure, '--with-curlwrappers')) { $warnings['curlwrappers'] = true; } } if (ini_get('xdebug.profiler_enabled')) { $warnings['xdebug_profile'] = true; } elseif (extension_loaded('xdebug')) { $warnings['xdebug_loaded'] = true; } if (!empty($errors)) { foreach ($errors as $error => $current) { switch ($error) { case 'json': $text = PHP_EOL."The json extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-json"; break; case 'phar': $text = PHP_EOL."The phar extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-phar"; break; case 'filter': $text = PHP_EOL."The filter extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-filter"; break; case 'hash': $text = PHP_EOL."The hash extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-hash"; break; case 'iconv_mbstring': $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL; $text .= "Install either of them or recompile php without --disable-iconv"; break; case 'unicode': $text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL; $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; $text .= " detect_unicode = Off"; $displayIniMessage = true; break; case 'suhosin': $text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL; $text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL; $text .= " suhosin.executor.include.whitelist = phar ".$current; $displayIniMessage = true; break; case 'php': $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher."; break; case 'allow_url_fopen': $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL; $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; $text .= " allow_url_fopen = On"; $displayIniMessage = true; break; case 'ioncube': $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL; $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL; $text .= " zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so"; $displayIniMessage = true; break; case 'openssl': $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL; $text .= "If possible you should enable it or recompile php with --with-openssl"; break; } $out($text, 'error'); } $output .= PHP_EOL; } if (!empty($warnings)) { foreach ($warnings as $warning => $current) { switch ($warning) { case 'apc_cli': $text = "The apc.enable_cli setting is incorrect.".PHP_EOL; $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; $text .= " apc.enable_cli = Off"; $displayIniMessage = true; break; case 'zlib': $text = 'The zlib extension is not loaded, this can slow down Composer a lot.'.PHP_EOL; $text .= 'If possible, enable it or recompile php with --with-zlib'.PHP_EOL; $displayIniMessage = true; break; case 'sigchild': $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL; $text .= "Recompile it without this flag if possible, see also:".PHP_EOL; $text .= " https://bugs.php.net/bug.php?id=22999"; break; case 'curlwrappers': $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL; $text .= " Recompile it without this flag if possible"; break; case 'php': $text = "Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL; $text .= " Composer works with 5.3.2+ for most people, but there might be edge case issues."; break; case 'openssl_version': // Attempt to parse version number out, fallback to whole string value. $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true); $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT; $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL; $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above."; break; case 'xdebug_loaded': $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL; $text .= " Disabling it when using Composer is recommended."; break; case 'xdebug_profile': $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL; $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL; $text .= " xdebug.profiler_enabled = 0"; $displayIniMessage = true; break; } $out($text, 'comment'); } } if ($displayIniMessage) { $out($iniMessage, 'comment'); } return !$warnings && !$errors ? true : $output; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Config; use Composer\Factory; use Composer\Installer; use Composer\Installer\ProjectInstaller; use Composer\Installer\InstallationManager; use Composer\Installer\SuggestedPackagesReporter; use Composer\IO\IOInterface; use Composer\Package\BasePackage; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\Package\Version\VersionSelector; use Composer\Package\AliasPackage; use Composer\Repository\RepositoryFactory; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\InstalledFilesystemRepository; use Composer\Script\ScriptEvents; use Composer\Util\Silencer; 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\Finder\Finder; use Composer\Json\JsonFile; use Composer\Config\JsonConfigSource; use Composer\Util\Filesystem; use Composer\Package\Version\VersionParser; /** * Install a package as new project into new directory. * * @author Benjamin Eberlei * @author Jordi Boggiano * @author Tobias Munk * @author Nils Adermann */ class CreateProjectCommand extends BaseCommand { /** * @var SuggestedPackagesReporter */ protected $suggestedPackagesReporter; protected function configure() { $this ->setName('create-project') ->setDescription('Creates new project from a package into given directory.') ->setDefinition(array( new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed'), new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('repository', null, InputOption::VALUE_REQUIRED, 'Pick a different repository (as url or json config) to look for the package.'), new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), )) ->setHelp(<<create-project command creates a new project from a given package into a new directory. If executed without params and in a directory with a composer.json file it installs the packages for the current project. You can use this command to bootstrap new projects or setup a clean version-controlled installation for developers of your project. php composer.phar create-project vendor/project target-directory [version] You can also specify the version with the package name using = or : as separator. php composer.phar create-project vendor/project:version target-directory To install unstable packages, either specify the version you want, or use the --stability=dev (where dev can be one of RC, beta, alpha or dev). To setup a developer workable version you should create the project using the source controlled code by appending the '--prefer-source' flag. To install a package from another repository than the default one you can pass the '--repository=https://myrepository.org' flag. EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $config = Factory::createConfig(); $io = $this->getIO(); list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input, true); if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } if ($input->getOption('no-custom-installers')) { $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } return $this->installProject( $io, $config, $input, $input->getArgument('package'), $input->getArgument('directory'), $input->getArgument('version'), $input->getOption('stability'), $preferSource, $preferDist, !$input->getOption('no-dev'), $input->getOption('repository') ?: $input->getOption('repository-url'), $input->getOption('no-plugins'), $input->getOption('no-scripts'), $input->getOption('keep-vcs'), $input->getOption('no-progress'), $input->getOption('no-install'), $input->getOption('ignore-platform-reqs'), !$input->getOption('no-secure-http') ); } public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true) { $oldCwd = getcwd(); // we need to manually load the configuration to pass the auth credentials to the io interface! $io->loadConfiguration($config); $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); if ($packageName !== null) { $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $keepVcs, $noProgress, $ignorePlatformReqs, $secureHttp); } else { $installedFromVcs = false; } $composer = Factory::create($io, null, $disablePlugins); $composer->getDownloadManager()->setOutputProgress(!$noProgress); $fs = new Filesystem(); if ($noScripts === false) { // dispatch event $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); } list($preferSource, $preferDist) = $this->getPreferredInstallOptions($composer->getConfig(), $input); // install dependencies of the created project if ($noInstall === false) { $installer = Installer::create($io, $composer); $installer->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode($installDevPackages) ->setRunScripts(!$noScripts) ->setIgnorePlatformRequirements($ignorePlatformReqs) ->setSuggestedPackagesReporter($this->suggestedPackagesReporter); if ($disablePlugins) { $installer->disablePlugins(); } $status = $installer->run(); if (0 !== $status) { return $status; } } $hasVcs = $installedFromVcs; if (!$keepVcs && $installedFromVcs && ( !$io->isInteractive() || $io->askConfirmation('Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? ', true) ) ) { $finder = new Finder(); $finder->depth(0)->directories()->in(getcwd())->ignoreVCS(false)->ignoreDotFiles(false); foreach (array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_') as $vcsName) { $finder->name($vcsName); } try { $dirs = iterator_to_array($finder); unset($finder); foreach ($dirs as $dir) { if (!$fs->removeDirectory($dir)) { throw new \RuntimeException('Could not remove '.$dir); } } } catch (\Exception $e) { $io->writeError('An error occurred while removing the VCS metadata: '.$e->getMessage().''); } $hasVcs = false; } // rewriting self.version dependencies with explicit version numbers if the package's vcs metadata is gone if (!$hasVcs) { $package = $composer->getPackage(); $configSource = new JsonConfigSource(new JsonFile('composer.json')); foreach (BasePackage::$supportedLinkTypes as $type => $meta) { foreach ($package->{'get'.$meta['method']}() as $link) { if ($link->getPrettyConstraint() === 'self.version') { $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion()); } } } } if ($noScripts === false) { // dispatch event $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); } chdir($oldCwd); $vendorComposerDir = $composer->getConfig()->get('vendor-dir').'/composer'; if (is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) { Silencer::call('rmdir', $vendorComposerDir); $vendorDir = $composer->getConfig()->get('vendor-dir'); if (is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) { Silencer::call('rmdir', $vendorDir); } } return 0; } protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $ignorePlatformReqs = false, $secureHttp = true) { if (!$secureHttp) { $config->merge(array('config' => array('secure-http' => false))); } if (null === $repository) { $sourceRepo = new CompositeRepository(RepositoryFactory::defaultRepos($io, $config)); } else { $sourceRepo = RepositoryFactory::fromString($io, $config, $repository, true); } $parser = new VersionParser(); $requirements = $parser->parseNameVersionPairs(array($packageName)); $name = strtolower($requirements[0]['name']); if (!$packageVersion && isset($requirements[0]['version'])) { $packageVersion = $requirements[0]['version']; } if (null === $stability) { if (preg_match('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $packageVersion, $match)) { $stability = $match[1]; } else { $stability = VersionParser::parseStability($packageVersion); } } $stability = VersionParser::normalizeStability($stability); if (!isset(BasePackage::$stabilities[$stability])) { throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities))); } $pool = new Pool($stability); $pool->addRepository($sourceRepo); $phpVersion = null; $prettyPhpVersion = null; if (!$ignorePlatformReqs) { $platformOverrides = $config->get('platform') ?: array(); // initialize $this->repos as it is used by the parent InitCommand $platform = new PlatformRepository(array(), $platformOverrides); $phpPackage = $platform->findPackage('php', '*'); $phpVersion = $phpPackage->getVersion(); $prettyPhpVersion = $phpPackage->getPrettyVersion(); } // find the latest version if there are multiple $versionSelector = new VersionSelector($pool); $package = $versionSelector->findBestCandidate($name, $packageVersion, $phpVersion, $stability); if (!$package) { $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability"); if ($phpVersion && $versionSelector->findBestCandidate($name, $packageVersion, null, $stability)) { throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version '.$prettyPhpVersion.'.'); } throw new \InvalidArgumentException($errorMessage .'.'); } if (null === $directory) { $parts = explode("/", $name, 2); $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts); } // handler Ctrl+C for unix-like systems if (function_exists('pcntl_signal')) { declare(ticks=100); pcntl_signal(SIGINT, function () use ($directory) { $fs = new Filesystem(); $fs->removeDirectory($directory); exit(130); }); } $io->writeError('Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')'); if ($disablePlugins) { $io->writeError('Plugins have been disabled.'); } if ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) { $package->setSourceReference(substr($package->getPrettyVersion(), 4)); } $dm = $this->createDownloadManager($io, $config); $dm->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setOutputProgress(!$noProgress); $projectInstaller = new ProjectInstaller($directory, $dm); $im = $this->createInstallationManager(); $im->addInstaller($projectInstaller); $im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package)); $im->notifyInstalls($io); // collect suggestions $this->suggestedPackagesReporter->addSuggestionsFromPackage($package); $installedFromVcs = 'source' === $package->getInstallationSource(); $io->writeError('Created project in ' . $directory . ''); chdir($directory); $_SERVER['COMPOSER_ROOT_VERSION'] = $package->getPrettyVersion(); putenv('COMPOSER_ROOT_VERSION='.$_SERVER['COMPOSER_ROOT_VERSION']); return $installedFromVcs; } protected function createDownloadManager(IOInterface $io, Config $config) { $factory = new Factory(); return $factory->createDownloadManager($io, $config); } protected function createInstallationManager() { return new InstallationManager(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; /** * @author Davey Shafik */ class ExecCommand extends BaseCommand { protected function configure() { $this ->setName('exec') ->setDescription('Executes a vendored binary/script.') ->setDefinition(array( new InputOption('list', 'l', InputOption::VALUE_NONE), new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit'), new InputArgument( 'args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Arguments to pass to the binary. Use -- to separate from composer arguments' ), )) ; } protected function execute(InputInterface $input, OutputInterface $output) { $composer = $this->getComposer(); $binDir = $composer->getConfig()->get('bin-dir'); if ($input->getOption('list') || !$input->getArgument('binary')) { $bins = glob($binDir . '/*'); $bins = array_merge($bins, array_map(function ($e) { return "$e (local)"; }, $composer->getPackage()->getBinaries())); if (!$bins) { throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)"); } $this->getIO()->write(<<Available binaries: EOT ); foreach ($bins as $bin) { // skip .bat copies if (isset($previousBin) && $bin === $previousBin.'.bat') { continue; } $previousBin = $bin; $bin = basename($bin); $this->getIO()->write(<<- $bin EOT ); } return 0; } $binary = $input->getArgument('binary'); $dispatcher = $composer->getEventDispatcher(); $dispatcher->addListener('__exec_command', $binary); if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); } return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args')); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class OutdatedCommand extends ShowCommand { protected function configure() { $this ->setName('outdated') ->setDescription('Shows a list of installed packages that have updates available, including their latest version.') ->setDefinition(array( new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'), new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), )) ->setHelp(<<green (=): Dependency is in the latest version and is up to date. - yellow (~): Dependency has a new version available that includes backwards compatibility breaks according to semver, so upgrade when you can but it may involve work. - red (!): Dependency has a new version that is semver-compatible and you should upgrade it. EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $args = array( 'show', '--latest' => true, ); if (!$input->getOption('all')) { $args['--outdated'] = true; } if ($input->getOption('direct')) { $args['--direct'] = true; } if ($input->getArgument('package')) { $args['package'] = $input->getArgument('package'); } if ($input->getOption('strict')) { $args['--strict'] = true; } if ($input->getOption('minor-only')) { $args['--minor-only'] = true; } $args['--format'] = $input->getOption('format'); $input = new ArrayInput($args); return $this->getApplication()->run($input, $output); } /** * {@inheritDoc} */ public function isProxyCommand() { return true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Factory; use Composer\Config; use Composer\Util\Filesystem; use Composer\SelfUpdate\Keys; use Composer\SelfUpdate\Versions; use Composer\IO\IOInterface; use Composer\Downloader\FilesystemException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; /** * @author Igor Wiedler * @author Kevin Ran * @author Jordi Boggiano */ class SelfUpdateCommand extends BaseCommand { const HOMEPAGE = 'getcomposer.org'; const OLD_INSTALL_EXT = '-old.phar'; protected function configure() { $this ->setName('self-update') ->setAliases(array('selfupdate')) ->setDescription('Updates composer.phar to the latest version.') ->setDefinition(array( new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'), new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'), new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'), new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'), new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'), new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'), new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'), )) ->setHelp(<<self-update command checks getcomposer.org for newer versions of composer and if found, installs the latest. php composer.phar self-update EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $config = Factory::createConfig(); if ($config->get('disable-tls') === true) { $baseUrl = 'http://' . self::HOMEPAGE; } else { $baseUrl = 'https://' . self::HOMEPAGE; } $io = $this->getIO(); $remoteFilesystem = Factory::createRemoteFilesystem($io, $config); $versionsUtil = new Versions($config, $remoteFilesystem); // switch channel if requested foreach (array('stable', 'preview', 'snapshot') as $channel) { if ($input->getOption($channel)) { $versionsUtil->setChannel($channel); } } if ($input->getOption('set-channel-only')) { return 0; } $cacheDir = $config->get('cache-dir'); $rollbackDir = $config->get('data-dir'); $home = $config->get('home'); $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; if ($input->getOption('update-keys')) { return $this->fetchKeys($io, $config); } // check if current dir is writable and if not try the cache dir from settings $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir; // check for permissions in local filesystem before start connection process if (!is_writable($tmpDir)) { throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); } if ($input->getOption('rollback')) { return $this->rollback($output, $rollbackDir, $localFilename); } $latest = $versionsUtil->getLatest(); $latestVersion = $latest['version']; $updateVersion = $input->getArgument('version') ?: $latestVersion; if (preg_match('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) { $io->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); return 1; } if (Composer::VERSION === $updateVersion) { $io->writeError(sprintf('You are already using composer version %s (%s channel).', $updateVersion, $versionsUtil->getChannel())); // remove all backups except for the most recent, if any if ($input->getOption('clean-backups')) { $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir)); } return 0; } $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp.phar'; $backupFile = sprintf( '%s/%s-%s%s', $rollbackDir, strtr(Composer::RELEASE_DATE, ' :', '_-'), preg_replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION), self::OLD_INSTALL_EXT ); $updatingToTag = !preg_match('{^[0-9a-f]{40}$}', $updateVersion); $io->write(sprintf("Updating to version %s (%s channel).", $updateVersion, $versionsUtil->getChannel())); $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar'); $signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false); $io->writeError(' ', false); $remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress')); $io->writeError(''); if (!file_exists($tempFilename) || !$signature) { $io->writeError('The download of the new composer version failed for an unexpected reason'); return 1; } // verify phar signature if (!extension_loaded('openssl') && $config->get('disable-tls')) { $io->writeError('Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls'); } else { if (!extension_loaded('openssl')) { throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. ' . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub'); if (!file_exists($sigFile)) { file_put_contents($home.'/keys.dev.pub', <<getOption('clean-backups')) { $this->cleanBackups($rollbackDir); } if ($err = $this->setLocalPhar($localFilename, $tempFilename, $backupFile)) { @unlink($tempFilename); $io->writeError('The file is corrupted ('.$err->getMessage().').'); $io->writeError('Please re-run the self-update command to try again.'); return 1; } if (file_exists($backupFile)) { $io->writeError('Use composer self-update --rollback to return to version '.Composer::VERSION); } else { $io->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); } } protected function fetchKeys(IOInterface $io, Config $config) { if (!$io->isInteractive()) { throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively'); } $io->write('Open https://composer.github.io/pubkeys.html to find the latest keys'); $validator = function ($value) { if (!preg_match('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) { throw new \UnexpectedValueException('Invalid input'); } return trim($value)."\n"; }; $devKey = ''; while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) { $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator); while ($line = $io->ask('')) { $devKey .= trim($line)."\n"; if (trim($line) === '-----END PUBLIC KEY-----') { break; } } } file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $tagsKey = ''; while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) { $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator); while ($line = $io->ask('')) { $tagsKey .= trim($line)."\n"; if (trim($line) === '-----END PUBLIC KEY-----') { break; } } } file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $io->write('Public keys stored in '.$config->get('home')); } protected function rollback(OutputInterface $output, $rollbackDir, $localFilename) { $rollbackVersion = $this->getLastBackupVersion($rollbackDir); if (!$rollbackVersion) { throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"'); } $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT; if (!is_file($oldFile)) { throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be found'); } if (!is_readable($oldFile)) { throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be read'); } $io = $this->getIO(); $io->writeError(sprintf("Rolling back to version %s.", $rollbackVersion)); if ($err = $this->setLocalPhar($localFilename, $oldFile)) { $io->writeError('The backup file was corrupted ('.$err->getMessage().').'); return 1; } return 0; } /** * @param string $localFilename * @param string $newFilename * @param string $backupTarget * @throws \Exception * @return \UnexpectedValueException|\PharException|null */ protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null) { try { @chmod($newFilename, fileperms($localFilename)); if (!ini_get('phar.readonly')) { // test the phar validity $phar = new \Phar($newFilename); // free the variable to unlock the file unset($phar); } // copy current file into installations dir if ($backupTarget && file_exists($localFilename)) { @copy($localFilename, $backupTarget); } rename($newFilename, $localFilename); return null; } catch (\Exception $e) { if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) { throw $e; } return $e; } } protected function cleanBackups($rollbackDir, $except = null) { $finder = $this->getOldInstallationFinder($rollbackDir); $io = $this->getIO(); $fs = new Filesystem; foreach ($finder as $file) { if ($except && $file->getBasename(self::OLD_INSTALL_EXT) === $except) { continue; } $file = (string) $file; $io->writeError('Removing: '.$file.''); $fs->remove($file); } } protected function getLastBackupVersion($rollbackDir) { $finder = $this->getOldInstallationFinder($rollbackDir); $finder->sortByName(); $files = iterator_to_array($finder); if (count($files)) { return basename(end($files), self::OLD_INSTALL_EXT); } return false; } protected function getOldInstallationFinder($rollbackDir) { $finder = Finder::create() ->depth(0) ->files() ->name('*' . self::OLD_INSTALL_EXT) ->in($rollbackDir); return $finder; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class ScriptAliasCommand extends BaseCommand { private $script; public function __construct($script) { $this->script = $script; parent::__construct(); } protected function configure() { $this ->setName($this->script) ->setDescription('Runs the '.$this->script.' script as defined in composer.json.') ->setDefinition(array( new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), )) ->setHelp(<<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $composer = $this->getComposer(); $args = $input->getArguments(); return $composer->getEventDispatcher()->dispatchScript($this->script, $input->getOption('dev') || !$input->getOption('no-dev'), $args['args']); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Downloader\ChangeReportInterface; use Composer\Downloader\DvcsDownloaderInterface; use Composer\Downloader\VcsCapableDownloaderInterface; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Script\ScriptEvents; use Composer\Util\ProcessExecutor; /** * @author Tiago Ribeiro * @author Rui Marinho */ class StatusCommand extends BaseCommand { const EXIT_CODE_ERRORS = 1; const EXIT_CODE_UNPUSHED_CHANGES = 2; const EXIT_CODE_VERSION_CHANGES = 4; protected function configure() { $this ->setName('status') ->setDescription('Shows a list of locally modified packages.') ->setDefinition(array( new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'), )) ->setHelp(<<getComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); $dm = $composer->getDownloadManager(); $im = $composer->getInstallationManager(); // Dispatch pre-status-command $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true); $errors = array(); $io = $this->getIO(); $unpushedChanges = array(); $vcsVersionChanges = array(); $parser = new VersionParser; $guesser = new VersionGuesser($composer->getConfig(), new ProcessExecutor($io), $parser); $dumper = new ArrayDumper; // list packages foreach ($installedRepo->getCanonicalPackages() as $package) { $downloader = $dm->getDownloaderForInstalledPackage($package); $targetDir = $im->getInstallPath($package); if ($downloader instanceof ChangeReportInterface) { if (is_link($targetDir)) { $errors[$targetDir] = $targetDir . ' is a symbolic link.'; } if ($changes = $downloader->getLocalChanges($package, $targetDir)) { $errors[$targetDir] = $changes; } } if ($downloader instanceof VcsCapableDownloaderInterface) { if ($currentRef = $downloader->getVcsReference($package, $targetDir)) { switch ($package->getInstallationSource()) { case 'source': $previousRef = $package->getSourceReference(); break; case 'dist': $previousRef = $package->getDistReference(); break; default: $previousRef = null; } $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef) { $vcsVersionChanges[$targetDir] = array( 'previous' => array( 'version' => $package->getPrettyVersion(), 'ref' => $previousRef, ), 'current' => array( 'version' => $currentVersion['pretty_version'], 'ref' => $currentVersion['commit'], ), ); } } } if ($downloader instanceof DvcsDownloaderInterface) { if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { $unpushedChanges[$targetDir] = $unpushed; } } } // output errors/warnings if (!$errors && !$unpushedChanges && !$vcsVersionChanges) { $io->writeError('No local changes'); return 0; } if ($errors) { $io->writeError('You have changes in the following dependencies:'); foreach ($errors as $path => $changes) { if ($input->getOption('verbose')) { $indentedChanges = implode("\n", array_map(function ($line) { return ' ' . ltrim($line); }, explode("\n", $changes))); $io->write(''.$path.':'); $io->write($indentedChanges); } else { $io->write($path); } } } if ($unpushedChanges) { $io->writeError('You have unpushed changes on the current branch in the following dependencies:'); foreach ($unpushedChanges as $path => $changes) { if ($input->getOption('verbose')) { $indentedChanges = implode("\n", array_map(function ($line) { return ' ' . ltrim($line); }, explode("\n", $changes))); $io->write(''.$path.':'); $io->write($indentedChanges); } else { $io->write($path); } } } if ($vcsVersionChanges) { $io->writeError('You have version variations in the following dependencies:'); foreach ($vcsVersionChanges as $path => $changes) { if ($input->getOption('verbose')) { // If we don't can't find a version, use the ref instead. $currentVersion = $changes['current']['version'] ?: $changes['current']['ref']; $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref']; if ($io->isVeryVerbose()) { // Output the ref regardless of whether or not it's being used as the version $currentVersion .= sprintf(' (%s)', $changes['current']['ref']); $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']); } $io->write(''.$path.':'); $io->write(sprintf(' From %s to %s', $previousVersion, $currentVersion)); } else { $io->write($path); } } } if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) { $io->writeError('Use --verbose (-v) to see a list of files'); } // Dispatch post-status-command $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true); return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Util\ConfigValidator; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * ValidateCommand * * @author Robert Schönthal * @author Jordi Boggiano */ class ValidateCommand extends BaseCommand { /** * configure */ protected function configure() { $this ->setName('validate') ->setDescription('Validates a composer.json and composer.lock.') ->setDefinition(array( new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not make a complete validation'), new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'), new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json'), )) ->setHelp(<<getArgument('file'); $io = $this->getIO(); if (!file_exists($file)) { $io->writeError('' . $file . ' not found.'); return 3; } if (!is_readable($file)) { $io->writeError('' . $file . ' is not readable.'); return 3; } $validator = new ConfigValidator($io); $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL; $checkPublish = !$input->getOption('no-check-publish'); $checkLock = !$input->getOption('no-check-lock'); $isStrict = $input->getOption('strict'); list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); $lockErrors = array(); $composer = Factory::create($io, $file); $locker = $composer->getLocker(); if ($locker->isLocked() && !$locker->isFresh()) { $lockErrors[] = 'The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update`.'; } $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); $exitCode = $errors || ($publishErrors && $checkPublish) || ($lockErrors && $checkLock) ? 2 : ($isStrict && $warnings ? 1 : 0); if ($input->getOption('with-dependencies')) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); foreach ($localRepo->getPackages() as $package) { $path = $composer->getInstallationManager()->getInstallPath($package); $file = $path . '/composer.json'; if (is_dir($path) && file_exists($file)) { list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); $depCode = $errors || ($publishErrors && $checkPublish) ? 2 : ($isStrict && $warnings ? 1 : 0); $exitCode = max($depCode, $exitCode); } } } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output); $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $exitCode = max($eventCode, $exitCode); return $exitCode; } private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false) { if (!$errors && !$publishErrors && !$warnings) { $io->write('' . $name . ' is valid'); } elseif (!$errors && !$publishErrors) { $io->writeError('' . $name . ' is valid, but with a few warnings'); if ($printSchemaUrl) { $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); } } elseif (!$errors) { $io->writeError('' . $name . ' is valid for simple usage with composer but has'); $io->writeError('strict errors that make it unable to be published as a package:'); if ($printSchemaUrl) { $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); } } else { $io->writeError('' . $name . ' is invalid, the following errors/warnings were found:'); } // If checking publish errors, display them as errors, otherwise just show them as warnings if ($checkPublish) { $errors = array_merge($errors, $publishErrors); } else { $warnings = array_merge($warnings, $publishErrors); } // If checking lock errors, display them as errors, otherwise just show them as warnings if ($checkLock) { $errors = array_merge($errors, $lockErrors); } else { $warnings = array_merge($warnings, $lockErrors); } $messages = array( 'error' => $errors, 'warning' => $warnings, ); foreach ($messages as $style => $msgs) { foreach ($msgs as $msg) { $io->writeError('<' . $style . '>' . $msg . ''); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\Composer; use Composer\Repository\CompositeRepository; use Composer\Repository\RepositoryFactory; use Composer\Script\ScriptEvents; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Util\Filesystem; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Creates an archive of a package for distribution. * * @author Nils Adermann */ class ArchiveCommand extends BaseCommand { protected function configure() { $this ->setName('archive') ->setDescription('Creates an archive of this composer package.') ->setDefinition(array( new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project'), new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar or zip'), new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'), new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.' .' Note that the format will be appended.'), new InputOption('ignore-filters', false, InputOption::VALUE_NONE, 'Ignore filters when saving package'), )) ->setHelp(<<archive command creates an archive of the specified format containing the files and directories of the Composer project or the specified package in the specified version and writes it to the specified directory. php composer.phar archive [--format=zip] [--dir=/foo] [package [version]] EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $config = Factory::createConfig(); $composer = $this->getComposer(false); if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); } if (null === $input->getOption('format')) { $input->setOption('format', $config->get('archive-format')); } if (null === $input->getOption('dir')) { $input->setOption('dir', $config->get('archive-dir')); } $returnCode = $this->archive( $this->getIO(), $config, $input->getArgument('package'), $input->getArgument('version'), $input->getOption('format'), $input->getOption('dir'), $input->getOption('file'), $input->getOption('ignore-filters'), $composer ); if (0 === $returnCode && $composer) { $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD); } return $returnCode; } protected function archive(IOInterface $io, Config $config, $packageName = null, $version = null, $format = 'tar', $dest = '.', $fileName = null, $ignoreFilters = false, Composer $composer = null) { if ($composer) { $archiveManager = $composer->getArchiveManager(); } else { $factory = new Factory; $downloadManager = $factory->createDownloadManager($io, $config); $archiveManager = $factory->createArchiveManager($config, $downloadManager); } if ($packageName) { $package = $this->selectPackage($io, $packageName, $version); if (!$package) { return 1; } } else { $package = $this->getComposer()->getPackage(); } $io->writeError('Creating the archive into "'.$dest.'".'); $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters); $fs = new Filesystem; $shortPath = $fs->findShortestPath(getcwd(), $packagePath, true); $io->writeError('Created: ', false); $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath); return 0; } protected function selectPackage(IOInterface $io, $packageName, $version = null) { $io->writeError('Searching for the specified package.'); if ($composer = $this->getComposer(false)) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $repo = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories())); } else { $defaultRepos = RepositoryFactory::defaultRepos($this->getIO()); $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); $repo = new CompositeRepository($defaultRepos); } $packages = $repo->findPackages($packageName, $version); if (count($packages) > 1) { $package = reset($packages); $io->writeError('Found multiple matches, selected '.$package->getPrettyString().'.'); $io->writeError('Alternatives were '.implode(', ', array_map(function ($p) { return $p->getPrettyString(); }, $packages)).'.'); $io->writeError('Please use a more specific constraint to pick a different package.'); } elseif ($packages) { $package = reset($packages); $io->writeError('Found an exact match '.$package->getPrettyString().'.'); } else { $io->writeError('Could not find a package matching '.$packageName.'.'); return false; } return $package; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Niels Keurentjes */ class ProhibitsCommand extends BaseDependencyCommand { /** * Configure command metadata. */ protected function configure() { parent::configure(); $this ->setName('prohibits') ->setAliases(array('why-not')) ->setDescription('Shows which packages prevent the given package from being installed.') ->setHelp(<<php composer.phar prohibits composer/composer EOT ) ; } /** * Execute the function. * * @param InputInterface $input * @param OutputInterface $output * @return int|null */ protected function execute(InputInterface $input, OutputInterface $output) { return parent::doExecute($input, $output, true); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Repository\PlatformRepository; 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 SuggestsCommand extends BaseCommand { protected function configure() { $this ->setName('suggests') ->setDescription('Shows package suggestions.') ->setDefinition(array( new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package'), new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.'), )) ->setHelp(<<%command.name% command shows a sorted list of suggested packages. Enabling -v implies --by-package --by-suggestion, showing both lists. EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $lock = $this->getComposer()->getLocker()->getLockData(); if (empty($lock)) { throw new \RuntimeException('Lockfile seems to be empty?'); } $packages = $lock['packages']; if (!$input->getOption('no-dev')) { $packages += $lock['packages-dev']; } $filter = $input->getArgument('packages'); // First assemble lookup list of packages that are installed, replaced or provided $installed = array(); foreach ($packages as $package) { $installed[] = $package['name']; if (!empty($package['provide'])) { $installed = array_merge($installed, array_keys($package['provide'])); } if (!empty($package['replace'])) { $installed = array_merge($installed, array_keys($package['replace'])); } } // Undub and sort the install list into a sorted lookup array $installed = array_flip($installed); ksort($installed); // Init platform repo $platform = new PlatformRepository(array(), $this->getComposer()->getConfig()->get('platform') ?: array()); // Next gather all suggestions that are not in that list $suggesters = array(); $suggested = array(); foreach ($packages as $package) { $packageName = $package['name']; if ((!empty($filter) && !in_array($packageName, $filter)) || empty($package['suggest'])) { continue; } foreach ($package['suggest'] as $suggestion => $reason) { if (false === strpos('/', $suggestion) && !is_null($platform->findPackage($suggestion, '*'))) { continue; } if (!isset($installed[$suggestion])) { $suggesters[$packageName][$suggestion] = $reason; $suggested[$suggestion][$packageName] = $reason; } } } ksort($suggesters); ksort($suggested); // Determine output mode $mode = 0; $io = $this->getIO(); if ($input->getOption('by-package') || $io->isVerbose()) { $mode |= 1; } if ($input->getOption('by-suggestion')) { $mode |= 2; } // Simple mode if ($mode === 0) { foreach (array_keys($suggested) as $suggestion) { $io->write(sprintf('%s', $suggestion)); } return; } // Grouped by package if ($mode & 1) { foreach ($suggesters as $suggester => $suggestions) { $io->write(sprintf('%s suggests:', $suggester)); foreach ($suggestions as $suggestion => $reason) { $io->write(sprintf(' - %s: %s', $suggestion, $reason ?: '*')); } $io->write(''); } } // Grouped by suggestion if ($mode & 2) { // Improve readability in full mode if ($mode & 1) { $io->write(str_repeat('-', 78)); } foreach ($suggested as $suggestion => $suggesters) { $io->write(sprintf('%s is suggested by:', $suggestion)); foreach ($suggesters as $suggester => $reason) { $io->write(sprintf(' - %s: %s', $suggester, $reason ?: '*')); } $io->write(''); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class GlobalCommand extends BaseCommand { protected function configure() { $this ->setName('global') ->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME).') ->setDefinition(array( new InputArgument('command-name', InputArgument::REQUIRED, ''), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), )) ->setHelp(<<\AppData\Roaming\Composer on Windows and /home//.composer on unix systems. If your system uses freedesktop.org standards, then it will first check XDG_CONFIG_HOME or default to /home//.config/composer Note: This path may vary depending on customizations to bin-dir in composer.json or the environmental variable COMPOSER_BIN_DIR. EOT ) ; } public function run(InputInterface $input, OutputInterface $output) { // extract real command name $tokens = preg_split('{\s+}', $input->__toString()); $args = array(); foreach ($tokens as $token) { if ($token && $token[0] !== '-') { $args[] = $token; if (count($args) >= 2) { break; } } } // show help for this command if no command was found if (count($args) < 2) { return parent::run($input, $output); } // change to global dir $config = Factory::createConfig(); chdir($config->get('home')); $this->getIO()->writeError('Changed current directory to '.$config->get('home').''); // create new input without "global" command prefix $input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1)); $this->getApplication()->resetComposer(); return $this->getApplication()->run($input, $output); } /** * {@inheritDoc} */ public function isProxyCommand() { return true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Niels Keurentjes */ class DependsCommand extends BaseDependencyCommand { /** * Configure command metadata. */ protected function configure() { parent::configure(); $this ->setName('depends') ->setAliases(array('why')) ->setDescription('Shows which packages cause the given package to be installed.') ->setHelp(<<php composer.phar depends composer/composer EOT ) ; } /** * Execute the function. * * @param InputInterface $input * @param OutputInterface $output * @return int|null */ protected function execute(InputInterface $input, OutputInterface $output) { return parent::doExecute($input, $output, false); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Script\Event as ScriptEvent; use Composer\Script\ScriptEvents; use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** * @author Fabien Potencier */ class RunScriptCommand extends BaseCommand { /** * @var array Array with command events */ protected $scriptEvents = array( ScriptEvents::PRE_INSTALL_CMD, ScriptEvents::POST_INSTALL_CMD, ScriptEvents::PRE_UPDATE_CMD, ScriptEvents::POST_UPDATE_CMD, ScriptEvents::PRE_STATUS_CMD, ScriptEvents::POST_STATUS_CMD, ScriptEvents::POST_ROOT_PACKAGE_INSTALL, ScriptEvents::POST_CREATE_PROJECT_CMD, ScriptEvents::PRE_ARCHIVE_CMD, ScriptEvents::POST_ARCHIVE_CMD, ScriptEvents::PRE_AUTOLOAD_DUMP, ScriptEvents::POST_AUTOLOAD_DUMP, ); protected function configure() { $this ->setName('run-script') ->setDescription('Runs the scripts defined in composer.json.') ->setDefinition(array( new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'), )) ->setHelp(<<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('list')) { return $this->listScripts(); } elseif (!$input->getArgument('script')) { throw new \RuntimeException('Missing required argument "script"'); } $script = $input->getArgument('script'); if (!in_array($script, $this->scriptEvents)) { if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script)); } } $composer = $this->getComposer(); $devMode = $input->getOption('dev') || !$input->getOption('no-dev'); $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode); $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event); if (!$hasListeners) { throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script)); } $args = $input->getArgument('args'); if (!is_null($timeout = $input->getOption('timeout'))) { if (!ctype_digit($timeout)) { throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever'); } // Override global timeout set before in Composer by environment or config ProcessExecutor::setTimeout((int) $timeout); } return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args); } protected function listScripts() { $scripts = $this->getComposer()->getPackage()->getScripts(); if (!count($scripts)) { return 0; } $io = $this->getIO(); $io->writeError('scripts:'); foreach ($scripts as $name => $script) { $io->write(' ' . $name); } return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; /** * @author Robert Schönthal */ class SearchCommand extends BaseCommand { protected $matches; protected $lowMatches = array(); protected $tokens; protected $output; protected $onlyName; protected function configure() { $this ->setName('search') ->setDescription('Searches for packages.') ->setDefinition(array( new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in name'), new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), )) ->setHelp(<<php composer.phar search symfony composer EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { // init repos $platformRepo = new PlatformRepository; $io = $this->getIO(); if (!($composer = $this->getComposer(false))) { $composer = Factory::create($this->getIO(), array()); } $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $onlyName = $input->getOption('only-name'); $type = $input->getOption('type') ?: null; $flags = $onlyName ? RepositoryInterface::SEARCH_NAME : RepositoryInterface::SEARCH_FULLTEXT; $results = $repos->search(implode(' ', $input->getArgument('tokens')), $flags, $type); foreach ($results as $result) { $io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : '')); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Cache; use Composer\Factory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author David Neilsen */ class ClearCacheCommand extends BaseCommand { protected function configure() { $this ->setName('clear-cache') ->setAliases(array('clearcache')) ->setDescription('Clears composer\'s internal package cache.') ->setHelp(<<clear-cache deletes all cached packages from composer's cache directory. EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $config = Factory::createConfig(); $io = $this->getIO(); $cachePaths = array( 'cache-vcs-dir' => $config->get('cache-vcs-dir'), 'cache-repo-dir' => $config->get('cache-repo-dir'), 'cache-files-dir' => $config->get('cache-files-dir'), 'cache-dir' => $config->get('cache-dir'), ); foreach ($cachePaths as $key => $cachePath) { $cachePath = realpath($cachePath); if (!$cachePath) { $io->writeError("Cache directory does not exist ($key): $cachePath"); continue; } $cache = new Cache($io, $cachePath); if (!$cache->isEnabled()) { $io->writeError("Cache is not enabled ($key): $cachePath"); continue; } $io->writeError("Clearing cache ($key): $cachePath"); $cache->clear(); } $io->writeError('All caches cleared.'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Config; use Composer\Config\JsonConfigSource; use Composer\Factory; use Composer\Json\JsonFile; use Composer\Semver\VersionParser; use Composer\Package\BasePackage; /** * @author Joshua Estes * @author Jordi Boggiano */ class ConfigCommand extends BaseCommand { /** * @var Config */ protected $config; /** * @var JsonFile */ protected $configFile; /** * @var JsonConfigSource */ protected $configSource; /** * @var JsonFile */ protected $authConfigFile; /** * @var JsonConfigSource */ protected $authConfigSource; /** * {@inheritDoc} */ protected function configure() { $this ->setName('config') ->setDescription('Sets config options.') ->setDefinition(array( new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'), new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'), new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), new InputArgument('setting-key', null, 'Setting key'), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), )) ->setHelp(<<%command.full_name% bin-dir bin/ To read a config setting: %command.full_name% bin-dir Outputs: bin To edit the global config.json file: %command.full_name% --global To add a repository: %command.full_name% repositories.foo vcs https://bar.com To remove a repository (repo is a short alias for repositories): %command.full_name% --unset repo.foo To disable packagist: %command.full_name% repo.packagist false You can alter repositories in the global config.json file by passing in the --global option. To edit the file in an external editor: %command.full_name% --editor To choose your editor you can set the "EDITOR" env variable. To get a list of configuration values in the file: %command.full_name% --list You can always pass more than one option. As an example, if you want to edit the global config.json file. %command.full_name% --editor --global EOT ) ; } /** * {@inheritDoc} */ protected function initialize(InputInterface $input, OutputInterface $output) { parent::initialize($input, $output); if ($input->getOption('global') && null !== $input->getOption('file')) { throw new \RuntimeException('--file and --global can not be combined'); } $io = $this->getIO(); $this->config = Factory::createConfig($io); // Get the local composer.json, global config.json, or if the user // passed in a file to use $configFile = $input->getOption('global') ? ($this->config->get('home') . '/config.json') : ($input->getOption('file') ?: Factory::getComposerFile()); // Create global composer.json if this was invoked using `composer global config` if ( ($configFile === 'composer.json' || $configFile === './composer.json') && !file_exists($configFile) && realpath(getcwd()) === realpath($this->config->get('home')) ) { file_put_contents($configFile, "{\n}\n"); } $this->configFile = new JsonFile($configFile, null, $io); $this->configSource = new JsonConfigSource($this->configFile); $authConfigFile = $input->getOption('global') ? ($this->config->get('home') . '/auth.json') : dirname(realpath($configFile)) . '/auth.json'; $this->authConfigFile = new JsonFile($authConfigFile, null, $io); $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true); // Initialize the global file if it's not there, ignoring any warnings or notices if ($input->getOption('global') && !$this->configFile->exists()) { touch($this->configFile->getPath()); $this->configFile->write(array('config' => new \ArrayObject)); Silencer::call('chmod', $this->configFile->getPath(), 0600); } if ($input->getOption('global') && !$this->authConfigFile->exists()) { touch($this->authConfigFile->getPath()); $this->authConfigFile->write(array('bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject)); Silencer::call('chmod', $this->authConfigFile->getPath(), 0600); } if (!$this->configFile->exists()) { throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile)); } } /** * {@inheritDoc} */ protected function execute(InputInterface $input, OutputInterface $output) { // Open file in editor if ($input->getOption('editor')) { $editor = escapeshellcmd(getenv('EDITOR')); if (!$editor) { if (Platform::isWindows()) { $editor = 'notepad'; } else { foreach (array('editor', 'vim', 'vi', 'nano', 'pico', 'ed') as $candidate) { if (exec('which '.$candidate)) { $editor = $candidate; break; } } } } $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`')); return 0; } if (!$input->getOption('global')) { $this->config->merge($this->configFile->read()); $this->config->merge(array('config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : array())); } // List the configuration of the file settings if ($input->getOption('list')) { $this->listConfiguration($this->config->all(), $this->config->raw(), $output); return 0; } $settingKey = $input->getArgument('setting-key'); if (!$settingKey) { return 0; } // If the user enters in a config variable, parse it and save to file if (array() !== $input->getArgument('setting-value') && $input->getOption('unset')) { throw new \RuntimeException('You can not combine a setting value with --unset'); } // show the value if no value is provided if (array() === $input->getArgument('setting-value') && !$input->getOption('unset')) { $properties = array('name', 'type', 'description', 'homepage', 'version', 'minimum-stability', 'prefer-stable', 'keywords', 'license', 'extra'); $rawData = $this->configFile->read(); $data = $this->config->all(); if (preg_match('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { if (!isset($matches[1]) || $matches[1] === '') { $value = isset($data['repositories']) ? $data['repositories'] : array(); } else { if (!isset($data['repositories'][$matches[1]])) { throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined'); } $value = $data['repositories'][$matches[1]]; } } elseif (strpos($settingKey, '.')) { $bits = explode('.', $settingKey); if ($bits[0] === 'extra') { $data = $rawData; } else { $data = $data['config']; } $match = false; foreach ($bits as $bit) { $key = isset($key) ? $key.'.'.$bit : $bit; $match = false; if (isset($data[$key])) { $match = true; $data = $data[$key]; unset($key); } } if (!$match) { throw new \RuntimeException($settingKey.' is not defined.'); } $value = $data; } elseif (isset($data['config'][$settingKey])) { $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); } elseif (in_array($settingKey, $properties, true) && isset($rawData[$settingKey])) { $value = $rawData[$settingKey]; } else { throw new \RuntimeException($settingKey.' is not defined'); } if (is_array($value)) { $value = json_encode($value); } $this->getIO()->write($value); return 0; } $values = $input->getArgument('setting-value'); // what the user is trying to add/change $booleanValidator = function ($val) { return in_array($val, array('true', 'false', '1', '0'), true); }; $booleanNormalizer = function ($val) { return $val !== 'false' && (bool) $val; }; // handle config values $uniqueConfigValues = array( 'process-timeout' => array('is_numeric', 'intval'), 'use-include-path' => array($booleanValidator, $booleanNormalizer), 'preferred-install' => array( function ($val) { return in_array($val, array('auto', 'source', 'dist'), true); }, function ($val) { return $val; }, ), 'store-auths' => array( function ($val) { return in_array($val, array('true', 'false', 'prompt'), true); }, function ($val) { if ('prompt' === $val) { return 'prompt'; } return $val !== 'false' && (bool) $val; }, ), 'notify-on-install' => array($booleanValidator, $booleanNormalizer), 'vendor-dir' => array('is_string', function ($val) { return $val; }), 'bin-dir' => array('is_string', function ($val) { return $val; }), 'archive-dir' => array('is_string', function ($val) { return $val; }), 'archive-format' => array('is_string', function ($val) { return $val; }), 'data-dir' => array('is_string', function ($val) { return $val; }), 'cache-dir' => array('is_string', function ($val) { return $val; }), 'cache-files-dir' => array('is_string', function ($val) { return $val; }), 'cache-repo-dir' => array('is_string', function ($val) { return $val; }), 'cache-vcs-dir' => array('is_string', function ($val) { return $val; }), 'cache-ttl' => array('is_numeric', 'intval'), 'cache-files-ttl' => array('is_numeric', 'intval'), 'cache-files-maxsize' => array( function ($val) { return preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val) > 0; }, function ($val) { return $val; }, ), 'bin-compat' => array( function ($val) { return in_array($val, array('auto', 'full')); }, function ($val) { return $val; }, ), 'discard-changes' => array( function ($val) { return in_array($val, array('stash', 'true', 'false', '1', '0'), true); }, function ($val) { if ('stash' === $val) { return 'stash'; } return $val !== 'false' && (bool) $val; }, ), 'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }), 'sort-packages' => array($booleanValidator, $booleanNormalizer), 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer), 'classmap-authoritative' => array($booleanValidator, $booleanNormalizer), 'apcu-autoloader' => array($booleanValidator, $booleanNormalizer), 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer), 'disable-tls' => array($booleanValidator, $booleanNormalizer), 'secure-http' => array($booleanValidator, $booleanNormalizer), 'cafile' => array( function ($val) { return file_exists($val) && is_readable($val); }, function ($val) { return $val === 'null' ? null : $val; }, ), 'capath' => array( function ($val) { return is_dir($val) && is_readable($val); }, function ($val) { return $val === 'null' ? null : $val; }, ), 'github-expose-hostname' => array($booleanValidator, $booleanNormalizer), 'htaccess-protect' => array($booleanValidator, $booleanNormalizer), ); $multiConfigValues = array( 'github-protocols' => array( function ($vals) { if (!is_array($vals)) { return 'array expected'; } foreach ($vals as $val) { if (!in_array($val, array('git', 'https', 'ssh'))) { return 'valid protocols include: git, https, ssh'; } } return true; }, function ($vals) { return $vals; }, ), 'github-domains' => array( function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, function ($vals) { return $vals; }, ), 'gitlab-domains' => array( function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, function ($vals) { return $vals; }, ), ); if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { return $this->configSource->removeConfigSetting($settingKey); } if (isset($uniqueConfigValues[$settingKey])) { return $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting'); } if (isset($multiConfigValues[$settingKey])) { return $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting'); } // handle properties $uniqueProps = array( 'name' => array('is_string', function ($val) { return $val; }), 'type' => array('is_string', function ($val) { return $val; }), 'description' => array('is_string', function ($val) { return $val; }), 'homepage' => array('is_string', function ($val) { return $val; }), 'version' => array('is_string', function ($val) { return $val; }), 'minimum-stability' => array( function ($val) { return isset(BasePackage::$stabilities[VersionParser::normalizeStability($val)]); }, function ($val) { return VersionParser::normalizeStability($val); }, ), 'prefer-stable' => array($booleanValidator, $booleanNormalizer), ); $multiProps = array( 'keywords' => array( function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, function ($vals) { return $vals; }, ), 'license' => array( function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, function ($vals) { return $vals; }, ), ); if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || substr($settingKey, 0, 6) === 'extra.')) { throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json'); } if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) { return $this->configSource->removeProperty($settingKey); } if (isset($uniqueProps[$settingKey])) { return $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty'); } if (isset($multiProps[$settingKey])) { return $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty'); } // handle repositories if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { return $this->configSource->removeRepository($matches[1]); } if (2 === count($values)) { return $this->configSource->addRepository($matches[1], array( 'type' => $values[0], 'url' => $values[1], )); } if (1 === count($values)) { $value = strtolower($values[0]); if (true === $booleanValidator($value)) { if (false === $booleanNormalizer($value)) { return $this->configSource->addRepository($matches[1], false); } } else { $value = JsonFile::parseJson($values[0]); return $this->configSource->addRepository($matches[1], $value); } } throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com'); } // handle extra if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { return $this->configSource->removeProperty($settingKey); } return $this->configSource->addProperty($settingKey, $values[0]); } // handle platform if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { return $this->configSource->removeConfigSetting($settingKey); } return $this->configSource->addConfigSetting($settingKey, $values[0]); } // handle auth if (preg_match('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic)\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); return; } if ($matches[1] === 'bitbucket-oauth') { if (2 !== count($values)) { throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values)); } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('consumer-key' => $values[0], 'consumer-secret' => $values[1])); } elseif (in_array($matches[1], array('github-oauth', 'gitlab-oauth', 'gitlab-token'), true)) { if (1 !== count($values)) { throw new \RuntimeException('Too many arguments, expected only one token'); } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]); } elseif ($matches[1] === 'http-basic') { if (2 !== count($values)) { throw new \RuntimeException('Expected two arguments (username, password), got '.count($values)); } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1])); } return; } throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); } protected function handleSingleValue($key, array $callbacks, array $values, $method) { list($validator, $normalizer) = $callbacks; if (1 !== count($values)) { throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); } if (true !== $validation = $validator($values[0])) { throw new \RuntimeException(sprintf( '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''), $values[0] )); } return call_user_func(array($this->configSource, $method), $key, $normalizer($values[0])); } protected function handleMultiValue($key, array $callbacks, array $values, $method) { list($validator, $normalizer) = $callbacks; if (true !== $validation = $validator($values)) { throw new \RuntimeException(sprintf( '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''), json_encode($values) )); } return call_user_func(array($this->configSource, $method), $key, $normalizer($values)); } /** * Display the contents of the file in a pretty formatted way * * @param array $contents * @param array $rawContents * @param OutputInterface $output * @param string|null $k */ protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, $k = null) { $origK = $k; $io = $this->getIO(); foreach ($contents as $key => $value) { if ($k === null && !in_array($key, array('config', 'repositories'))) { continue; } $rawVal = isset($rawContents[$key]) ? $rawContents[$key] : null; if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) { $k .= preg_replace('{^config\.}', '', $key . '.'); $this->listConfiguration($value, $rawVal, $output, $k); $k = $origK; continue; } if (is_array($value)) { $value = array_map(function ($val) { return is_array($val) ? json_encode($val) : $val; }, $value); $value = '['.implode(', ', $value).']'; } if (is_bool($value)) { $value = var_export($value, true); } if (is_string($rawVal) && $rawVal != $value) { $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')'); } else { $io->write('[' . $k . $key . '] ' . $value . ''); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Config; use Composer\Console\Application; use Composer\IO\IOInterface; use Composer\IO\NullIO; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command; /** * Base class for Composer commands * * @author Ryan Weaver * @author Konstantin Kudryashov */ abstract class BaseCommand extends Command { /** * @var Composer */ private $composer; /** * @var IOInterface */ private $io; /** * @param bool $required * @param bool|null $disablePlugins * @throws \RuntimeException * @return Composer */ public function getComposer($required = true, $disablePlugins = null) { if (null === $this->composer) { $application = $this->getApplication(); if ($application instanceof Application) { /* @var $application Application */ $this->composer = $application->getComposer($required, $disablePlugins); } elseif ($required) { throw new \RuntimeException( 'Could not create a Composer\Composer instance, you must inject '. 'one if this command is not used with a Composer\Console\Application instance' ); } } return $this->composer; } /** * @param Composer $composer */ public function setComposer(Composer $composer) { $this->composer = $composer; } /** * Removes the cached composer instance */ public function resetComposer() { $this->composer = null; $this->getApplication()->resetComposer(); } /** * Whether or not this command is meant to call another command. * * This is mainly needed to avoid duplicated warnings messages. * * @return bool */ public function isProxyCommand() { return false; } /** * @return IOInterface */ public function getIO() { if (null === $this->io) { $application = $this->getApplication(); if ($application instanceof Application) { /* @var $application Application */ $this->io = $application->getIO(); } else { $this->io = new NullIO(); } } return $this->io; } /** * @param IOInterface $io */ public function setIO(IOInterface $io) { $this->io = $io; } /** * {@inheritDoc} */ protected function initialize(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) { $input->setOption('no-progress', true); } parent::initialize($input, $output); } /** * Returns preferSource and preferDist values based on the configuration. * * @param Config $config * @param InputInterface $input * @param bool $keepVcsRequiresPreferSource * * @return bool[] An array composed of the preferSource and preferDist values */ protected function getPreferredInstallOptions(Config $config, InputInterface $input, $keepVcsRequiresPreferSource = false) { $preferSource = false; $preferDist = false; switch ($config->get('preferred-install')) { case 'source': $preferSource = true; break; case 'dist': $preferDist = true; break; case 'auto': default: // noop break; } if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) { $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')); $preferDist = $input->getOption('prefer-dist'); } return array($preferSource, $preferDist); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class DumpAutoloadCommand extends BaseCommand { protected function configure() { $this ->setName('dump-autoload') ->setAliases(array('dumpautoload')) ->setDescription('Dumps the autoloader.') ->setDefinition(array( new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'), new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'), )) ->setHelp(<<php composer.phar dump-autoload EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $composer = $this->getComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $installationManager = $composer->getInstallationManager(); $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $package = $composer->getPackage(); $config = $composer->getConfig(); $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader'); if ($optimize || $authoritative) { $this->getIO()->writeError('Generating optimized autoload files'); } else { $this->getIO()->writeError('Generating autoload files'); } $generator = $composer->getAutoloadGenerator(); $generator->setDevMode(!$input->getOption('no-dev')); $generator->setClassMapAuthoritative($authoritative); $generator->setApcu($apcu); $generator->setRunScripts(!$input->getOption('no-scripts')); $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Installer; use Composer\IO\IOInterface; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; /** * @author Jordi Boggiano * @author Nils Adermann */ class UpdateCommand extends BaseCommand { protected function configure() { $this ->setName('update') ->setAliases(array('upgrade')) ->setDescription('Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.') ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'), new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'), new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), )) ->setHelp(<<update command reads the composer.json file from the current directory, processes it, and updates, removes or installs all the dependencies. php composer.phar update To limit the update operation to a few packages, you can list the package(s) you want to update as such: php composer.phar update vendor/package1 foo/mypackage [...] You may also use an asterisk (*) pattern to limit the update operation to package(s) from a specific vendor: php composer.phar update vendor/package1 foo/* [...] To select packages names interactively with auto-completion use -i. EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $io = $this->getIO(); if ($input->getOption('no-custom-installers')) { $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } $composer = $this->getComposer(true, $input->getOption('no-plugins')); $packages = $input->getArgument('packages'); if ($input->getOption('interactive')) { $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages); } if ($input->getOption('root-reqs')) { $require = array_keys($composer->getPackage()->getRequires()); if (!$input->getOption('no-dev')) { $requireDev = array_keys($composer->getPackage()->getDevRequires()); $require = array_merge($require, $requireDev); } if (!empty($packages)) { $packages = array_intersect($packages, $require); } else { $packages = $require; } } $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $install = Installer::create($io, $composer); $config = $composer->getConfig(); list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $install ->setDryRun($input->getOption('dry-run')) ->setVerbose($input->getOption('verbose')) ->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode(!$input->getOption('no-dev')) ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setRunScripts(!$input->getOption('no-scripts')) ->setSkipSuggest($input->getOption('no-suggest')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setApcuAutoloader($apcu) ->setUpdate(true) ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $packages) ->setWhitelistDependencies($input->getOption('with-dependencies')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setPreferStable($input->getOption('prefer-stable')) ->setPreferLowest($input->getOption('prefer-lowest')) ; if ($input->getOption('no-plugins')) { $install->disablePlugins(); } return $install->run(); } private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages) { if (!$input->isInteractive()) { throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.'); } $requires = array_merge( $composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires() ); $autocompleterValues = array(); foreach ($requires as $require) { $autocompleterValues[strtolower($require->getTarget())] = $require->getTarget(); } $installedPackages = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); foreach ($installedPackages as $package) { $autocompleterValues[$package->getName()] = $package->getPrettyName(); } $helper = $this->getHelper('question'); $question = new Question('Enter package name: ', null); $io->writeError('Press enter without value to end submission'); do { $autocompleterValues = array_diff($autocompleterValues, $packages); $question->setAutocompleterValues($autocompleterValues); $addedPackage = $helper->ask($input, $output, $question); if (!is_string($addedPackage) || empty($addedPackage)) { break; } $addedPackage = strtolower($addedPackage); if (!in_array($addedPackage, $packages)) { $packages[] = $addedPackage; } } while (true); $packages = array_filter($packages); if (!$packages) { throw new \InvalidArgumentException('You must enter minimum one package.'); } $table = new Table($output); $table->setHeaders(array('Selected packages')); foreach ($packages as $package) { $table->addRow(array($package)); } $table->render(); if ($io->askConfirmation(sprintf( 'Would you like to continue and update the above package%s [yes]? ', 1 === count($packages) ? '' : 's' ), true)) { return $packages; } throw new \RuntimeException('Installation aborted.'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Factory; use Composer\Repository\RepositoryFactory; use Composer\Package\BasePackage; use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionSelector; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; use Symfony\Component\Process\ExecutableFinder; /** * @author Justin Rainbow * @author Jordi Boggiano */ class InitCommand extends BaseCommand { /** @var CompositeRepository */ protected $repos; /** @var array */ private $gitConfig; /** @var Pool */ private $pool; /** * {@inheritdoc} */ protected function configure() { $this ->setName('init') ->setDescription('Creates a basic composer.json file in current directory.') ->setDefinition(array( new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'), new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'), new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), // new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'), new InputOption('type', null, InputOption::VALUE_OPTIONAL, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::$stabilities)).')'), new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), )) ->setHelp(<<init command creates a basic composer.json file in the current directory. php composer.phar init EOT ) ; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $io = $this->getIO(); $whitelist = array('name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license'); $options = array_filter(array_intersect_key($input->getOptions(), array_flip($whitelist))); if (isset($options['author'])) { $options['authors'] = $this->formatAuthors($options['author']); unset($options['author']); } $repositories = $input->getOption('repository'); if ($repositories) { $config = Factory::createConfig($io); foreach ($repositories as $repo) { $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo); } } if (isset($options['stability'])) { $options['minimum-stability'] = $options['stability']; unset($options['stability']); } $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass; if (array() === $options['require']) { $options['require'] = new \stdClass; } if (isset($options['require-dev'])) { $options['require-dev'] = $this->formatRequirements($options['require-dev']); if (array() === $options['require-dev']) { $options['require-dev'] = new \stdClass; } } $file = new JsonFile(Factory::getComposerFile()); $json = $file->encode($options); if ($input->isInteractive()) { $io->writeError(array('', $json, '')); if (!$io->askConfirmation('Do you confirm generation [yes]? ', true)) { $io->writeError('Command aborted'); return 1; } } $file->write($options); if ($input->isInteractive() && is_dir('.git')) { $ignoreFile = realpath('.gitignore'); if (false === $ignoreFile) { $ignoreFile = realpath('.') . '/.gitignore'; } if (!$this->hasVendorIgnore($ignoreFile)) { $question = 'Would you like the vendor directory added to your .gitignore [yes]? '; if ($io->askConfirmation($question, true)) { $this->addVendorIgnore($ignoreFile); } } } } /** * {@inheritdoc} */ protected function interact(InputInterface $input, OutputInterface $output) { $git = $this->getGitConfig(); $io = $this->getIO(); $formatter = $this->getHelperSet()->get('formatter'); // initialize repos if configured $repositories = $input->getOption('repository'); if ($repositories) { $config = Factory::createConfig($io); $repos = array(new PlatformRepository); foreach ($repositories as $repo) { $repos[] = RepositoryFactory::fromString($io, $config, $repo); } $repos[] = RepositoryFactory::createRepo($io, $config, array( 'type' => 'composer', 'url' => 'https://packagist.org', )); $this->repos = new CompositeRepository($repos); unset($repos, $config, $repositories); } $io->writeError(array( '', $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), '', )); // namespace $io->writeError(array( '', 'This command will guide you through creating your composer.json config.', '', )); $cwd = realpath("."); if (!$name = $input->getOption('name')) { $name = basename($cwd); $name = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); $name = strtolower($name); if (isset($git['github.user'])) { $name = $git['github.user'] . '/' . $name; } elseif (!empty($_SERVER['USERNAME'])) { $name = $_SERVER['USERNAME'] . '/' . $name; } elseif (!empty($_SERVER['USER'])) { $name = $_SERVER['USER'] . '/' . $name; } elseif (get_current_user()) { $name = get_current_user() . '/' . $name; } else { // package names must be in the format foo/bar $name = $name . '/' . $name; } $name = strtolower($name); } else { if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $name)) { throw new \InvalidArgumentException( 'The package name '.$name.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); } } $name = $io->askAndValidate( 'Package name (/) ['.$name.']: ', function ($value) use ($name) { if (null === $value) { return $name; } if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $value)) { throw new \InvalidArgumentException( 'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); } return $value; }, null, $name ); $input->setOption('name', $name); $description = $input->getOption('description') ?: false; $description = $io->ask( 'Description ['.$description.']: ', $description ); $input->setOption('description', $description); if (null === $author = $input->getOption('author')) { if (isset($git['user.name']) && isset($git['user.email'])) { $author = sprintf('%s <%s>', $git['user.name'], $git['user.email']); } } $self = $this; $author = $io->askAndValidate( 'Author ['.$author.', n to skip]: ', function ($value) use ($self, $author) { if ($value === 'n' || $value === 'no') { return; } $value = $value ?: $author; $author = $self->parseAuthorString($value); return sprintf('%s <%s>', $author['name'], $author['email']); }, null, $author ); $input->setOption('author', $author); $minimumStability = $input->getOption('stability') ?: null; $minimumStability = $io->askAndValidate( 'Minimum Stability ['.$minimumStability.']: ', function ($value) use ($self, $minimumStability) { if (null === $value) { return $minimumStability; } if (!isset(BasePackage::$stabilities[$value])) { throw new \InvalidArgumentException( 'Invalid minimum stability "'.$value.'". Must be empty or one of: '. implode(', ', array_keys(BasePackage::$stabilities)) ); } return $value; }, null, $minimumStability ); $input->setOption('stability', $minimumStability); $type = $input->getOption('type') ?: false; $type = $io->ask( 'Package Type (e.g. library, project, metapackage, composer-plugin) ['.$type.']: ', $type ); $input->setOption('type', $type); $license = $input->getOption('license') ?: false; $license = $io->ask( 'License ['.$license.']: ', $license ); $input->setOption('license', $license); $io->writeError(array('', 'Define your dependencies.', '')); $question = 'Would you like to define your dependencies (require) interactively [yes]? '; $require = $input->getOption('require'); $requirements = array(); if ($require || $io->askConfirmation($question, true)) { $requirements = $this->determineRequirements($input, $output, $require); } $input->setOption('require', $requirements); $question = 'Would you like to define your dev dependencies (require-dev) interactively [yes]? '; $requireDev = $input->getOption('require-dev'); $devRequirements = array(); if ($requireDev || $io->askConfirmation($question, true)) { $devRequirements = $this->determineRequirements($input, $output, $requireDev); } $input->setOption('require-dev', $devRequirements); } /** * @private * @param string $author * @return array */ public function parseAuthorString($author) { if (preg_match('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+) <(?P.+?)>$/u', $author, $match)) { if ($this->isValidEmail($match['email'])) { return array( 'name' => trim($match['name']), 'email' => $match['email'], ); } } throw new \InvalidArgumentException( 'Invalid author string. Must be in the format: '. 'John Smith ' ); } protected function findPackages($name) { return $this->getRepos()->search($name); } protected function getRepos() { if (!$this->repos) { $this->repos = new CompositeRepository(array_merge( array(new PlatformRepository), RepositoryFactory::defaultRepos($this->getIO()) )); } return $this->repos; } protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable') { if ($requires) { $requires = $this->normalizeRequirements($requires); $result = array(); $io = $this->getIO(); foreach ($requires as $requirement) { if (!isset($requirement['version'])) { // determine the best version automatically $version = $this->findBestVersionForPackage($input, $requirement['name'], $phpVersion, $preferredStability); $requirement['version'] = $version; $io->writeError(sprintf( 'Using version %s for %s', $requirement['version'], $requirement['name'] )); } $result[] = $requirement['name'] . ' ' . $requirement['version']; } return $result; } $versionParser = new VersionParser(); $io = $this->getIO(); while (null !== $package = $io->ask('Search for a package: ')) { $matches = $this->findPackages($package); if (count($matches)) { $exactMatch = null; $choices = array(); foreach ($matches as $position => $foundPackage) { $choices[] = sprintf(' %5s %s', "[$position]", $foundPackage['name']); if ($foundPackage['name'] === $package) { $exactMatch = true; break; } } // no match, prompt which to pick if (!$exactMatch) { $io->writeError(array( '', sprintf('Found %s packages matching %s', count($matches), $package), '', )); $io->writeError($choices); $io->writeError(''); $validator = function ($selection) use ($matches, $versionParser) { if ('' === $selection) { return false; } if (is_numeric($selection) && isset($matches[(int) $selection])) { $package = $matches[(int) $selection]; return $package['name']; } if (preg_match('{^\s*(?P[\S/]+)(?:\s+(?P\S+))?\s*$}', $selection, $packageMatches)) { if (isset($packageMatches['version'])) { // parsing `acme/example ~2.3` // validate version constraint $versionParser->parseConstraints($packageMatches['version']); return $packageMatches['name'].' '.$packageMatches['version']; } // parsing `acme/example` return $packageMatches['name']; } throw new \Exception('Not a valid selection'); }; $package = $io->askAndValidate( 'Enter package # to add, or the complete package name if it is not listed: ', $validator, 3, false ); } // no constraint yet, determine the best version automatically if (false !== $package && false === strpos($package, ' ')) { $validator = function ($input) { $input = trim($input); return $input ?: false; }; $constraint = $io->askAndValidate( 'Enter the version constraint to require (or leave blank to use the latest version): ', $validator, 3, false ); if (false === $constraint) { $constraint = $this->findBestVersionForPackage($input, $package, $phpVersion, $preferredStability); $io->writeError(sprintf( 'Using version %s for %s', $constraint, $package )); } $package .= ' '.$constraint; } if (false !== $package) { $requires[] = $package; } } } return $requires; } protected function formatAuthors($author) { return array($this->parseAuthorString($author)); } protected function formatRequirements(array $requirements) { $requires = array(); $requirements = $this->normalizeRequirements($requirements); foreach ($requirements as $requirement) { $requires[$requirement['name']] = $requirement['version']; } return $requires; } protected function getGitConfig() { if (null !== $this->gitConfig) { return $this->gitConfig; } $finder = new ExecutableFinder(); $gitBin = $finder->find('git'); $cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin))); $cmd->run(); if ($cmd->isSuccessful()) { $this->gitConfig = array(); preg_match_all('{^([^=]+)=(.*)$}m', $cmd->getOutput(), $matches, PREG_SET_ORDER); foreach ($matches as $match) { $this->gitConfig[$match[1]] = $match[2]; } return $this->gitConfig; } return $this->gitConfig = array(); } /** * Checks the local .gitignore file for the Composer vendor directory. * * Tested patterns include: * "/$vendor" * "$vendor" * "$vendor/" * "/$vendor/" * "/$vendor/*" * "$vendor/*" * * @param string $ignoreFile * @param string $vendor * * @return bool */ protected function hasVendorIgnore($ignoreFile, $vendor = 'vendor') { if (!file_exists($ignoreFile)) { return false; } $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor)); $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES); foreach ($lines as $line) { if (preg_match($pattern, $line)) { return true; } } return false; } protected function normalizeRequirements(array $requirements) { $parser = new VersionParser(); return $parser->parseNameVersionPairs($requirements); } protected function addVendorIgnore($ignoreFile, $vendor = '/vendor/') { $contents = ""; if (file_exists($ignoreFile)) { $contents = file_get_contents($ignoreFile); if ("\n" !== substr($contents, 0, -1)) { $contents .= "\n"; } } file_put_contents($ignoreFile, $contents . $vendor. "\n"); } protected function isValidEmail($email) { // assume it's valid if we can't validate it if (!function_exists('filter_var')) { return true; } // php <5.3.3 has a very broken email validator, so bypass checks if (PHP_VERSION_ID < 50303) { return true; } return false !== filter_var($email, FILTER_VALIDATE_EMAIL); } private function getPool(InputInterface $input) { if (!$this->pool) { $this->pool = new Pool($this->getMinimumStability($input)); $this->pool->addRepository($this->getRepos()); } return $this->pool; } private function getMinimumStability(InputInterface $input) { if ($input->hasOption('stability')) { return $input->getOption('stability') ?: 'stable'; } $file = Factory::getComposerFile(); if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) { if (!empty($composer['minimum-stability'])) { return $composer['minimum-stability']; } } return 'stable'; } /** * Given a package name, this determines the best version to use in the require key. * * This returns a version with the ~ operator prefixed when possible. * * @param InputInterface $input * @param string $name * @param string $phpVersion * @param string $preferredStability * @throws \InvalidArgumentException * @return string */ private function findBestVersionForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable') { // find the latest version allowed in this pool $versionSelector = new VersionSelector($this->getPool($input)); $package = $versionSelector->findBestCandidate($name, null, $phpVersion, $preferredStability); if (!$package) { // Check whether the PHP version was the problem if ($phpVersion && $versionSelector->findBestCandidate($name)) { throw new \InvalidArgumentException(sprintf( 'Could not find package %s at any version matching your PHP version %s', $name, $phpVersion )); } throw new \InvalidArgumentException(sprintf( 'Could not find package %s at any version for your minimum-stability (%s). Check the package spelling or your minimum-stability', $name, $this->getMinimumStability($input) )); } return $versionSelector->findRecommendedRequireVersion($package); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Package\CompletePackageInterface; use Composer\Repository\RepositoryInterface; use Composer\Repository\ArrayRepository; use Composer\Repository\RepositoryFactory; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Robert Schönthal */ class HomeCommand extends BaseCommand { /** * {@inheritDoc} */ protected function configure() { $this ->setName('browse') ->setAliases(array('home')) ->setDescription('Opens the package\'s repository URL or homepage in your browser.') ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package(s) to browse to.'), new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'), )) ->setHelp(<<initializeRepos(); $io = $this->getIO(); $return = 0; foreach ($input->getArgument('packages') as $packageName) { $handled = false; $packageExists = false; foreach ($repos as $repo) { foreach ($repo->findPackages($packageName) as $package) { $packageExists = true; if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) { $handled = true; break 2; } } } if (!$packageExists) { $return = 1; $io->writeError('Package '.$packageName.' not found'); } if (!$handled) { $return = 1; $io->writeError(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); } } return $return; } private function handlePackage(CompletePackageInterface $package, $showHomepage, $showOnly) { $support = $package->getSupport(); $url = isset($support['source']) ? $support['source'] : $package->getSourceUrl(); if (!$url || $showHomepage) { $url = $package->getHomepage(); } if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) { return false; } if ($showOnly) { $this->getIO()->write(sprintf('%s', $url)); } else { $this->openBrowser($url); } return true; } /** * opens a url in your system default browser * * @param string $url */ private function openBrowser($url) { $url = ProcessExecutor::escape($url); $process = new ProcessExecutor($this->getIO()); if (Platform::isWindows()) { return $process->execute('start "web" explorer "' . $url . '"', $output); } $linux = $process->execute('which xdg-open', $output); $osx = $process->execute('which open', $output); if (0 === $linux) { $process->execute('xdg-open ' . $url, $output); } elseif (0 === $osx) { $process->execute('open ' . $url, $output); } else { $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url); } } /** * Initializes repositories * * Returns an array of repos in order they should be checked in * * @return RepositoryInterface[] */ private function initializeRepos() { $composer = $this->getComposer(false); if ($composer) { return array_merge( array(new ArrayRepository(array($composer->getPackage()))), // root package array($composer->getRepositoryManager()->getLocalRepository()), // installed packages $composer->getRepositoryManager()->getRepositories() // remotes ); } return RepositoryFactory::defaultRepos($this->getIO()); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class AboutCommand extends BaseCommand { protected function configure() { $this ->setName('about') ->setDescription('Shows the short information about Composer.') ->setHelp(<<php composer.phar about EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $this->getIO()->write(<<Composer - Package Management for PHP Composer is a dependency manager tracking local dependencies of your projects and libraries. See https://getcomposer.org/ for more information. EOT ); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Json\JsonFile; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\PackageInterface; use Composer\Repository\RepositoryInterface; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * @author Benoît Merlet */ class LicensesCommand extends BaseCommand { protected function configure() { $this ->setName('licenses') ->setDescription('Shows information about licenses of dependencies.') ->setDefinition(array( new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), )) ->setHelp(<<getComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $root = $composer->getPackage(); $repo = $composer->getRepositoryManager()->getLocalRepository(); if ($input->getOption('no-dev')) { $packages = $this->filterRequiredPackages($repo, $root); } else { $packages = $this->appendPackages($repo->getPackages(), array()); } ksort($packages); $io = $this->getIO(); switch ($format = $input->getOption('format')) { case 'text': $io->write('Name: '.$root->getPrettyName().''); $io->write('Version: '.$root->getFullPrettyVersion().''); $io->write('Licenses: '.(implode(', ', $root->getLicense()) ?: 'none').''); $io->write('Dependencies:'); $io->write(''); $table = new Table($output); $table->setStyle('compact'); $table->getStyle()->setVerticalBorderChar(''); $table->getStyle()->setCellRowContentFormat('%s '); $table->setHeaders(array('Name', 'Version', 'License')); foreach ($packages as $package) { $table->addRow(array( $package->getPrettyName(), $package->getFullPrettyVersion(), implode(', ', $package->getLicense()) ?: 'none', )); } $table->render(); break; case 'json': $dependencies = array(); foreach ($packages as $package) { $dependencies[$package->getPrettyName()] = array( 'version' => $package->getFullPrettyVersion(), 'license' => $package->getLicense(), ); } $io->write(JsonFile::encode(array( 'name' => $root->getPrettyName(), 'version' => $root->getFullPrettyVersion(), 'license' => $root->getLicense(), 'dependencies' => $dependencies, ))); break; default: throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format)); } } /** * Find package requires and child requires * * @param RepositoryInterface $repo * @param PackageInterface $package * @param array $bucket * @return array */ private function filterRequiredPackages(RepositoryInterface $repo, PackageInterface $package, $bucket = array()) { $requires = array_keys($package->getRequires()); $packageListNames = array_keys($bucket); $packages = array_filter( $repo->getPackages(), function ($package) use ($requires, $packageListNames) { return in_array($package->getName(), $requires) && !in_array($package->getName(), $packageListNames); } ); $bucket = $this->appendPackages($packages, $bucket); foreach ($packages as $package) { $bucket = $this->filterRequiredPackages($repo, $package, $bucket); } return $bucket; } /** * Adds packages to the package list * * @param array $packages the list of packages to add * @param array $bucket the list to add packages to * @return array */ public function appendPackages(array $packages, array $bucket) { foreach ($packages as $package) { $bucket[$package->getName()] = $package; } return $bucket; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano * @author Ryan Weaver * @author Konstantin Kudryashov * @author Nils Adermann */ class InstallCommand extends BaseCommand { protected function configure() { $this ->setName('install') ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.') ->setDefinition(array( new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'), )) ->setHelp(<<install command reads the composer.lock file from the current directory, processes it, and downloads and installs all the libraries and dependencies outlined in that file. If the file does not exist it will look for composer.json and do the same. php composer.phar install EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $io = $this->getIO(); if ($args = $input->getArgument('packages')) { $io->writeError('Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.'); return 1; } if ($input->getOption('no-custom-installers')) { $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $install = Installer::create($io, $composer); $config = $composer->getConfig(); list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $install ->setDryRun($input->getOption('dry-run')) ->setVerbose($input->getOption('verbose')) ->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode(!$input->getOption('no-dev')) ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setRunScripts(!$input->getOption('no-scripts')) ->setSkipSuggest($input->getOption('no-suggest')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setApcuAutoloader($apcu) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ; if ($input->getOption('no-plugins')) { $install->disablePlugins(); } return $install->run(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Helper\HelperSet; use Composer\Question\StrictConfirmationQuestion; use Symfony\Component\Console\Question\Question; /** * The Input/Output helper. * * @author François Pluchino * @author Jordi Boggiano */ class ConsoleIO extends BaseIO { /** @var InputInterface */ protected $input; /** @var OutputInterface */ protected $output; /** @var HelperSet */ protected $helperSet; /** @var string */ protected $lastMessage; /** @var string */ protected $lastMessageErr; /** @var float */ private $startTime; /** @var array */ private $verbosityMap; /** * Constructor. * * @param InputInterface $input The input instance * @param OutputInterface $output The output instance * @param HelperSet $helperSet The helperSet instance */ public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet) { $this->input = $input; $this->output = $output; $this->helperSet = $helperSet; $this->verbosityMap = array( self::QUIET => OutputInterface::VERBOSITY_QUIET, self::NORMAL => OutputInterface::VERBOSITY_NORMAL, self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE, self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE, self::DEBUG => OutputInterface::VERBOSITY_DEBUG, ); } /** * @param float $startTime */ public function enableDebugging($startTime) { $this->startTime = $startTime; } /** * {@inheritDoc} */ public function isInteractive() { return $this->input->isInteractive(); } /** * {@inheritDoc} */ public function isDecorated() { return $this->output->isDecorated(); } /** * {@inheritDoc} */ public function isVerbose() { return $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE; } /** * {@inheritDoc} */ public function isVeryVerbose() { return $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE; } /** * {@inheritDoc} */ public function isDebug() { return $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG; } /** * {@inheritDoc} */ public function write($messages, $newline = true, $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, false, $verbosity); } /** * {@inheritDoc} */ public function writeError($messages, $newline = true, $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, true, $verbosity); } /** * @param array|string $messages * @param bool $newline * @param bool $stderr * @param int $verbosity */ private function doWrite($messages, $newline, $stderr, $verbosity) { $sfVerbosity = $this->verbosityMap[$verbosity]; if ($sfVerbosity > $this->output->getVerbosity()) { return; } // hack to keep our usage BC with symfony<2.8 versions // this removes the quiet output but there is no way around it // see https://github.com/composer/composer/pull/4913 if (OutputInterface::VERBOSITY_QUIET === 0) { $sfVerbosity = OutputInterface::OUTPUT_NORMAL; } if (null !== $this->startTime) { $memoryUsage = memory_get_usage() / 1024 / 1024; $timeSpent = microtime(true) - $this->startTime; $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) { return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message); }, (array) $messages); } if (true === $stderr && $this->output instanceof ConsoleOutputInterface) { $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity); $this->lastMessageErr = implode($newline ? "\n" : '', (array) $messages); return; } $this->output->write($messages, $newline, $sfVerbosity); $this->lastMessage = implode($newline ? "\n" : '', (array) $messages); } /** * {@inheritDoc} */ public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL) { $this->doOverwrite($messages, $newline, $size, false, $verbosity); } /** * {@inheritDoc} */ public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL) { $this->doOverwrite($messages, $newline, $size, true, $verbosity); } /** * @param array|string $messages * @param bool $newline * @param int|null $size * @param bool $stderr * @param int $verbosity */ private function doOverwrite($messages, $newline, $size, $stderr, $verbosity) { // messages can be an array, let's convert it to string anyway $messages = implode($newline ? "\n" : '', (array) $messages); // since overwrite is supposed to overwrite last message... if (!isset($size)) { // removing possible formatting of lastMessage with strip_tags $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage)); } // ...let's fill its length with backspaces $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity); // write the new message $this->doWrite($messages, false, $stderr, $verbosity); // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to // track the length of previous output and fill it with spaces to make sure the line is cleared. // See https://github.com/composer/composer/pull/5836 for more details $fill = $size - strlen(strip_tags($messages)); if ($fill > 0) { // whitespace whatever has left $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity); // move the cursor back $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity); } if ($newline) { $this->doWrite('', true, $stderr, $verbosity); } if ($stderr) { $this->lastMessageErr = $messages; } else { $this->lastMessage = $messages; } } /** * {@inheritDoc} */ public function ask($question, $default = null) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new Question($question, $default); return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** * {@inheritDoc} */ public function askConfirmation($question, $default = true) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new StrictConfirmationQuestion($question, $default); return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** * {@inheritDoc} */ public function askAndValidate($question, $validator, $attempts = null, $default = null) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new Question($question, $default); $question->setValidator($validator); $question->setMaxAttempts($attempts); return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** * {@inheritDoc} */ public function askAndHideAnswer($question) { $this->writeError($question, false); return \Seld\CliPrompt\CliPrompt::hiddenPrompt(true); } /** * {@inheritDoc} */ public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) { if ($this->isInteractive()) { return $this->helperSet->get('dialog')->select($this->getErrorOutput(), $question, $choices, $default, $attempts, $errorMessage, $multiselect); } return $default; } /** * @return OutputInterface */ private function getErrorOutput() { if ($this->output instanceof ConsoleOutputInterface) { return $this->output->getErrorOutput(); } return $this->output; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Composer\Config; use Composer\Util\ProcessExecutor; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; abstract class BaseIO implements IOInterface, LoggerInterface { protected $authentications = array(); /** * {@inheritDoc} */ public function getAuthentications() { return $this->authentications; } /** * {@inheritDoc} */ public function hasAuthentication($repositoryName) { return isset($this->authentications[$repositoryName]); } /** * {@inheritDoc} */ public function getAuthentication($repositoryName) { if (isset($this->authentications[$repositoryName])) { return $this->authentications[$repositoryName]; } return array('username' => null, 'password' => null); } /** * {@inheritDoc} */ public function setAuthentication($repositoryName, $username, $password = null) { $this->authentications[$repositoryName] = array('username' => $username, 'password' => $password); } /** * Check for overwrite and set the authentication information for the repository. * * @param string $repositoryName The unique name of repository * @param string $username The username * @param string $password The password */ protected function checkAndSetAuthentication($repositoryName, $username, $password = null) { if ($this->hasAuthentication($repositoryName)) { $auth = $this->getAuthentication($repositoryName); if ($auth['username'] === $username && $auth['password'] === $password) { return; } $this->writeError( sprintf( "Warning: You should avoid overwriting already defined auth settings for %s.", $repositoryName ) ); } $this->setAuthentication($repositoryName, $username, $password); } /** * {@inheritDoc} */ public function loadConfiguration(Config $config) { $bitbucketOauth = $config->get('bitbucket-oauth') ?: array(); $githubOauth = $config->get('github-oauth') ?: array(); $gitlabOauth = $config->get('gitlab-oauth') ?: array(); $gitlabToken = $config->get('gitlab-token') ?: array(); $httpBasic = $config->get('http-basic') ?: array(); // reload oauth tokens from config if available foreach ($bitbucketOauth as $domain => $cred) { $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']); } foreach ($githubOauth as $domain => $token) { if (!preg_match('{^[.a-z0-9]+$}', $token)) { throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'); } $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic'); } foreach ($gitlabOauth as $domain => $token) { $this->checkAndSetAuthentication($domain, $token, 'oauth2'); } foreach ($gitlabToken as $domain => $token) { $this->checkAndSetAuthentication($domain, $token, 'private-token'); } // reload http basic credentials from config if available foreach ($httpBasic as $domain => $cred) { $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']); } // setup process timeout ProcessExecutor::setTimeout((int) $config->get('process-timeout')); } /** * System is unusable. * * @param string $message * @param array $context * @return null */ public function emergency($message, array $context = array()) { return $this->log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context * @return null */ public function alert($message, array $context = array()) { return $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context * @return null */ public function critical($message, array $context = array()) { return $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context * @return null */ public function error($message, array $context = array()) { return $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context * @return null */ public function warning($message, array $context = array()) { return $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param array $context * @return null */ public function notice($message, array $context = array()) { return $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context * @return null */ public function info($message, array $context = array()) { return $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param array $context * @return null */ public function debug($message, array $context = array()) { return $this->log(LogLevel::DEBUG, $message, $context); } /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * @return null */ public function log($level, $message, array $context = array()) { if (in_array($level, array(LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR))) { $this->writeError(''.$message.'', true, self::NORMAL); } elseif ($level === LogLevel::WARNING) { $this->writeError(''.$message.'', true, self::NORMAL); } elseif ($level === LogLevel::NOTICE) { $this->writeError(''.$message.'', true, self::VERBOSE); } elseif ($level === LogLevel::INFO) { $this->writeError(''.$message.'', true, self::VERY_VERBOSE); } else { $this->writeError($message, true, self::DEBUG); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; /** * IOInterface that is not interactive and never writes the output * * @author Christophe Coevoet */ class NullIO extends BaseIO { /** * {@inheritDoc} */ public function isInteractive() { return false; } /** * {@inheritDoc} */ public function isVerbose() { return false; } /** * {@inheritDoc} */ public function isVeryVerbose() { return false; } /** * {@inheritDoc} */ public function isDebug() { return false; } /** * {@inheritDoc} */ public function isDecorated() { return false; } /** * {@inheritDoc} */ public function write($messages, $newline = true, $verbosity = self::NORMAL) { } /** * {@inheritDoc} */ public function writeError($messages, $newline = true, $verbosity = self::NORMAL) { } /** * {@inheritDoc} */ public function overwrite($messages, $newline = true, $size = 80, $verbosity = self::NORMAL) { } /** * {@inheritDoc} */ public function overwriteError($messages, $newline = true, $size = 80, $verbosity = self::NORMAL) { } /** * {@inheritDoc} */ public function ask($question, $default = null) { return $default; } /** * {@inheritDoc} */ public function askConfirmation($question, $default = true) { return $default; } /** * {@inheritDoc} */ public function askAndValidate($question, $validator, $attempts = false, $default = null) { return $default; } /** * {@inheritDoc} */ public function askAndHideAnswer($question) { return null; } /** * {@inheritDoc} */ public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) { return $default; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Helper\HelperSet; /** * @author Jordi Boggiano */ class BufferIO extends ConsoleIO { /** * @param string $input * @param int $verbosity * @param OutputFormatterInterface|null $formatter */ public function __construct($input = '', $verbosity = StreamOutput::VERBOSITY_NORMAL, OutputFormatterInterface $formatter = null) { $input = new StringInput($input); $input->setInteractive(false); $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : false, $formatter); parent::__construct($input, $output, new HelperSet(array())); } public function getOutput() { fseek($this->output->getStream(), 0); $output = stream_get_contents($this->output->getStream()); $output = preg_replace_callback("{(?<=^|\n|\x08)(.+?)(\x08+)}", function ($matches) { $pre = strip_tags($matches[1]); if (strlen($pre) === strlen($matches[2])) { return ''; } // TODO reverse parse the string, skipping span tags and \033\[([0-9;]+)m(.*?)\033\[0m style blobs return rtrim($matches[1])."\n"; }, $output); return $output; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Composer\Config; /** * The Input/Output helper interface. * * @author François Pluchino */ interface IOInterface { const QUIET = 1; const NORMAL = 2; const VERBOSE = 4; const VERY_VERBOSE = 8; const DEBUG = 16; /** * Is this input means interactive? * * @return bool */ public function isInteractive(); /** * Is this output verbose? * * @return bool */ public function isVerbose(); /** * Is the output very verbose? * * @return bool */ public function isVeryVerbose(); /** * Is the output in debug verbosity? * * @return bool */ public function isDebug(); /** * Is this output decorated? * * @return bool */ public function isDecorated(); /** * Writes a message to the output. * * @param string|array $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ public function write($messages, $newline = true, $verbosity = self::NORMAL); /** * Writes a message to the error output. * * @param string|array $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ public function writeError($messages, $newline = true, $verbosity = self::NORMAL); /** * Overwrites a previous message to the output. * * @param string|array $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $size The size of line * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL); /** * Overwrites a previous message to the error output. * * @param string|array $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $size The size of line * @param int $verbosity Verbosity level from the VERBOSITY_* constants */ public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL); /** * Asks a question to the user. * * @param string|array $question The question to ask * @param string $default The default answer if none is given by the user * * @throws \RuntimeException If there is no data to read in the input stream * @return string The user answer */ public function ask($question, $default = null); /** * Asks a confirmation to the user. * * The question will be asked until the user answers by nothing, yes, or no. * * @param string|array $question The question to ask * @param bool $default The default answer if the user enters nothing * * @return bool true if the user has confirmed, false otherwise */ public function askConfirmation($question, $default = true); /** * Asks for a value and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param string|array $question The question to ask * @param callable $validator A PHP callback * @param null|int $attempts Max number of times to ask before giving up (default of null means infinite) * @param mixed $default The default answer if none is given by the user * * @throws \Exception When any of the validators return an error * @return mixed */ public function askAndValidate($question, $validator, $attempts = null, $default = null); /** * Asks a question to the user and hide the answer. * * @param string $question The question to ask * * @return string The answer */ public function askAndHideAnswer($question); /** * Asks the user to select a value. * * @param string|array $question The question to ask * @param array $choices List of choices to pick from * @param bool|string $default The default answer if the user enters nothing * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked * @param bool $multiselect Select more than one value separated by comma * * @throws \InvalidArgumentException * @return int|string|array The selected value or values (the key of the choices array) */ public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false); /** * Get all authentication information entered. * * @return array The map of authentication data */ public function getAuthentications(); /** * Verify if the repository has a authentication information. * * @param string $repositoryName The unique name of repository * * @return bool */ public function hasAuthentication($repositoryName); /** * Get the username and password of repository. * * @param string $repositoryName The unique name of repository * * @return array The 'username' and 'password' */ public function getAuthentication($repositoryName); /** * Set the authentication information for the repository. * * @param string $repositoryName The unique name of repository * @param string $username The username * @param string $password The password */ public function setAuthentication($repositoryName, $username, $password = null); /** * Loads authentications from a config instance * * @param Config $config */ public function loadConfiguration(Config $config); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Question; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Question\Question; /** * Represents a yes/no question * Enforces strict responses rather than non-standard answers counting as default * Based on Symfony\Component\Console\Question\ConfirmationQuestion * * @author Theo Tonge */ class StrictConfirmationQuestion extends Question { private $trueAnswerRegex; private $falseAnswerRegex; /** * Constructor.s * * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param string $trueAnswerRegex A regex to match the "yes" answer * @param string $falseAnswerRegex A regex to match the "no" answer */ public function __construct($question, $default = true, $trueAnswerRegex = '/^y(?:es)?$/i', $falseAnswerRegex = '/^no?$/i') { parent::__construct($question, (bool) $default); $this->trueAnswerRegex = $trueAnswerRegex; $this->falseAnswerRegex = $falseAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); $this->setValidator($this->getDefaultValidator()); } /** * Returns the default answer normalizer. * * @return callable */ private function getDefaultNormalizer() { $default = $this->getDefault(); $trueRegex = $this->trueAnswerRegex; $falseRegex = $this->falseAnswerRegex; return function ($answer) use ($default, $trueRegex, $falseRegex) { if (is_bool($answer)) { return $answer; } if (empty($answer) && !empty($default)) { return $default; } if (preg_match($trueRegex, $answer)) { return true; } if (preg_match($falseRegex, $answer)) { return false; } return null; }; } /** * Returns the default answer validator. * * @return callable */ private function getDefaultValidator() { return function ($answer) { if (!is_bool($answer)) { throw new InvalidArgumentException('Please answer yes, y, no, or n.'); } return $answer; }; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier * @author Jordi Boggiano * @see http://www.php-fig.org/psr/psr-0/ * @see http://www.php-fig.org/psr/psr-4/ */ class ClassLoader { // PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); // PSR-0 private $prefixesPsr0 = array(); private $fallbackDirsPsr0 = array(); private $useIncludePath = false; private $classMap = array(); private $classMapAuthoritative = false; private $missingClasses = array(); private $apcuPrefix; public function getPrefixes() { if (!empty($this->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; } /** * @param array $classMap Class to filename map */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param array|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories */ 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 ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param array|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException */ public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $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) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param array|string $paths The PSR-0 base directories */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param array|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException */ 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; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); } /** * Unregisters this instance as an autoloader. */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); } /** * Loads the given class or interface. * * @param string $class The name of the class * @return bool|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath.'\\'; if (isset($this->prefixDirsPsr4[$search])) { foreach ($this->prefixDirsPsr4[$search] as $dir) { $length = $this->prefixLengthsPsr4[$first][$search]; if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $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; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } /** * Scope isolated include. * * Prevents access to $this/self from included files. */ function includeFile($file) { include $file; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; use Composer\Script\ScriptEvents; /** * @author Igor Wiedler * @author Jordi Boggiano */ class AutoloadGenerator { /** * @var EventDispatcher */ private $eventDispatcher; /** * @var IOInterface */ private $io; /** * @var bool */ private $devMode = false; /** * @var bool */ private $classMapAuthoritative = false; /** * @var bool */ private $apcu = false; /** * @var bool */ private $runScripts = false; public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null) { $this->eventDispatcher = $eventDispatcher; $this->io = $io; } public function setDevMode($devMode = true) { $this->devMode = (bool) $devMode; } /** * Whether or not generated autoloader considers the class map * authoritative. * * @param bool $classMapAuthoritative */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = (bool) $classMapAuthoritative; } /** * Whether or not generated autoloader considers APCu caching. * * @param bool $apcu */ public function setApcu($apcu) { $this->apcu = (bool) $apcu; } /** * Set whether to run scripts or not * * @param bool $runScripts */ public function setRunScripts($runScripts = true) { $this->runScripts = (bool) $runScripts; } public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '') { if ($this->classMapAuthoritative) { // Force scanPsr0Packages when classmap is authoritative $scanPsr0Packages = true; } if ($this->runScripts) { $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, array(), array( 'optimize' => (bool) $scanPsr0Packages, )); } $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($config->get('vendor-dir')); // Do not remove double realpath() calls. // Fixes failing Windows realpath() implementation. // See https://bugs.php.net/bug.php?id=72738 $basePath = $filesystem->normalizePath(realpath(realpath(getcwd()))); $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir')))); $useGlobalIncludePath = (bool) $config->get('use-include-path'); $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; $targetDir = $vendorPath.'/'.$targetDir; $filesystem->ensureDirectoryExists($targetDir); $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true); $vendorPathCode52 = str_replace('__DIR__', 'dirname(__FILE__)', $vendorPathCode); $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true); $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true); $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode); $namespacesFile = <<buildPackageMap($installationManager, $mainPackage, $localRepo->getCanonicalPackages()); $autoloads = $this->parseAutoloads($packageMap, $mainPackage); // Process the 'psr-0' base directories. foreach ($autoloads['psr-0'] as $namespace => $paths) { $exportedPaths = array(); foreach ($paths as $path) { $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); } $exportedPrefix = var_export($namespace, true); $namespacesFile .= " $exportedPrefix => "; $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n"; } $namespacesFile .= ");\n"; // Process the 'psr-4' base directories. foreach ($autoloads['psr-4'] as $namespace => $paths) { $exportedPaths = array(); foreach ($paths as $path) { $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); } $exportedPrefix = var_export($namespace, true); $psr4File .= " $exportedPrefix => "; $psr4File .= "array(".implode(', ', $exportedPaths)."),\n"; } $psr4File .= ");\n"; $classmapFile = <<getAutoload(); if ($mainPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) { $levels = count(explode('/', $filesystem->normalizePath($mainPackage->getTargetDir()))); $prefixes = implode(', ', array_map(function ($prefix) { return var_export($prefix, true); }, array_keys($mainAutoload['psr-0']))); $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true); $targetDirLoader = << $paths) { $namespacesToScan[$namespace][] = array('paths' => $paths, 'type' => $psrType); } } krsort($namespacesToScan); foreach ($namespacesToScan as $namespace => $groups) { foreach ($groups as $group) { foreach ($group['paths'] as $dir) { $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); if (!is_dir($dir)) { continue; } $namespaceFilter = $namespace === '' ? null : $namespace; $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap); } } } } foreach ($autoloads['classmap'] as $dir) { $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap); } ksort($classMap); foreach ($classMap as $class => $code) { $classmapFile .= ' '.var_export($class, true).' => '.$code; } $classmapFile .= ");\n"; if (!$suffix) { if (!$config->get('autoloader-suffix') && is_readable($vendorPath.'/autoload.php')) { $content = file_get_contents($vendorPath.'/autoload.php'); if (preg_match('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) { $suffix = $match[1]; } } if (!$suffix) { $suffix = $config->get('autoloader-suffix') ?: md5(uniqid('', true)); } } file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile); file_put_contents($targetDir.'/autoload_psr4.php', $psr4File); file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile); $includePathFilePath = $targetDir.'/include_paths.php'; if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { file_put_contents($includePathFilePath, $includePathFileContents); } elseif (file_exists($includePathFilePath)) { unlink($includePathFilePath); } $includeFilesFilePath = $targetDir.'/autoload_files.php'; if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { file_put_contents($includeFilesFilePath, $includeFilesFileContents); } elseif (file_exists($includeFilesFilePath)) { unlink($includeFilesFilePath); } file_put_contents($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath, $staticPhpVersion)); file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $staticPhpVersion)); $this->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); $this->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); if ($this->runScripts) { $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array( 'optimize' => (bool) $scanPsr0Packages, )); } } private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array()) { foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) { $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; if (!isset($classMap[$class])) { $classMap[$class] = $pathCode; } elseif ($this->io && $classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class].' '.$path, '\\', '/'))) { $this->io->writeError( 'Warning: Ambiguous class resolution, "'.$class.'"'. ' was found in both "'.str_replace(array('$vendorDir . \'', "',\n"), array($vendorPath, ''), $classMap[$class]).'" and "'.$path.'", the first will be used.' ); } } return $classMap; } private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true) { return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter); } public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages) { // build package => install path map $packageMap = array(array($mainPackage, '')); foreach ($packages as $package) { if ($package instanceof AliasPackage) { continue; } $this->validatePackage($package); $packageMap[] = array( $package, $installationManager->getInstallPath($package), ); } return $packageMap; } /** * @param PackageInterface $package * * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings. */ protected function validatePackage(PackageInterface $package) { $autoload = $package->getAutoload(); if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) { $name = $package->getName(); $package->getTargetDir(); throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'."); } if (!empty($autoload['psr-4'])) { foreach ($autoload['psr-4'] as $namespace => $dirs) { if ($namespace !== '' && '\\' !== substr($namespace, -1)) { throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'."); } } } } /** * Compiles an ordered list of namespace => path mappings * * @param array $packageMap array of array(package, installDir-relative-to-composer.json) * @param PackageInterface $mainPackage root package instance * @return array array('psr-0' => array('Ns\\Foo' => array('installDir'))) */ public function parseAutoloads(array $packageMap, PackageInterface $mainPackage) { $mainPackageMap = array_shift($packageMap); $sortedPackageMap = $this->sortPackageMap($packageMap); $sortedPackageMap[] = $mainPackageMap; array_unshift($packageMap, $mainPackageMap); $psr0 = $this->parseAutoloadsType($packageMap, 'psr-0', $mainPackage); $psr4 = $this->parseAutoloadsType($packageMap, 'psr-4', $mainPackage); $classmap = $this->parseAutoloadsType(array_reverse($sortedPackageMap), 'classmap', $mainPackage); $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $mainPackage); $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $mainPackage); krsort($psr0); krsort($psr4); return array( 'psr-0' => $psr0, 'psr-4' => $psr4, 'classmap' => $classmap, 'files' => $files, 'exclude-from-classmap' => $exclude, ); } /** * Registers an autoloader based on an autoload map returned by parseAutoloads * * @param array $autoloads see parseAutoloads return value * @return ClassLoader */ public function createLoader(array $autoloads) { $loader = new ClassLoader(); if (isset($autoloads['psr-0'])) { foreach ($autoloads['psr-0'] as $namespace => $path) { $loader->add($namespace, $path); } } if (isset($autoloads['psr-4'])) { foreach ($autoloads['psr-4'] as $namespace => $path) { $loader->addPsr4($namespace, $path); } } if (isset($autoloads['classmap'])) { $blacklist = null; if (!empty($autoloads['exclude-from-classmap'])) { $blacklist = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; } foreach ($autoloads['classmap'] as $dir) { try { $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false)); } catch (\RuntimeException $e) { $this->io->writeError(''.$e->getMessage().''); } } } return $loader; } protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode) { $includePaths = array(); foreach ($packageMap as $item) { list($package, $installPath) = $item; if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) { $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); } foreach ($package->getIncludePaths() as $includePath) { $includePath = trim($includePath, '/'); $includePaths[] = empty($installPath) ? $includePath : $installPath.'/'.$includePath; } } if (!$includePaths) { return; } $includePathsCode = ''; foreach ($includePaths as $path) { $includePathsCode .= " " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n"; } return << $functionFile) { $filesCode .= ' ' . var_export($fileIdentifier, true) . ' => ' . $this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile) . ",\n"; } if (!$filesCode) { return false; } return <<isAbsolutePath($path)) { $path = $basePath . '/' . $path; } $path = $filesystem->normalizePath($path); $baseDir = ''; if (strpos($path.'/', $vendorPath.'/') === 0) { $path = substr($path, strlen($vendorPath)); $baseDir = '$vendorDir'; if ($path !== false) { $baseDir .= " . "; } } else { $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, true)); if (!$filesystem->isAbsolutePath($path)) { $baseDir = '$baseDir . '; $path = '/' . $path; } } if (preg_match('/\.phar.+$/', $path)) { $baseDir = "'phar://' . " . $baseDir; } return $baseDir . (($path !== false) ? var_export($path, true) : ""); } protected function getAutoloadFile($vendorPathToTargetDirCode, $suffix) { $lastChar = $vendorPathToTargetDirCode[strlen($vendorPathToTargetDirCode) - 1]; if ("'" === $lastChar || '"' === $lastChar) { $vendorPathToTargetDirCode = substr($vendorPathToTargetDirCode, 0, -1).'/autoload_real.php'.$lastChar; } else { $vendorPathToTargetDirCode .= " . '/autoload_real.php'"; } return <<= $staticPhpVersion && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if (\$useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit$suffix::getInitializer(\$loader)); } else { STATIC_INIT; if (!$this->classMapAuthoritative) { $file .= <<<'PSR04' $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); } PSR04; } if ($useClassMap) { $file .= <<<'CLASSMAP' $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } CLASSMAP; } $file .= " }\n\n"; if ($this->classMapAuthoritative) { $file .= <<<'CLASSMAPAUTHORITATIVE' $loader->setClassMapAuthoritative(true); CLASSMAPAUTHORITATIVE; } if ($this->apcu) { $apcuPrefix = substr(base64_encode(md5(uniqid('', true), true)), 0, -3); $file .= <<setApcuPrefix('$apcuPrefix'); APCU; } if ($useGlobalIncludePath) { $file .= <<<'INCLUDEPATH' $loader->setUseIncludePath(true); INCLUDEPATH; } if ($targetDirLoader) { $file .= <<register($prependAutoloader); REGISTER_LOADER; if ($useIncludeFiles) { $file .= << \$file) { composerRequire$suffix(\$fileIdentifier, \$file); } INCLUDE_FILES; } $file .= << $path) { $loader->set($namespace, $path); } $map = require $targetDir . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require $targetDir . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } $filesystem = new Filesystem(); $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1); $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1); $initializer = ''; $prefix = "\0Composer\Autoload\ClassLoader\0"; $prefixLen = strlen($prefix); if (file_exists($targetDir . '/autoload_files.php')) { $maps = array('files' => require $targetDir . '/autoload_files.php'); } else { $maps = array(); } foreach ((array) $loader as $prop => $value) { if ($value && 0 === strpos($prop, $prefix)) { $maps[substr($prop, $prefixLen)] = $value; } } foreach ($maps as $prop => $value) { if (count($value) > 32767) { // Static arrays are limited to 32767 values on PHP 5.6 // See https://bugs.php.net/68057 $staticPhpVersion = 70000; } $value = var_export($value, true); $value = str_replace($absoluteVendorPathCode, $vendorPathCode, $value); $value = str_replace($absoluteAppBaseDirCode, $appBaseDirCode, $value); $value = ltrim(preg_replace('/^ */m', ' $0$0', $value)); $file .= sprintf(" public static $%s = %s;\n\n", $prop, $value); if ('files' !== $prop) { $initializer .= " \$loader->$prop = ComposerStaticInit$suffix::\$$prop;\n"; } } return $file . <<getAutoload(); if ($this->devMode && $package === $mainPackage) { $autoload = array_merge_recursive($autoload, $package->getDevAutoload()); } // skip misconfigured packages if (!isset($autoload[$type]) || !is_array($autoload[$type])) { continue; } if (null !== $package->getTargetDir() && $package !== $mainPackage) { $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); } foreach ($autoload[$type] as $namespace => $paths) { foreach ((array) $paths as $path) { if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) { // remove target-dir from file paths of the root package if ($package === $mainPackage) { $targetDir = str_replace('\\', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '', $package->getTargetDir()))); $path = ltrim(preg_replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/'); } else { // add target-dir from file paths that don't have it $path = $package->getTargetDir() . '/' . $path; } } if ($type === 'exclude-from-classmap') { // first escape user input $path = preg_replace('{/+}', '/', preg_quote(trim(strtr($path, '\\', '/'), '/'))); // add support for wildcards * and ** $path = str_replace('\\*\\*', '.+?', $path); $path = str_replace('\\*', '[^/]+?', $path); // add support for up-level relative paths $updir = null; $path = preg_replace_callback( '{^((?:(?:\\\\\\.){1,2}+/)+)}', function ($matches) use (&$updir) { if (isset($matches[1])) { // undo preg_quote for the matched string $updir = str_replace('\\.', '.', $matches[1]); } return ''; }, $path ); if (empty($installPath)) { $installPath = strtr(getcwd(), '\\', '/'); } $resolvedPath = realpath($installPath . '/' . $updir); $autoloads[] = preg_quote(strtr($resolvedPath, '\\', '/')) . '/' . $path; continue; } $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path; if ($type === 'files') { $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath; continue; } elseif ($type === 'classmap') { $autoloads[] = $relativePath; continue; } $autoloads[$namespace][] = $relativePath; } } } return $autoloads; } protected function getFileIdentifier(PackageInterface $package, $path) { return md5($package->getName() . ':' . $path); } /** * Sorts packages by dependency weight * * Packages of equal weight retain the original order * * @param array $packageMap * @return array */ protected function sortPackageMap(array $packageMap) { $packages = array(); $paths = array(); $usageList = array(); foreach ($packageMap as $item) { list($package, $path) = $item; $name = $package->getName(); $packages[$name] = $package; $paths[$name] = $path; foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { $target = $link->getTarget(); $usageList[$target][] = $name; } } $computing = array(); $computed = array(); $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) { // reusing computed importance if (isset($computed[$name])) { return $computed[$name]; } // canceling circular dependency if (isset($computing[$name])) { return 0; } $computing[$name] = true; $weight = 0; if (isset($usageList[$name])) { foreach ($usageList[$name] as $user) { $weight -= 1 - $computeImportance($user); } } unset($computing[$name]); $computed[$name] = $weight; return $weight; }; $weightList = array(); foreach ($packages as $name => $package) { $weight = $computeImportance($name); $weightList[$name] = $weight; } $stable_sort = function (&$array) { static $transform, $restore; $i = 0; if (!$transform) { $transform = function (&$v, $k) use (&$i) { $v = array($v, ++$i, $k, $v); }; $restore = function (&$v, $k) { $v = $v[3]; }; } array_walk($array, $transform); asort($array); array_walk($array, $restore); }; $stable_sort($weightList); $sortedPackageMap = array(); foreach (array_keys($weightList) as $name) { $sortedPackageMap[] = array($packages[$name], $paths[$name]); } return $sortedPackageMap; } /** * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 * * @param string $source * @param string $target */ protected function safeCopy($source, $target) { $source = fopen($source, 'r'); $target = fopen($target, 'w+'); stream_copy_to_stream($source, $target); fclose($source); fclose($target); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /* * This file is copied from the Symfony package. * * (c) Fabien Potencier */ namespace Composer\Autoload; use Symfony\Component\Finder\Finder; use Composer\IO\IOInterface; use Composer\Util\Filesystem; /** * ClassMapGenerator * * @author Gyula Sallai * @author Jordi Boggiano */ class ClassMapGenerator { /** * Generate a class map file * * @param \Traversable $dirs Directories or a single path to search in * @param string $file The name of the class map file */ public static function dump($dirs, $file) { $maps = array(); foreach ($dirs as $dir) { $maps = array_merge($maps, static::createMap($dir)); } file_put_contents($file, sprintf('files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path); } else { throw new \RuntimeException( 'Could not scan for classes inside "'.$path. '" which does not appear to be a file nor a folder' ); } } $map = array(); $filesystem = new Filesystem(); $cwd = realpath(getcwd()); foreach ($path as $file) { $filePath = $file->getPathname(); if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), array('php', 'inc', 'hh'))) { continue; } if (!$filesystem->isAbsolutePath($filePath)) { $filePath = $cwd . '/' . $filePath; $filePath = $filesystem->normalizePath($filePath); } else { $filePath = preg_replace('{[\\\\/]{2,}}', '/', $filePath); } // check the realpath of the file against the blacklist as the path might be a symlink and the blacklist is realpath'd so symlink are resolved if ($blacklist && preg_match($blacklist, strtr(realpath($filePath), '\\', '/'))) { continue; } $classes = self::findClasses($filePath); foreach ($classes as $class) { // skip classes not within the given namespace prefix if (null !== $namespace && 0 !== strpos($class, $namespace)) { continue; } if (!isset($map[$class])) { $map[$class] = $filePath; } elseif ($io && $map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class].' '.$filePath, '\\', '/'))) { $io->writeError( 'Warning: Ambiguous class resolution, "'.$class.'"'. ' was found in both "'.$map[$class].'" and "'.$filePath.'", the first will be used.' ); } } } return $map; } /** * Extract the classes in the given file * * @param string $path The file to check * @throws \RuntimeException * @return array The found classes */ private static function findClasses($path) { $extraTypes = PHP_VERSION_ID < 50400 ? '' : '|trait'; if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) { $extraTypes .= '|enum'; } // Use @ here instead of Silencer to actively suppress 'unhelpful' output // @link https://github.com/composer/composer/pull/4886 $contents = @php_strip_whitespace($path); if (!$contents) { if (!file_exists($path)) { $message = 'File at "%s" does not exist, check your classmap definitions'; } elseif (!is_readable($path)) { $message = 'File at "%s" is not readable, check its permissions'; } elseif ('' === trim(file_get_contents($path))) { // The input file was really empty and thus contains no classes return array(); } else { $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; } $error = error_get_last(); if (isset($error['message'])) { $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; } throw new \RuntimeException(sprintf($message, $path)); } // return early if there is no chance of matching anything in this file if (!preg_match('{\b(?:class|interface'.$extraTypes.')\s}i', $contents)) { return array(); } // strip heredocs/nowdocs $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); // strip leading non-php code if needed if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface'.$extraTypes.') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] ) }ix', $contents, $matches); $classes = array(); $namespace = ''; for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { if (!empty($matches['ns'][$i])) { $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\'; } else { $name = $matches['name'][$i]; // skip anon classes extending/implementing if ($name === 'extends' || $name === 'implements') { continue; } if ($name[0] === ':') { // This is an XHP class, https://github.com/facebook/xhp $name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1); } elseif ($matches['type'][$i] === 'enum') { // In Hack, something like: // enum Foo: int { HERP = '123'; } // The regex above captures the colon, which isn't part of // the class name. $name = rtrim($name, ':'); } $classes[] = ltrim($namespace . $name, '\\'); } } return $classes; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Package\RootPackageInterface; use Composer\Package\Locker; use Composer\Repository\RepositoryManager; use Composer\Installer\InstallationManager; use Composer\Plugin\PluginManager; use Composer\Downloader\DownloadManager; use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Archiver\ArchiveManager; /** * @author Jordi Boggiano * @author Konstantin Kudryashiv * @author Nils Adermann */ class Composer { const VERSION = '1.5.2'; const BRANCH_ALIAS_VERSION = ''; const RELEASE_DATE = '2017-09-11 16:59:25'; /** * @var Package\RootPackageInterface */ private $package; /** * @var Locker */ private $locker; /** * @var Repository\RepositoryManager */ private $repositoryManager; /** * @var Downloader\DownloadManager */ private $downloadManager; /** * @var Installer\InstallationManager */ private $installationManager; /** * @var Plugin\PluginManager */ private $pluginManager; /** * @var Config */ private $config; /** * @var EventDispatcher */ private $eventDispatcher; /** * @var Autoload\AutoloadGenerator */ private $autoloadGenerator; /** * @var ArchiveManager */ private $archiveManager; /** * @param Package\RootPackageInterface $package * @return void */ public function setPackage(RootPackageInterface $package) { $this->package = $package; } /** * @return Package\RootPackageInterface */ public function getPackage() { return $this->package; } /** * @param Config $config */ public function setConfig(Config $config) { $this->config = $config; } /** * @return Config */ public function getConfig() { return $this->config; } /** * @param Package\Locker $locker */ public function setLocker(Locker $locker) { $this->locker = $locker; } /** * @return Package\Locker */ public function getLocker() { return $this->locker; } /** * @param Repository\RepositoryManager $manager */ public function setRepositoryManager(RepositoryManager $manager) { $this->repositoryManager = $manager; } /** * @return Repository\RepositoryManager */ public function getRepositoryManager() { return $this->repositoryManager; } /** * @param Downloader\DownloadManager $manager */ public function setDownloadManager(DownloadManager $manager) { $this->downloadManager = $manager; } /** * @return Downloader\DownloadManager */ public function getDownloadManager() { return $this->downloadManager; } /** * @param ArchiveManager $manager */ public function setArchiveManager(ArchiveManager $manager) { $this->archiveManager = $manager; } /** * @return ArchiveManager */ public function getArchiveManager() { return $this->archiveManager; } /** * @param Installer\InstallationManager $manager */ public function setInstallationManager(InstallationManager $manager) { $this->installationManager = $manager; } /** * @return Installer\InstallationManager */ public function getInstallationManager() { return $this->installationManager; } /** * @param Plugin\PluginManager $manager */ public function setPluginManager(PluginManager $manager) { $this->pluginManager = $manager; } /** * @return Plugin\PluginManager */ public function getPluginManager() { return $this->pluginManager; } /** * @param EventDispatcher $eventDispatcher */ public function setEventDispatcher(EventDispatcher $eventDispatcher) { $this->eventDispatcher = $eventDispatcher; } /** * @return EventDispatcher */ public function getEventDispatcher() { return $this->eventDispatcher; } /** * @param Autoload\AutoloadGenerator $autoloadGenerator */ public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator) { $this->autoloadGenerator = $autoloadGenerator; } /** * @return Autoload\AutoloadGenerator */ public function getAutoloadGenerator() { return $this->autoloadGenerator; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * Package containing additional metadata that is not used by the solver * * @author Nils Adermann */ class CompletePackage extends Package implements CompletePackageInterface { protected $repositories; protected $license = array(); protected $keywords; protected $authors; protected $description; protected $homepage; protected $scripts = array(); protected $support = array(); protected $abandoned = false; /** * @param array $scripts */ public function setScripts(array $scripts) { $this->scripts = $scripts; } /** * {@inheritDoc} */ public function getScripts() { return $this->scripts; } /** * Set the repositories * * @param array $repositories */ public function setRepositories($repositories) { $this->repositories = $repositories; } /** * {@inheritDoc} */ public function getRepositories() { return $this->repositories; } /** * Set the license * * @param array $license */ public function setLicense(array $license) { $this->license = $license; } /** * {@inheritDoc} */ public function getLicense() { return $this->license; } /** * Set the keywords * * @param array $keywords */ public function setKeywords(array $keywords) { $this->keywords = $keywords; } /** * {@inheritDoc} */ public function getKeywords() { return $this->keywords; } /** * Set the authors * * @param array $authors */ public function setAuthors(array $authors) { $this->authors = $authors; } /** * {@inheritDoc} */ public function getAuthors() { return $this->authors; } /** * Set the description * * @param string $description */ public function setDescription($description) { $this->description = $description; } /** * {@inheritDoc} */ public function getDescription() { return $this->description; } /** * Set the homepage * * @param string $homepage */ public function setHomepage($homepage) { $this->homepage = $homepage; } /** * {@inheritDoc} */ public function getHomepage() { return $this->homepage; } /** * Set the support information * * @param array $support */ public function setSupport(array $support) { $this->support = $support; } /** * {@inheritDoc} */ public function getSupport() { return $this->support; } /** * @return bool */ public function isAbandoned() { return (bool) $this->abandoned; } /** * @param bool|string $abandoned */ public function setAbandoned($abandoned) { $this->abandoned = $abandoned; } /** * If the package is abandoned and has a suggested replacement, this method returns it * * @return string|null */ public function getReplacementPackage() { return is_string($this->abandoned) ? $this->abandoned : null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Semver\Constraint\ConstraintInterface; /** * Represents a link between two packages, represented by their names * * @author Nils Adermann */ class Link { /** * @var string */ protected $source; /** * @var string */ protected $target; /** * @var ConstraintInterface|null */ protected $constraint; /** * @var string */ protected $description; /** * @var string|null */ protected $prettyConstraint; /** * Creates a new package link. * * @param string $source * @param string $target * @param ConstraintInterface|null $constraint Constraint applying to the target of this link * @param string $description Used to create a descriptive string representation * @param string|null $prettyConstraint */ public function __construct($source, $target, ConstraintInterface $constraint = null, $description = 'relates to', $prettyConstraint = null) { $this->source = strtolower($source); $this->target = strtolower($target); $this->constraint = $constraint; $this->description = $description; $this->prettyConstraint = $prettyConstraint; } /** * @return string */ public function getDescription() { return $this->description; } /** * @return string */ public function getSource() { return $this->source; } /** * @return string */ public function getTarget() { return $this->target; } /** * @return ConstraintInterface|null */ public function getConstraint() { return $this->constraint; } /** * @throws \UnexpectedValueException If no pretty constraint was provided * @return string */ public function getPrettyConstraint() { if (null === $this->prettyConstraint) { throw new \UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this)); } return $this->prettyConstraint; } /** * @return string */ public function __toString() { return $this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')'; } /** * @param PackageInterface $sourcePackage * @return string */ public function getPrettyString(PackageInterface $sourcePackage) { return $sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString().''; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * The root package represents the project's composer.json and contains additional metadata * * @author Jordi Boggiano */ class RootPackage extends CompletePackage implements RootPackageInterface { protected $minimumStability = 'stable'; protected $preferStable = false; protected $stabilityFlags = array(); protected $config = array(); protected $references = array(); protected $aliases = array(); /** * Set the minimumStability * * @param string $minimumStability */ public function setMinimumStability($minimumStability) { $this->minimumStability = $minimumStability; } /** * {@inheritDoc} */ public function getMinimumStability() { return $this->minimumStability; } /** * Set the stabilityFlags * * @param array $stabilityFlags */ public function setStabilityFlags(array $stabilityFlags) { $this->stabilityFlags = $stabilityFlags; } /** * {@inheritDoc} */ public function getStabilityFlags() { return $this->stabilityFlags; } /** * Set the preferStable * * @param bool $preferStable */ public function setPreferStable($preferStable) { $this->preferStable = $preferStable; } /** * {@inheritDoc} */ public function getPreferStable() { return $this->preferStable; } /** * Set the config * * @param array $config */ public function setConfig(array $config) { $this->config = $config; } /** * {@inheritDoc} */ public function getConfig() { return $this->config; } /** * Set the references * * @param array $references */ public function setReferences(array $references) { $this->references = $references; } /** * {@inheritDoc} */ public function getReferences() { return $this->references; } /** * Set the aliases * * @param array $aliases */ public function setAliases(array $aliases) { $this->aliases = $aliases; } /** * {@inheritDoc} */ public function getAliases() { return $this->aliases; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use ZipArchive; use Composer\Util\Filesystem; /** * @author Jan Prieser */ class ZipArchiver implements ArchiverInterface { protected static $formats = array( 'zip' => 1, ); /** * {@inheritdoc} */ public function archive($sources, $target, $format, array $excludes = array(), $ignoreFilters = false) { $fs = new Filesystem(); $sources = $fs->normalizePath($sources); $zip = new ZipArchive(); $res = $zip->open($target, ZipArchive::CREATE); if ($res === true) { $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); foreach ($files as $file) { /** @var $file \SplFileInfo */ $filepath = strtr($file->getPath()."/".$file->getFilename(), '\\', '/'); $localname = str_replace($sources.'/', '', $filepath); if ($file->isDir()) { $zip->addEmptyDir($localname); } else { $zip->addFile($filepath, $localname); } } if ($zip->close()) { return $target; } } $message = sprintf("Could not create archive '%s' from '%s': %s", $target, $sources, $zip->getStatusString() ); throw new \RuntimeException($message); } /** * {@inheritdoc} */ public function supports($format, $sourceType) { return isset(static::$formats[$format]) && $this->compressionAvailable(); } private function compressionAvailable() { return class_exists('ZipArchive'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Symfony\Component\Finder; /** * @author Nils Adermann */ abstract class BaseExcludeFilter { /** * @var string */ protected $sourcePath; /** * @var array */ protected $excludePatterns; /** * @param string $sourcePath Directory containing sources to be filtered */ public function __construct($sourcePath) { $this->sourcePath = $sourcePath; $this->excludePatterns = array(); } /** * Checks the given path against all exclude patterns in this filter * * Negated patterns overwrite exclude decisions of previous filters. * * @param string $relativePath The file's path relative to the sourcePath * @param bool $exclude Whether a previous filter wants to exclude this file * * @return bool Whether the file should be excluded */ public function filter($relativePath, $exclude) { foreach ($this->excludePatterns as $patternData) { list($pattern, $negate, $stripLeadingSlash) = $patternData; if ($stripLeadingSlash) { $path = substr($relativePath, 1); } else { $path = $relativePath; } if (preg_match($pattern, $path)) { $exclude = !$negate; } } return $exclude; } /** * Processes a file containing exclude rules of different formats per line * * @param array $lines A set of lines to be parsed * @param callback $lineParser The parser to be used on each line * * @return array Exclude patterns to be used in filter() */ protected function parseLines(array $lines, $lineParser) { return array_filter( array_map( function ($line) use ($lineParser) { $line = trim($line); if (!$line || 0 === strpos($line, '#')) { return null; } return call_user_func($lineParser, $line); }, $lines ), function ($pattern) { return $pattern !== null; } ); } /** * Generates a set of exclude patterns for filter() from gitignore rules * * @param array $rules A list of exclude rules in gitignore syntax * * @return array Exclude patterns */ protected function generatePatterns($rules) { $patterns = array(); foreach ($rules as $rule) { $patterns[] = $this->generatePattern($rule); } return $patterns; } /** * Generates an exclude pattern for filter() from a gitignore rule * * @param string $rule An exclude rule in gitignore syntax * * @return array An exclude pattern */ protected function generatePattern($rule) { $negate = false; $pattern = '{'; if (strlen($rule) && $rule[0] === '!') { $negate = true; $rule = substr($rule, 1); } if (strlen($rule) && $rule[0] === '/') { $pattern .= '^/'; $rule = substr($rule, 1); } elseif (strlen($rule) - 1 === strpos($rule, '/')) { $pattern .= '/'; $rule = substr($rule, 0, -1); } elseif (false === strpos($rule, '/')) { $pattern .= '/'; } // remove delimiters as well as caret (^) and dollar sign ($) from the regex $pattern .= substr(Finder\Glob::toRegex($rule), 2, -2) . '(?=$|/)'; return array($pattern . '}', $negate, false); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; /** * @author Till Klampaeckel * @author Nils Adermann * @author Matthieu Moquet */ class PharArchiver implements ArchiverInterface { protected static $formats = array( 'zip' => \Phar::ZIP, 'tar' => \Phar::TAR, 'tar.gz' => \Phar::TAR, 'tar.bz2' => \Phar::TAR, ); protected static $compressFormats = array( 'tar.gz' => \Phar::GZ, 'tar.bz2' => \Phar::BZ2, ); /** * {@inheritdoc} */ public function archive($sources, $target, $format, array $excludes = array(), $ignoreFilters = false) { $sources = realpath($sources); // Phar would otherwise load the file which we don't want if (file_exists($target)) { unlink($target); } try { $filename = substr($target, 0, strrpos($target, $format) - 1); // Check if compress format if (isset(static::$compressFormats[$format])) { // Current compress format supported base on tar $target = $filename . '.tar'; } $phar = new \PharData($target, null, null, static::$formats[$format]); $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); $filesOnly = new ArchivableFilesFilter($files); $phar->buildFromIterator($filesOnly, $sources); $filesOnly->addEmptyDir($phar, $sources); if (isset(static::$compressFormats[$format])) { // Check can be compressed? if (!$phar->canCompress(static::$compressFormats[$format])) { throw new \RuntimeException(sprintf('Can not compress to %s format', $format)); } // Delete old tar unlink($target); // Compress the new tar $phar->compress(static::$compressFormats[$format]); // Make the correct filename $target = $filename . '.' . $format; } return $target; } catch (\UnexpectedValueException $e) { $message = sprintf("Could not create archive '%s' from '%s': %s", $target, $sources, $e->getMessage() ); throw new \RuntimeException($message, $e->getCode(), $e); } } /** * {@inheritdoc} */ public function supports($format, $sourceType) { return isset(static::$formats[$format]); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Composer\Downloader\DownloadManager; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; use Composer\Util\Filesystem; use Composer\Json\JsonFile; /** * @author Matthieu Moquet * @author Till Klampaeckel */ class ArchiveManager { protected $downloadManager; protected $archivers = array(); /** * @var bool */ protected $overwriteFiles = true; /** * @param DownloadManager $downloadManager A manager used to download package sources */ public function __construct(DownloadManager $downloadManager) { $this->downloadManager = $downloadManager; } /** * @param ArchiverInterface $archiver */ public function addArchiver(ArchiverInterface $archiver) { $this->archivers[] = $archiver; } /** * Set whether existing archives should be overwritten * * @param bool $overwriteFiles New setting * * @return $this */ public function setOverwriteFiles($overwriteFiles) { $this->overwriteFiles = $overwriteFiles; return $this; } /** * Generate a distinct filename for a particular version of a package. * * @param PackageInterface $package The package to get a name for * * @return string A filename without an extension */ public function getPackageFilename(PackageInterface $package) { $nameParts = array(preg_replace('#[^a-z0-9-_]#i', '-', $package->getName())); if (preg_match('{^[a-f0-9]{40}$}', $package->getDistReference())) { $nameParts = array_merge($nameParts, array($package->getDistReference(), $package->getDistType())); } else { $nameParts = array_merge($nameParts, array($package->getPrettyVersion(), $package->getDistReference())); } if ($package->getSourceReference()) { $nameParts[] = substr(sha1($package->getSourceReference()), 0, 6); } $name = implode('-', array_filter($nameParts, function ($p) { return !empty($p); })); return str_replace('/', '-', $name); } /** * Create an archive of the specified package. * * @param PackageInterface $package The package to archive * @param string $format The format of the archive (zip, tar, ...) * @param string $targetDir The directory where to build the archive * @param string|null $fileName The relative file name to use for the archive, or null to generate * the package name. Note that the format will be appended to this name * @param bool $ignoreFilters Ignore filters when looking for files in the package * @throws \InvalidArgumentException * @throws \RuntimeException * @return string The path of the created archive */ public function archive(PackageInterface $package, $format, $targetDir, $fileName = null, $ignoreFilters = false) { if (empty($format)) { throw new \InvalidArgumentException('Format must be specified'); } // Search for the most appropriate archiver $usableArchiver = null; foreach ($this->archivers as $archiver) { if ($archiver->supports($format, $package->getSourceType())) { $usableArchiver = $archiver; break; } } // Checks the format/source type are supported before downloading the package if (null === $usableArchiver) { throw new \RuntimeException(sprintf('No archiver found to support %s format', $format)); } $filesystem = new Filesystem(); if (null === $fileName) { $packageName = $this->getPackageFilename($package); } else { $packageName = $fileName; } // Archive filename $filesystem->ensureDirectoryExists($targetDir); $target = realpath($targetDir).'/'.$packageName.'.'.$format; $filesystem->ensureDirectoryExists(dirname($target)); if (!$this->overwriteFiles && file_exists($target)) { return $target; } if ($package instanceof RootPackageInterface) { $sourcePath = realpath('.'); } else { // Directory used to download the sources $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid(); $filesystem->ensureDirectoryExists($sourcePath); // Download sources $this->downloadManager->download($package, $sourcePath); // Check exclude from downloaded composer.json if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { $jsonFile = new JsonFile($composerJsonPath); $jsonData = $jsonFile->read(); if (!empty($jsonData['archive']['exclude'])) { $package->setArchiveExcludes($jsonData['archive']['exclude']); } } } // Create the archive $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid().'.'.$format; $filesystem->ensureDirectoryExists(dirname($tempTarget)); $archivePath = $usableArchiver->archive($sourcePath, $tempTarget, $format, $package->getArchiveExcludes(), $ignoreFilters); $filesystem->rename($archivePath, $target); // cleanup temporary download if (!$package instanceof RootPackageInterface) { $filesystem->removeDirectory($sourcePath); } $filesystem->remove($tempTarget); return $target; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; /** * An exclude filter that processes gitignore and gitattributes * * It respects export-ignore git attributes * * @author Nils Adermann */ class GitExcludeFilter extends BaseExcludeFilter { /** * Parses .gitignore and .gitattributes files if they exist * * @param string $sourcePath */ public function __construct($sourcePath) { parent::__construct($sourcePath); if (file_exists($sourcePath.'/.gitignore')) { $this->excludePatterns = $this->parseLines( file($sourcePath.'/.gitignore'), array($this, 'parseGitIgnoreLine') ); } if (file_exists($sourcePath.'/.gitattributes')) { $this->excludePatterns = array_merge( $this->excludePatterns, $this->parseLines( file($sourcePath.'/.gitattributes'), array($this, 'parseGitAttributesLine') )); } } /** * Callback line parser which process gitignore lines * * @param string $line A line from .gitignore * * @return array An exclude pattern for filter() */ public function parseGitIgnoreLine($line) { return $this->generatePattern($line); } /** * Callback parser which finds export-ignore rules in git attribute lines * * @param string $line A line from .gitattributes * * @return array An exclude pattern for filter() */ public function parseGitAttributesLine($line) { $parts = preg_split('#\s+#', $line); if (count($parts) == 2 && $parts[1] === 'export-ignore') { return $this->generatePattern($parts[0]); } return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use FilterIterator; use PharData; class ArchivableFilesFilter extends FilterIterator { private $dirs = array(); /** * @return bool true if the current element is acceptable, otherwise false. */ public function accept() { $file = $this->getInnerIterator()->current(); if ($file->isDir()) { $this->dirs[] = (string) $file; return false; } return true; } public function addEmptyDir(PharData $phar, $sources) { foreach ($this->dirs as $filepath) { $localname = str_replace($sources . "/", '', $filepath); $phar->addEmptyDir($localname); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; /** * An exclude filter which processes composer's own exclude rules * * @author Nils Adermann */ class ComposerExcludeFilter extends BaseExcludeFilter { /** * @param string $sourcePath Directory containing sources to be filtered * @param array $excludeRules An array of exclude rules from composer.json */ public function __construct($sourcePath, array $excludeRules) { parent::__construct($sourcePath); $this->excludePatterns = $this->generatePatterns($excludeRules); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Symfony\Component\Finder; /** * An exclude filter that processes hgignore files * * @author Nils Adermann */ class HgExcludeFilter extends BaseExcludeFilter { const HG_IGNORE_REGEX = 1; const HG_IGNORE_GLOB = 2; /** * Either HG_IGNORE_REGEX or HG_IGNORE_GLOB * @var int */ protected $patternMode; /** * Parses .hgignore file if it exist * * @param string $sourcePath */ public function __construct($sourcePath) { parent::__construct($sourcePath); $this->patternMode = self::HG_IGNORE_REGEX; if (file_exists($sourcePath.'/.hgignore')) { $this->excludePatterns = $this->parseLines( file($sourcePath.'/.hgignore'), array($this, 'parseHgIgnoreLine') ); } } /** * Callback line parser which process hgignore lines * * @param string $line A line from .hgignore * * @return array An exclude pattern for filter() */ public function parseHgIgnoreLine($line) { if (preg_match('#^syntax\s*:\s*(glob|regexp)$#', $line, $matches)) { if ($matches[1] === 'glob') { $this->patternMode = self::HG_IGNORE_GLOB; } else { $this->patternMode = self::HG_IGNORE_REGEX; } return null; } if ($this->patternMode == self::HG_IGNORE_GLOB) { return $this->patternFromGlob($line); } else { return $this->patternFromRegex($line); } } /** * Generates an exclude pattern for filter() from a hg glob expression * * @param string $line A line from .hgignore in glob mode * * @return array An exclude pattern for filter() */ protected function patternFromGlob($line) { $pattern = '#'.substr(Finder\Glob::toRegex($line), 2, -1).'#'; $pattern = str_replace('[^/]*', '.*', $pattern); return array($pattern, false, true); } /** * Generates an exclude pattern for filter() from a hg regexp expression * * @param string $line A line from .hgignore in regexp mode * * @return array An exclude pattern for filter() */ public function patternFromRegex($line) { // WTF need to escape the delimiter safely $pattern = '#'.preg_replace('/((?:\\\\\\\\)*)(\\\\?)#/', '\1\2\2\\#', $line).'#'; return array($pattern, false, true); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; /** * @author Till Klampaeckel * @author Matthieu Moquet * @author Nils Adermann */ interface ArchiverInterface { /** * Create an archive from the sources. * * @param string $sources The sources directory * @param string $target The target file * @param string $format The format used for archive * @param array $excludes A list of patterns for files to exclude * * @return string The path to the written archive file */ public function archive($sources, $target, $format, array $excludes = array(), $ignoreFilters = false); /** * Format supported by the archiver. * * @param string $format The archive format * @param string $sourceType The source type (git, svn, hg, etc.) * * @return bool true if the format is supported by the archiver */ public function supports($format, $sourceType); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Composer\Util\Filesystem; use FilesystemIterator; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; /** * A Symfony Finder wrapper which locates files that should go into archives * * Handles .gitignore, .gitattributes and .hgignore files as well as composer's * own exclude rules from composer.json * * @author Nils Adermann */ class ArchivableFilesFinder extends \FilterIterator { /** * @var Finder */ protected $finder; /** * Initializes the internal Symfony Finder with appropriate filters * * @param string $sources Path to source files to be archived * @param array $excludes Composer's own exclude rules from composer.json * @param bool $ignoreFilters Ignore filters when looking for files */ public function __construct($sources, array $excludes, $ignoreFilters = false) { $fs = new Filesystem(); $sources = $fs->normalizePath($sources); if ($ignoreFilters) { $filters = array(); } else { $filters = array( new HgExcludeFilter($sources), new GitExcludeFilter($sources), new ComposerExcludeFilter($sources, $excludes), ); } $this->finder = new Finder(); $filter = function (\SplFileInfo $file) use ($sources, $filters, $fs) { if ($file->isLink() && strpos($file->getLinkTarget(), $sources) !== 0) { return false; } $relativePath = preg_replace( '#^'.preg_quote($sources, '#').'#', '', $fs->normalizePath($file->getRealPath()) ); $exclude = false; foreach ($filters as $filter) { $exclude = $filter->filter($relativePath, $exclude); } return !$exclude; }; if (method_exists($filter, 'bindTo')) { $filter = $filter->bindTo(null); } $this->finder ->in($sources) ->filter($filter) ->ignoreVCS(true) ->ignoreDotFiles(false); parent::__construct($this->finder->getIterator()); } public function accept() { /** @var SplFileInfo $current */ $current = $this->getInnerIterator()->current(); if (!$current->isDir()) { return true; } $iterator = new FilesystemIterator($current, FilesystemIterator::SKIP_DOTS); return !$iterator->valid(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Repository\RepositoryInterface; /** * Defines the essential information a package has that is used during solving/installation * * @author Jordi Boggiano */ interface PackageInterface { /** * Returns the package's name without version info, thus not a unique identifier * * @return string package name */ public function getName(); /** * Returns the package's pretty (i.e. with proper case) name * * @return string package name */ public function getPrettyName(); /** * Returns a set of names that could refer to this package * * No version or release type information should be included in any of the * names. Provided or replaced package names need to be returned as well. * * @return array An array of strings referring to this package */ public function getNames(); /** * Allows the solver to set an id for this package to refer to it. * * @param int $id */ public function setId($id); /** * Retrieves the package's id set through setId * * @return int The previously set package id */ public function getId(); /** * Returns whether the package is a development virtual package or a concrete one * * @return bool */ public function isDev(); /** * Returns the package type, e.g. library * * @return string The package type */ public function getType(); /** * Returns the package targetDir property * * @return string The package targetDir */ public function getTargetDir(); /** * Returns the package extra data * * @return array The package extra data */ public function getExtra(); /** * Sets source from which this package was installed (source/dist). * * @param string $type source/dist */ public function setInstallationSource($type); /** * Returns source from which this package was installed (source/dist). * * @return string source/dist */ public function getInstallationSource(); /** * Returns the repository type of this package, e.g. git, svn * * @return string The repository type */ public function getSourceType(); /** * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git * * @return string The repository url */ public function getSourceUrl(); /** * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git * * @return array */ public function getSourceUrls(); /** * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git * * @return string The repository reference */ public function getSourceReference(); /** * Returns the source mirrors of this package * * @return array|null */ public function getSourceMirrors(); /** * Returns the type of the distribution archive of this version, e.g. zip, tarball * * @return string The repository type */ public function getDistType(); /** * Returns the url of the distribution archive of this version * * @return string */ public function getDistUrl(); /** * Returns the urls of the distribution archive of this version, including mirrors * * @return array */ public function getDistUrls(); /** * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git * * @return string */ public function getDistReference(); /** * Returns the sha1 checksum for the distribution archive of this version * * @return string */ public function getDistSha1Checksum(); /** * Returns the dist mirrors of this package * * @return array|null */ public function getDistMirrors(); /** * Returns the version of this package * * @return string version */ public function getVersion(); /** * Returns the pretty (i.e. non-normalized) version string of this package * * @return string version */ public function getPrettyVersion(); /** * Returns the pretty version string plus a git or hg commit hash of this package * * @see getPrettyVersion * * @param bool $truncate If the source reference is a sha1 hash, truncate it * @return string version */ public function getFullPrettyVersion($truncate = true); /** * Returns the release date of the package * * @return \DateTime */ public function getReleaseDate(); /** * Returns the stability of this package: one of (dev, alpha, beta, RC, stable) * * @return string */ public function getStability(); /** * Returns a set of links to packages which need to be installed before * this package can be installed * * @return Link[] An array of package links defining required packages */ public function getRequires(); /** * Returns a set of links to packages which must not be installed at the * same time as this package * * @return Link[] An array of package links defining conflicting packages */ public function getConflicts(); /** * Returns a set of links to virtual packages that are provided through * this package * * @return Link[] An array of package links defining provided packages */ public function getProvides(); /** * Returns a set of links to packages which can alternatively be * satisfied by installing this package * * @return Link[] An array of package links defining replaced packages */ public function getReplaces(); /** * Returns a set of links to packages which are required to develop * this package. These are installed if in dev mode. * * @return Link[] An array of package links defining packages required for development */ public function getDevRequires(); /** * Returns a set of package names and reasons why they are useful in * combination with this package. * * @return array An array of package suggestions with descriptions */ public function getSuggests(); /** * Returns an associative array of autoloading rules * * {"": {""}} * * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to * directories for autoloading using the type specified. * * @return array Mapping of autoloading rules */ public function getAutoload(); /** * Returns an associative array of dev autoloading rules * * {"": {""}} * * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to * directories for autoloading using the type specified. * * @return array Mapping of dev autoloading rules */ public function getDevAutoload(); /** * Returns a list of directories which should get added to PHP's * include path. * * @return array */ public function getIncludePaths(); /** * Stores a reference to the repository that owns the package * * @param RepositoryInterface $repository */ public function setRepository(RepositoryInterface $repository); /** * Returns a reference to the repository that owns the package * * @return RepositoryInterface */ public function getRepository(); /** * Returns the package binaries * * @return array */ public function getBinaries(); /** * Returns package unique name, constructed from name and version. * * @return string */ public function getUniqueName(); /** * Returns the package notification url * * @return string */ public function getNotificationUrl(); /** * Converts the package into a readable and unique string * * @return string */ public function __toString(); /** * Converts the package into a pretty readable string * * @return string */ public function getPrettyString(); /** * Returns a list of patterns to exclude from package archives * * @return array */ public function getArchiveExcludes(); /** * Returns a list of options to download package dist files * * @return array */ public function getTransportOptions(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Repository\RepositoryInterface; use Composer\Repository\PlatformRepository; /** * Base class for packages providing name storage and default match implementation * * @author Nils Adermann */ abstract class BasePackage implements PackageInterface { public static $supportedLinkTypes = array( 'require' => array('description' => 'requires', 'method' => 'requires'), 'conflict' => array('description' => 'conflicts', 'method' => 'conflicts'), 'provide' => array('description' => 'provides', 'method' => 'provides'), 'replace' => array('description' => 'replaces', 'method' => 'replaces'), 'require-dev' => array('description' => 'requires (for development)', 'method' => 'devRequires'), ); const STABILITY_STABLE = 0; const STABILITY_RC = 5; const STABILITY_BETA = 10; const STABILITY_ALPHA = 15; const STABILITY_DEV = 20; public static $stabilities = array( 'stable' => self::STABILITY_STABLE, 'RC' => self::STABILITY_RC, 'beta' => self::STABILITY_BETA, 'alpha' => self::STABILITY_ALPHA, 'dev' => self::STABILITY_DEV, ); /** * READ-ONLY: The package id, public for fast access in dependency solver * @var int */ public $id; /** @var string */ protected $name; /** @var string */ protected $prettyName; /** @var RepositoryInterface */ protected $repository; /** @var array */ protected $transportOptions = array(); /** * All descendants' constructors should call this parent constructor * * @param string $name The package's name */ public function __construct($name) { $this->prettyName = $name; $this->name = strtolower($name); $this->id = -1; } /** * {@inheritDoc} */ public function getName() { return $this->name; } /** * {@inheritDoc} */ public function getPrettyName() { return $this->prettyName; } /** * {@inheritDoc} */ public function getNames() { $names = array( $this->getName() => true, ); foreach ($this->getProvides() as $link) { $names[$link->getTarget()] = true; } foreach ($this->getReplaces() as $link) { $names[$link->getTarget()] = true; } return array_keys($names); } /** * {@inheritDoc} */ public function setId($id) { $this->id = $id; } /** * {@inheritDoc} */ public function getId() { return $this->id; } /** * {@inheritDoc} */ public function setRepository(RepositoryInterface $repository) { if ($this->repository && $repository !== $this->repository) { throw new \LogicException('A package can only be added to one repository'); } $this->repository = $repository; } /** * {@inheritDoc} */ public function getRepository() { return $this->repository; } /** * {@inheritDoc} */ public function getTransportOptions() { return $this->transportOptions; } /** * Configures the list of options to download package dist files * * @param array $options */ public function setTransportOptions(array $options) { $this->transportOptions = $options; } /** * checks if this package is a platform package * * @return bool */ public function isPlatform() { return $this->getRepository() instanceof PlatformRepository; } /** * Returns package unique name, constructed from name, version and release type. * * @return string */ public function getUniqueName() { return $this->getName().'-'.$this->getVersion(); } public function equals(PackageInterface $package) { $self = $this; if ($this instanceof AliasPackage) { $self = $this->getAliasOf(); } if ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } return $package === $self; } /** * Converts the package into a readable and unique string * * @return string */ public function __toString() { return $this->getUniqueName(); } public function getPrettyString() { return $this->getPrettyName().' '.$this->getPrettyVersion(); } /** * {@inheritDoc} */ public function getFullPrettyVersion($truncate = true) { if (!$this->isDev() || !in_array($this->getSourceType(), array('hg', 'git'))) { return $this->getPrettyVersion(); } // if source reference is a sha1 hash -- truncate if ($truncate && strlen($this->getSourceReference()) === 40) { return $this->getPrettyVersion() . ' ' . substr($this->getSourceReference(), 0, 7); } return $this->getPrettyVersion() . ' ' . $this->getSourceReference(); } public function getStabilityPriority() { return self::$stabilities[$this->getStability()]; } public function __clone() { $this->repository = null; $this->id = -1; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Package; use Composer\Package\AliasPackage; use Composer\Package\Link; use Composer\Package\RootAliasPackage; use Composer\Package\RootPackageInterface; use Composer\Package\Version\VersionParser; use Composer\Semver\VersionParser as SemverVersionParser; /** * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class ArrayLoader implements LoaderInterface { protected $versionParser; protected $loadOptions; public function __construct(SemverVersionParser $parser = null, $loadOptions = false) { if (!$parser) { $parser = new VersionParser; } $this->versionParser = $parser; $this->loadOptions = $loadOptions; } public function load(array $config, $class = 'Composer\Package\CompletePackage') { if (!isset($config['name'])) { throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').'); } if (!isset($config['version'])) { throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.'); } // handle already normalized versions if (isset($config['version_normalized'])) { $version = $config['version_normalized']; } else { $version = $this->versionParser->normalize($config['version']); } $package = new $class($config['name'], $version, $config['version']); $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library'); if (isset($config['target-dir'])) { $package->setTargetDir($config['target-dir']); } if (isset($config['extra']) && is_array($config['extra'])) { $package->setExtra($config['extra']); } if (isset($config['bin'])) { if (!is_array($config['bin'])) { throw new \UnexpectedValueException('Package '.$config['name'].'\'s bin key should be an array, '.gettype($config['bin']).' given.'); } foreach ($config['bin'] as $key => $bin) { $config['bin'][$key] = ltrim($bin, '/'); } $package->setBinaries($config['bin']); } if (isset($config['installation-source'])) { $package->setInstallationSource($config['installation-source']); } if (isset($config['source'])) { if (!isset($config['source']['type']) || !isset($config['source']['url']) || !isset($config['source']['reference'])) { throw new \UnexpectedValueException(sprintf( "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.", $config['name'], json_encode($config['source']) )); } $package->setSourceType($config['source']['type']); $package->setSourceUrl($config['source']['url']); $package->setSourceReference($config['source']['reference']); if (isset($config['source']['mirrors'])) { $package->setSourceMirrors($config['source']['mirrors']); } } if (isset($config['dist'])) { if (!isset($config['dist']['type']) || !isset($config['dist']['url'])) { throw new \UnexpectedValueException(sprintf( "Package %s's dist key should be specified as ". "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.", $config['name'], json_encode($config['dist']) )); } $package->setDistType($config['dist']['type']); $package->setDistUrl($config['dist']['url']); $package->setDistReference(isset($config['dist']['reference']) ? $config['dist']['reference'] : null); $package->setDistSha1Checksum(isset($config['dist']['shasum']) ? $config['dist']['shasum'] : null); if (isset($config['dist']['mirrors'])) { $package->setDistMirrors($config['dist']['mirrors']); } } foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) { if (isset($config[$type])) { $method = 'set'.ucfirst($opts['method']); $package->{$method}( $this->parseLinks( $package->getName(), $package->getPrettyVersion(), $opts['description'], $config[$type] ) ); } } if (isset($config['suggest']) && is_array($config['suggest'])) { foreach ($config['suggest'] as $target => $reason) { if ('self.version' === trim($reason)) { $config['suggest'][$target] = $package->getPrettyVersion(); } } $package->setSuggests($config['suggest']); } if (isset($config['autoload'])) { $package->setAutoload($config['autoload']); } if (isset($config['autoload-dev'])) { $package->setDevAutoload($config['autoload-dev']); } if (isset($config['include-path'])) { $package->setIncludePaths($config['include-path']); } if (!empty($config['time'])) { $time = preg_match('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time']; try { $date = new \DateTime($time, new \DateTimeZone('UTC')); $package->setReleaseDate($date); } catch (\Exception $e) { } } if (!empty($config['notification-url'])) { $package->setNotificationUrl($config['notification-url']); } if (!empty($config['archive']['exclude'])) { $package->setArchiveExcludes($config['archive']['exclude']); } if ($package instanceof Package\CompletePackageInterface) { if (isset($config['scripts']) && is_array($config['scripts'])) { foreach ($config['scripts'] as $event => $listeners) { $config['scripts'][$event] = (array) $listeners; } if (isset($config['scripts']['composer'])) { trigger_error('The `composer` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED); } $package->setScripts($config['scripts']); } if (!empty($config['description']) && is_string($config['description'])) { $package->setDescription($config['description']); } if (!empty($config['homepage']) && is_string($config['homepage'])) { $package->setHomepage($config['homepage']); } if (!empty($config['keywords']) && is_array($config['keywords'])) { $package->setKeywords($config['keywords']); } if (!empty($config['license'])) { $package->setLicense(is_array($config['license']) ? $config['license'] : array($config['license'])); } if (!empty($config['authors']) && is_array($config['authors'])) { $package->setAuthors($config['authors']); } if (isset($config['support'])) { $package->setSupport($config['support']); } if (isset($config['abandoned'])) { $package->setAbandoned($config['abandoned']); } } if ($aliasNormalized = $this->getBranchAlias($config)) { if ($package instanceof RootPackageInterface) { $package = new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); } else { $package = new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); } } if ($this->loadOptions && isset($config['transport-options'])) { $package->setTransportOptions($config['transport-options']); } return $package; } /** * @param string $source source package name * @param string $sourceVersion source package version (pretty version ideally) * @param string $description link description (e.g. requires, replaces, ..) * @param array $links array of package name => constraint mappings * @return Link[] */ public function parseLinks($source, $sourceVersion, $description, $links) { $res = array(); foreach ($links as $target => $constraint) { if (!is_string($constraint)) { throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($constraint) . ' (' . var_export($constraint, true) . ')'); } if ('self.version' === $constraint) { $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion); } else { $parsedConstraint = $this->versionParser->parseConstraints($constraint); } $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint); } return $res; } /** * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists * * @param array $config the entire package config * @return string|null normalized version of the branch alias or null if there is none */ public function getBranchAlias(array $config) { if (('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4)) || !isset($config['extra']['branch-alias']) || !is_array($config['extra']['branch-alias']) ) { return; } foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { // ensure it is an alias to a -dev package if ('-dev' !== substr($targetBranch, -4)) { continue; } // normalize without -dev and ensure it's a numeric branch that is parseable $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); if ('-dev' !== substr($validatedTargetBranch, -4)) { continue; } // ensure that it is the current branch aliasing itself if (strtolower($config['version']) !== strtolower($sourceBranch)) { continue; } // If using numeric aliases ensure the alias is a valid subversion if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && (stripos($targetPrefix, $sourcePrefix) !== 0) ) { continue; } return $validatedTargetBranch; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; /** * @author Jordi Boggiano */ class InvalidPackageException extends \Exception { private $errors; private $warnings; private $data; public function __construct(array $errors, array $warnings, array $data) { $this->errors = $errors; $this->warnings = $warnings; $this->data = $data; parent::__construct("Invalid package information: \n".implode("\n", array_merge($errors, $warnings))); } public function getData() { return $this->data; } public function getErrors() { return $this->errors; } public function getWarnings() { return $this->warnings; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Package; use Composer\Package\BasePackage; use Composer\Semver\Constraint\Constraint; use Composer\Package\Version\VersionParser; use Composer\Repository\PlatformRepository; /** * @author Jordi Boggiano */ class ValidatingArrayLoader implements LoaderInterface { const CHECK_ALL = 3; const CHECK_UNBOUND_CONSTRAINTS = 1; const CHECK_STRICT_CONSTRAINTS = 2; private $loader; private $versionParser; private $errors; private $warnings; private $config; private $strictName; private $flags; public function __construct(LoaderInterface $loader, $strictName = true, VersionParser $parser = null, $flags = 0) { $this->loader = $loader; $this->versionParser = $parser ?: new VersionParser(); $this->strictName = $strictName; $this->flags = $flags; } public function load(array $config, $class = 'Composer\Package\CompletePackage') { $this->errors = array(); $this->warnings = array(); $this->config = $config; if ($this->strictName) { $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true); } else { $this->validateString('name', true); } if (!empty($this->config['version'])) { try { $this->versionParser->normalize($this->config['version']); } catch (\Exception $e) { $this->errors[] = 'version : invalid value ('.$this->config['version'].'): '.$e->getMessage(); unset($this->config['version']); } } if (!empty($this->config['config']['platform'])) { foreach ((array) $this->config['config']['platform'] as $key => $platform) { try { $this->versionParser->normalize($platform); } catch (\Exception $e) { $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.$platform.'): '.$e->getMessage(); } } } $this->validateRegex('type', '[A-Za-z0-9-]+'); $this->validateString('target-dir'); $this->validateArray('extra'); $this->validateFlatArray('bin'); $this->validateArray('scripts'); // TODO validate event names & listener syntax $this->validateString('description'); $this->validateUrl('homepage'); $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+'); if (isset($this->config['license'])) { if (is_string($this->config['license'])) { $this->validateRegex('license', '[A-Za-z0-9+. ()-]+'); } else { $this->validateFlatArray('license', '[A-Za-z0-9+. ()-]+'); } } $this->validateString('time'); if (!empty($this->config['time'])) { try { $date = new \DateTime($this->config['time'], new \DateTimeZone('UTC')); } catch (\Exception $e) { $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage(); unset($this->config['time']); } } if ($this->validateArray('authors') && !empty($this->config['authors'])) { foreach ($this->config['authors'] as $key => $author) { if (!is_array($author)) { $this->errors[] = 'authors.'.$key.' : should be an array, '.gettype($author).' given'; unset($this->config['authors'][$key]); continue; } foreach (array('homepage', 'email', 'name', 'role') as $authorData) { if (isset($author[$authorData]) && !is_string($author[$authorData])) { $this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string'; unset($this->config['authors'][$key][$authorData]); } } if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) { $this->warnings[] = 'authors.'.$key.'.homepage : invalid value ('.$author['homepage'].'), must be an http/https URL'; unset($this->config['authors'][$key]['homepage']); } if (isset($author['email']) && !filter_var($author['email'], FILTER_VALIDATE_EMAIL)) { $this->warnings[] = 'authors.'.$key.'.email : invalid value ('.$author['email'].'), must be a valid email address'; unset($this->config['authors'][$key]['email']); } if (empty($this->config['authors'][$key])) { unset($this->config['authors'][$key]); } } if (empty($this->config['authors'])) { unset($this->config['authors']); } } if ($this->validateArray('support') && !empty($this->config['support'])) { foreach (array('issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss') as $key) { if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) { $this->errors[] = 'support.'.$key.' : invalid value, must be a string'; unset($this->config['support'][$key]); } } if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) { $this->warnings[] = 'support.email : invalid value ('.$this->config['support']['email'].'), must be a valid email address'; unset($this->config['support']['email']); } if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], array('irc'))) { $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc:/// URL'; unset($this->config['support']['irc']); } foreach (array('issues', 'forum', 'wiki', 'source', 'docs') as $key) { if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) { $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL'; unset($this->config['support'][$key]); } } if (empty($this->config['support'])) { unset($this->config['support']); } } $unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master')); $stableConstraint = new Constraint('=', '1.0.0'); foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if ($this->validateArray($linkType) && isset($this->config[$linkType])) { foreach ($this->config[$linkType] as $package => $constraint) { if (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) { $this->warnings[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]'; } if (!is_string($constraint)) { $this->errors[] = $linkType.'.'.$package.' : invalid value, must be a string containing a version constraint'; unset($this->config[$linkType][$package]); } elseif ('self.version' !== $constraint) { try { $linkConstraint = $this->versionParser->parseConstraints($constraint); } catch (\Exception $e) { $this->errors[] = $linkType.'.'.$package.' : invalid version constraint ('.$e->getMessage().')'; unset($this->config[$linkType][$package]); continue; } // check requires for unbound constraints on non-platform packages if ( ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS) && 'require' === $linkType && $linkConstraint->matches($unboundConstraint) && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $package) ) { $this->warnings[] = $linkType.'.'.$package.' : unbound version constraints ('.$constraint.') should be avoided'; } elseif ( // check requires for exact constraints ($this->flags & self::CHECK_STRICT_CONSTRAINTS) && 'require' === $linkType && substr($linkConstraint, 0, 1) === '=' && $stableConstraint->versionCompare($stableConstraint, $linkConstraint, '<=') ) { $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning'; } } } } } if ($this->validateArray('suggest') && !empty($this->config['suggest'])) { foreach ($this->config['suggest'] as $package => $description) { if (!is_string($description)) { $this->errors[] = 'suggest.'.$package.' : invalid value, must be a string describing why the package is suggested'; unset($this->config['suggest'][$package]); } } } if ($this->validateString('minimum-stability') && !empty($this->config['minimum-stability'])) { if (!isset(BasePackage::$stabilities[$this->config['minimum-stability']])) { $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::$stabilities)); unset($this->config['minimum-stability']); } } if ($this->validateArray('autoload') && !empty($this->config['autoload'])) { $types = array('psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap'); foreach ($this->config['autoload'] as $type => $typeConfig) { if (!in_array($type, $types)) { $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types); unset($this->config['autoload'][$type]); } if ($type === 'psr-4') { foreach ($typeConfig as $namespace => $dirs) { if ($namespace !== '' && '\\' !== substr($namespace, -1)) { $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\'; } } } } } if (!empty($this->config['autoload']['psr-4']) && !empty($this->config['target-dir'])) { $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4'; // Unset the psr-4 setting, since unsetting target-dir might // interfere with other settings. unset($this->config['autoload']['psr-4']); } // TODO validate dist // TODO validate source // TODO validate repositories // TODO validate package repositories' packages using this recursively $this->validateFlatArray('include-path'); $this->validateArray('transport-options'); // branch alias validation if (isset($this->config['extra']['branch-alias'])) { if (!is_array($this->config['extra']['branch-alias'])) { $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases'; } else { foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { // ensure it is an alias to a -dev package if ('-dev' !== substr($targetBranch, -4)) { $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } // normalize without -dev and ensure it's a numeric branch that is parseable $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); if ('-dev' !== substr($validatedTargetBranch, -4)) { $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } // If using numeric aliases ensure the alias is a valid subversion if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && (stripos($targetPrefix, $sourcePrefix) !== 0) ) { $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version'; unset($this->config['extra']['branch-alias'][$sourceBranch]); } } } } if ($this->errors) { throw new InvalidPackageException($this->errors, $this->warnings, $config); } $package = $this->loader->load($this->config, $class); $this->config = null; return $package; } public function getWarnings() { return $this->warnings; } public function getErrors() { return $this->errors; } private function validateRegex($property, $regex, $mandatory = false) { if (!$this->validateString($property, $mandatory)) { return false; } if (!preg_match('{^'.$regex.'$}u', $this->config[$property])) { $message = $property.' : invalid value ('.$this->config[$property].'), must match '.$regex; if ($mandatory) { $this->errors[] = $message; } else { $this->warnings[] = $message; } unset($this->config[$property]); return false; } return true; } private function validateString($property, $mandatory = false) { if (isset($this->config[$property]) && !is_string($this->config[$property])) { $this->errors[] = $property.' : should be a string, '.gettype($this->config[$property]).' given'; unset($this->config[$property]); return false; } if (!isset($this->config[$property]) || trim($this->config[$property]) === '') { if ($mandatory) { $this->errors[] = $property.' : must be present'; } unset($this->config[$property]); return false; } return true; } private function validateArray($property, $mandatory = false) { if (isset($this->config[$property]) && !is_array($this->config[$property])) { $this->errors[] = $property.' : should be an array, '.gettype($this->config[$property]).' given'; unset($this->config[$property]); return false; } if (!isset($this->config[$property]) || !count($this->config[$property])) { if ($mandatory) { $this->errors[] = $property.' : must be present and contain at least one element'; } unset($this->config[$property]); return false; } return true; } private function validateFlatArray($property, $regex = null, $mandatory = false) { if (!$this->validateArray($property, $mandatory)) { return false; } $pass = true; foreach ($this->config[$property] as $key => $value) { if (!is_string($value) && !is_numeric($value)) { $this->errors[] = $property.'.'.$key.' : must be a string or int, '.gettype($value).' given'; unset($this->config[$property][$key]); $pass = false; continue; } if ($regex && !preg_match('{^'.$regex.'$}u', $value)) { $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex; unset($this->config[$property][$key]); $pass = false; } } return $pass; } private function validateUrl($property, $mandatory = false) { if (!$this->validateString($property, $mandatory)) { return false; } if (!$this->filterUrl($this->config[$property])) { $this->warnings[] = $property.' : invalid value ('.$this->config[$property].'), must be an http/https URL'; unset($this->config[$property]); return false; } return true; } private function filterUrl($value, array $schemes = array('http', 'https')) { if ($value === '') { return true; } $bits = parse_url($value); if (empty($bits['scheme']) || empty($bits['host'])) { return false; } if (!in_array($bits['scheme'], $schemes, true)) { return false; } return true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Config; use Composer\Package\RootPackageInterface; use Composer\Repository\RepositoryFactory; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Repository\RepositoryManager; use Composer\Util\ProcessExecutor; /** * ArrayLoader built for the sole purpose of loading the root package * * Sets additional defaults and loads repositories * * @author Jordi Boggiano */ class RootPackageLoader extends ArrayLoader { /** * @var RepositoryManager */ private $manager; /** * @var Config */ private $config; /** * @var VersionGuesser */ private $versionGuesser; public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null) { parent::__construct($parser); $this->manager = $manager; $this->config = $config; $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser); } /** * @param array $config package data * @param string $class FQCN to be instantiated * @param string $cwd cwd of the root package to be used to guess the version if it is not provided * @return RootPackageInterface */ public function load(array $config, $class = 'Composer\Package\RootPackage', $cwd = null) { if (!isset($config['name'])) { $config['name'] = '__root__'; } $autoVersioned = false; if (!isset($config['version'])) { // override with env var if available if (getenv('COMPOSER_ROOT_VERSION')) { $version = getenv('COMPOSER_ROOT_VERSION'); $commit = null; } else { $versionData = $this->versionGuesser->guessVersion($config, $cwd ?: getcwd()); $version = $versionData['version']; $commit = $versionData['commit']; } if (!$version) { $version = '1.0.0'; $autoVersioned = true; } $config['version'] = $version; if ($commit) { $config['source'] = array( 'type' => '', 'url' => '', 'reference' => $commit, ); $config['dist'] = array( 'type' => '', 'url' => '', 'reference' => $commit, ); } } $realPackage = $package = parent::load($config, $class); if ($realPackage instanceof AliasPackage) { $realPackage = $package->getAliasOf(); } if ($autoVersioned) { $realPackage->replaceVersion($realPackage->getVersion(), 'No version set (parsed as 1.0.0)'); } if (isset($config['minimum-stability'])) { $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability'])); } $aliases = array(); $stabilityFlags = array(); $references = array(); foreach (array('require', 'require-dev') as $linkType) { if (isset($config[$linkType])) { $linkInfo = BasePackage::$supportedLinkTypes[$linkType]; $method = 'get'.ucfirst($linkInfo['method']); $links = array(); foreach ($realPackage->$method() as $link) { $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); } $aliases = $this->extractAliases($links, $aliases); $stabilityFlags = $this->extractStabilityFlags($links, $stabilityFlags, $realPackage->getMinimumStability()); $references = $this->extractReferences($links, $references); } } if (isset($links[$config['name']])) { throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . 'Did you accidentally name your root package after an external package?', $config['name'])); } $realPackage->setAliases($aliases); $realPackage->setStabilityFlags($stabilityFlags); $realPackage->setReferences($references); if (isset($config['prefer-stable'])) { $realPackage->setPreferStable((bool) $config['prefer-stable']); } if (isset($config['config'])) { $realPackage->setConfig($config['config']); } $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager); foreach ($repos as $repo) { $this->manager->addRepository($repo); } $realPackage->setRepositories($this->config->getRepositories()); return $package; } private function extractAliases(array $requires, array $aliases) { foreach ($requires as $reqName => $reqVersion) { if (preg_match('{^([^,\s#]+)(?:#[^ ]+)? +as +([^,\s]+)$}', $reqVersion, $match)) { $aliases[] = array( 'package' => strtolower($reqName), 'version' => $this->versionParser->normalize($match[1], $reqVersion), 'alias' => $match[2], 'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion), ); } } return $aliases; } private function extractStabilityFlags(array $requires, array $stabilityFlags, $minimumStability) { $stabilities = BasePackage::$stabilities; $minimumStability = $stabilities[$minimumStability]; foreach ($requires as $reqName => $reqVersion) { $constraints = array(); // extract all sub-constraints in case it is an OR/AND multi-constraint $orSplit = preg_split('{\s*\|\|?\s*}', trim($reqVersion)); foreach ($orSplit as $orConstraint) { $andSplit = preg_split('{(?< ,]) *(? $stability) { continue; } $stabilityFlags[$name] = $stability; $match = true; } } if ($match) { continue; } foreach ($constraints as $constraint) { // infer flags for requirements that have an explicit -dev or -beta version specified but only // for those that are more unstable than the minimumStability or existing flags $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $constraint); if (preg_match('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) { $name = strtolower($reqName); $stability = $stabilities[$stabilityName]; if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) { continue; } $stabilityFlags[$name] = $stability; } } } return $stabilityFlags; } private function extractReferences(array $requires, array $references) { foreach ($requires as $reqName => $reqVersion) { $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion); if (preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === ($stabilityName = VersionParser::parseStability($reqVersion))) { $name = strtolower($reqName); $references[$name] = $match[1]; } } return $references; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Json\JsonFile; /** * @author Konstantin Kudryashiv */ class JsonLoader { private $loader; public function __construct(LoaderInterface $loader) { $this->loader = $loader; } /** * @param string|JsonFile $json A filename, json string or JsonFile instance to load the package from * @return \Composer\Package\PackageInterface */ public function load($json) { if ($json instanceof JsonFile) { $config = $json->read(); } elseif (file_exists($json)) { $config = JsonFile::parseJson(file_get_contents($json), $json); } elseif (is_string($json)) { $config = JsonFile::parseJson($json); } return $this->loader->load($config); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; /** * Defines a loader that takes an array to create package instances * * @author Jordi Boggiano */ interface LoaderInterface { /** * Converts a package from an array to a real instance * * @param array $package Package config * @param string $class Package class to use * @return \Composer\Package\PackageInterface */ public function load(array $package, $class = 'Composer\Package\CompletePackage'); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * Defines additional fields that are only needed for the root package * * @author Jordi Boggiano */ interface RootPackageInterface extends CompletePackageInterface { /** * Returns a set of package names and their aliases * * @return array */ public function getAliases(); /** * Returns the minimum stability of the package * * @return string */ public function getMinimumStability(); /** * Returns the stability flags to apply to dependencies * * array('foo/bar' => 'dev') * * @return array */ public function getStabilityFlags(); /** * Returns a set of package names and source references that must be enforced on them * * array('foo/bar' => 'abcd1234') * * @return array */ public function getReferences(); /** * Returns true if the root package prefers picking stable packages over unstable ones * * @return bool */ public function getPreferStable(); /** * Returns the root package's configuration * * @return array */ public function getConfig(); /** * Set the required packages * * @param Link[] $requires A set of package links */ public function setRequires(array $requires); /** * Set the recommended packages * * @param Link[] $devRequires A set of package links */ public function setDevRequires(array $devRequires); /** * Set the conflicting packages * * @param Link[] $conflicts A set of package links */ public function setConflicts(array $conflicts); /** * Set the provided virtual packages * * @param Link[] $provides A set of package links */ public function setProvides(array $provides); /** * Set the packages this one replaces * * @param Link[] $replaces A set of package links */ public function setReplaces(array $replaces); /** * Set the repositories * * @param array $repositories */ public function setRepositories($repositories); /** * Set the autoload mapping * * @param array $autoload Mapping of autoloading rules */ public function setAutoload(array $autoload); /** * Set the dev autoload mapping * * @param array $devAutoload Mapping of dev autoloading rules */ public function setDevAutoload(array $devAutoload); /** * Set the stabilityFlags * * @param array $stabilityFlags */ public function setStabilityFlags(array $stabilityFlags); /** * Set the suggested packages * * @param array $suggests A set of package names/comments */ public function setSuggests(array $suggests); /** * @param array $extra */ public function setExtra(array $extra); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Json\JsonFile; use Composer\Installer\InstallationManager; use Composer\Repository\RepositoryManager; use Composer\Util\ProcessExecutor; use Composer\Repository\ArrayRepository; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Loader\ArrayLoader; use Composer\Util\Git as GitUtil; use Composer\IO\IOInterface; use Seld\JsonLint\ParsingException; /** * Reads/writes project lockfile (composer.lock). * * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class Locker { private $lockFile; private $repositoryManager; private $installationManager; private $hash; private $contentHash; private $loader; private $dumper; private $process; private $lockDataCache; /** * Initializes packages locker. * * @param IOInterface $io * @param JsonFile $lockFile lockfile loader * @param RepositoryManager $repositoryManager repository manager instance * @param InstallationManager $installationManager installation manager instance * @param string $composerFileContents The contents of the composer file */ public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $composerFileContents) { $this->lockFile = $lockFile; $this->repositoryManager = $repositoryManager; $this->installationManager = $installationManager; $this->hash = md5($composerFileContents); $this->contentHash = self::getContentHash($composerFileContents); $this->loader = new ArrayLoader(null, true); $this->dumper = new ArrayDumper(); $this->process = new ProcessExecutor($io); } /** * Returns the md5 hash of the sorted content of the composer file. * * @param string $composerFileContents The contents of the composer file. * * @return string */ public static function getContentHash($composerFileContents) { $content = json_decode($composerFileContents, true); $relevantKeys = array( 'name', 'version', 'require', 'require-dev', 'conflict', 'replace', 'provide', 'minimum-stability', 'prefer-stable', 'repositories', 'extra', ); $relevantContent = array(); foreach (array_intersect($relevantKeys, array_keys($content)) as $key) { $relevantContent[$key] = $content[$key]; } if (isset($content['config']['platform'])) { $relevantContent['config']['platform'] = $content['config']['platform']; } ksort($relevantContent); return md5(json_encode($relevantContent)); } /** * Checks whether locker were been locked (lockfile found). * * @return bool */ public function isLocked() { if (!$this->lockFile->exists()) { return false; } $data = $this->getLockData(); return isset($data['packages']); } /** * Checks whether the lock file is still up to date with the current hash * * @return bool */ public function isFresh() { $lock = $this->lockFile->read(); if (!empty($lock['content-hash'])) { // There is a content hash key, use that instead of the file hash return $this->contentHash === $lock['content-hash']; } // BC support for old lock files without content-hash if (!empty($lock['hash'])) { return $this->hash === $lock['hash']; } // should not be reached unless the lock file is corrupted, so assume it's out of date return false; } /** * Searches and returns an array of locked packages, retrieved from registered repositories. * * @param bool $withDevReqs true to retrieve the locked dev packages * @throws \RuntimeException * @return \Composer\Repository\RepositoryInterface */ public function getLockedRepository($withDevReqs = false) { $lockData = $this->getLockData(); $packages = new ArrayRepository(); $lockedPackages = $lockData['packages']; if ($withDevReqs) { if (isset($lockData['packages-dev'])) { $lockedPackages = array_merge($lockedPackages, $lockData['packages-dev']); } else { throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or run update to install those packages.'); } } if (empty($lockedPackages)) { return $packages; } if (isset($lockedPackages[0]['name'])) { foreach ($lockedPackages as $info) { $packages->addPackage($this->loader->load($info)); } return $packages; } throw new \RuntimeException('Your composer.lock was created before 2012-09-15, and is not supported anymore. Run "composer update" to generate a new one.'); } /** * Returns the platform requirements stored in the lock file * * @param bool $withDevReqs if true, the platform requirements from the require-dev block are also returned * @return \Composer\Package\Link[] */ public function getPlatformRequirements($withDevReqs = false) { $lockData = $this->getLockData(); $requirements = array(); if (!empty($lockData['platform'])) { $requirements = $this->loader->parseLinks( '__ROOT__', '1.0.0', 'requires', isset($lockData['platform']) ? $lockData['platform'] : array() ); } if ($withDevReqs && !empty($lockData['platform-dev'])) { $devRequirements = $this->loader->parseLinks( '__ROOT__', '1.0.0', 'requires', isset($lockData['platform-dev']) ? $lockData['platform-dev'] : array() ); $requirements = array_merge($requirements, $devRequirements); } return $requirements; } public function getMinimumStability() { $lockData = $this->getLockData(); return isset($lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'stable'; } public function getStabilityFlags() { $lockData = $this->getLockData(); return isset($lockData['stability-flags']) ? $lockData['stability-flags'] : array(); } public function getPreferStable() { $lockData = $this->getLockData(); // return null if not set to allow caller logic to choose the // right behavior since old lock files have no prefer-stable return isset($lockData['prefer-stable']) ? $lockData['prefer-stable'] : null; } public function getPreferLowest() { $lockData = $this->getLockData(); // return null if not set to allow caller logic to choose the // right behavior since old lock files have no prefer-lowest return isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null; } public function getPlatformOverrides() { $lockData = $this->getLockData(); return isset($lockData['platform-overrides']) ? $lockData['platform-overrides'] : array(); } public function getAliases() { $lockData = $this->getLockData(); return isset($lockData['aliases']) ? $lockData['aliases'] : array(); } public function getLockData() { if (null !== $this->lockDataCache) { return $this->lockDataCache; } if (!$this->lockFile->exists()) { throw new \LogicException('No lockfile found. Unable to read locked packages'); } return $this->lockDataCache = $this->lockFile->read(); } /** * Locks provided data into lockfile. * * @param array $packages array of packages * @param mixed $devPackages array of dev packages or null if installed without --dev * @param array $platformReqs array of package name => constraint for required platform packages * @param mixed $platformDevReqs array of package name => constraint for dev-required platform packages * @param array $aliases array of aliases * @param string $minimumStability * @param array $stabilityFlags * @param bool $preferStable * @param bool $preferLowest * @param array $platformOverrides * * @return bool */ public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest, array $platformOverrides) { $lock = array( '_readme' => array('This file locks the dependencies of your project to a known state', 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file', 'This file is @gener'.'ated automatically', ), 'content-hash' => $this->contentHash, 'packages' => null, 'packages-dev' => null, 'aliases' => array(), 'minimum-stability' => $minimumStability, 'stability-flags' => $stabilityFlags, 'prefer-stable' => $preferStable, 'prefer-lowest' => $preferLowest, ); foreach ($aliases as $package => $versions) { foreach ($versions as $version => $alias) { $lock['aliases'][] = array( 'alias' => $alias['alias'], 'alias_normalized' => $alias['alias_normalized'], 'version' => $version, 'package' => $package, ); } } $lock['packages'] = $this->lockPackages($packages); if (null !== $devPackages) { $lock['packages-dev'] = $this->lockPackages($devPackages); } $lock['platform'] = $platformReqs; $lock['platform-dev'] = $platformDevReqs; if ($platformOverrides) { $lock['platform-overrides'] = $platformOverrides; } if (empty($lock['packages']) && empty($lock['packages-dev']) && empty($lock['platform']) && empty($lock['platform-dev'])) { if ($this->lockFile->exists()) { unlink($this->lockFile->getPath()); } return false; } try { $isLocked = $this->isLocked(); } catch (ParsingException $e) { $isLocked = false; } if (!$isLocked || $lock !== $this->getLockData()) { $this->lockFile->write($lock); $this->lockDataCache = null; return true; } return false; } private function lockPackages(array $packages) { $locked = array(); foreach ($packages as $package) { if ($package instanceof AliasPackage) { continue; } $name = $package->getPrettyName(); $version = $package->getPrettyVersion(); if (!$name || !$version) { throw new \LogicException(sprintf( 'Package "%s" has no version or name and can not be locked', $package )); } $spec = $this->dumper->dump($package); unset($spec['version_normalized']); // always move time to the end of the package definition $time = isset($spec['time']) ? $spec['time'] : null; unset($spec['time']); if ($package->isDev() && $package->getInstallationSource() === 'source') { // use the exact commit time of the current reference if it's a dev package $time = $this->getPackageTime($package) ?: $time; } if (null !== $time) { $spec['time'] = $time; } unset($spec['installation-source']); $locked[] = $spec; } usort($locked, function ($a, $b) { $comparison = strcmp($a['name'], $b['name']); if (0 !== $comparison) { return $comparison; } // If it is the same package, compare the versions to make the order deterministic return strcmp($a['version'], $b['version']); }); return $locked; } /** * Returns the packages's datetime for its source reference. * * @param PackageInterface $package The package to scan. * @return string|null The formatted datetime or null if none was found. */ private function getPackageTime(PackageInterface $package) { if (!function_exists('proc_open')) { return null; } $path = realpath($this->installationManager->getInstallPath($package)); $sourceType = $package->getSourceType(); $datetime = null; if ($path && in_array($sourceType, array('git', 'hg'))) { $sourceRef = $package->getSourceReference() ?: $package->getDistReference(); switch ($sourceType) { case 'git': GitUtil::cleanEnv(); if (0 === $this->process->execute('git log -n1 --pretty=%ct '.ProcessExecutor::escape($sourceRef), $output, $path) && preg_match('{^\s*\d+\s*$}', $output)) { $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); } break; case 'hg': if (0 === $this->process->execute('hg log --template "{date|hgdate}" -r '.ProcessExecutor::escape($sourceRef), $output, $path) && preg_match('{^\s*(\d+)\s*}', $output, $match)) { $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC')); } break; } } return $datetime ? $datetime->format(DATE_RFC3339) : null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Dumper; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\RootPackageInterface; /** * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class ArrayDumper { public function dump(PackageInterface $package) { $keys = array( 'binaries' => 'bin', 'type', 'extra', 'installationSource' => 'installation-source', 'autoload', 'devAutoload' => 'autoload-dev', 'notificationUrl' => 'notification-url', 'includePaths' => 'include-path', ); $data = array(); $data['name'] = $package->getPrettyName(); $data['version'] = $package->getPrettyVersion(); $data['version_normalized'] = $package->getVersion(); if ($package->getTargetDir()) { $data['target-dir'] = $package->getTargetDir(); } if ($package->getSourceType()) { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); $data['source']['reference'] = $package->getSourceReference(); if ($mirrors = $package->getSourceMirrors()) { $data['source']['mirrors'] = $mirrors; } } if ($package->getDistType()) { $data['dist']['type'] = $package->getDistType(); $data['dist']['url'] = $package->getDistUrl(); $data['dist']['reference'] = $package->getDistReference(); $data['dist']['shasum'] = $package->getDistSha1Checksum(); if ($mirrors = $package->getDistMirrors()) { $data['dist']['mirrors'] = $mirrors; } } if ($package->getArchiveExcludes()) { $data['archive']['exclude'] = $package->getArchiveExcludes(); } foreach (BasePackage::$supportedLinkTypes as $type => $opts) { if ($links = $package->{'get'.ucfirst($opts['method'])}()) { foreach ($links as $link) { $data[$type][$link->getTarget()] = $link->getPrettyConstraint(); } ksort($data[$type]); } } if ($packages = $package->getSuggests()) { ksort($packages); $data['suggest'] = $packages; } if ($package->getReleaseDate()) { $data['time'] = $package->getReleaseDate()->format(DATE_RFC3339); } $data = $this->dumpValues($package, $keys, $data); if ($package instanceof CompletePackageInterface) { $keys = array( 'scripts', 'license', 'authors', 'description', 'homepage', 'keywords', 'repositories', 'support', ); $data = $this->dumpValues($package, $keys, $data); if (isset($data['keywords']) && is_array($data['keywords'])) { sort($data['keywords']); } if ($package->isAbandoned()) { $data['abandoned'] = $package->getReplacementPackage() ?: true; } } if ($package instanceof RootPackageInterface) { $minimumStability = $package->getMinimumStability(); if ($minimumStability) { $data['minimum-stability'] = $minimumStability; } } if (count($package->getTransportOptions()) > 0) { $data['transport-options'] = $package->getTransportOptions(); } return $data; } private function dumpValues(PackageInterface $package, array $keys, array $data) { foreach ($keys as $method => $key) { if (is_numeric($method)) { $method = $key; } $getter = 'get'.ucfirst($method); $value = $package->$getter(); if (null !== $value && !(is_array($value) && 0 === count($value))) { $data[$key] = $value; } } return $data; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Semver\Constraint\Constraint; use Composer\Package\Version\VersionParser; /** * @author Jordi Boggiano */ class AliasPackage extends BasePackage implements CompletePackageInterface { protected $version; protected $prettyVersion; protected $dev; protected $rootPackageAlias = false; protected $stability; /** @var PackageInterface */ protected $aliasOf; /** @var Link[] */ protected $requires; /** @var Link[] */ protected $devRequires; /** @var Link[] */ protected $conflicts; /** @var Link[] */ protected $provides; /** @var Link[] */ protected $replaces; /** * All descendants' constructors should call this parent constructor * * @param PackageInterface $aliasOf The package this package is an alias of * @param string $version The version the alias must report * @param string $prettyVersion The alias's non-normalized version */ public function __construct(PackageInterface $aliasOf, $version, $prettyVersion) { parent::__construct($aliasOf->getName()); $this->version = $version; $this->prettyVersion = $prettyVersion; $this->aliasOf = $aliasOf; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; foreach (array('requires', 'devRequires', 'conflicts', 'provides', 'replaces') as $type) { $links = $aliasOf->{'get' . ucfirst($type)}(); $this->$type = $this->replaceSelfVersionDependencies($links, $type); } } /** * @return PackageInterface */ public function getAliasOf() { return $this->aliasOf; } /** * {@inheritDoc} */ public function getVersion() { return $this->version; } /** * {@inheritDoc} */ public function getStability() { return $this->stability; } /** * {@inheritDoc} */ public function getPrettyVersion() { return $this->prettyVersion; } /** * {@inheritDoc} */ public function isDev() { return $this->dev; } /** * {@inheritDoc} */ public function getRequires() { return $this->requires; } /** * {@inheritDoc} */ public function getConflicts() { return $this->conflicts; } /** * {@inheritDoc} */ public function getProvides() { return $this->provides; } /** * {@inheritDoc} */ public function getReplaces() { return $this->replaces; } /** * {@inheritDoc} */ public function getDevRequires() { return $this->devRequires; } /** * Stores whether this is an alias created by an aliasing in the requirements of the root package or not * * Use by the policy for sorting manually aliased packages first, see #576 * * @param bool $value * * @return mixed */ public function setRootPackageAlias($value) { return $this->rootPackageAlias = $value; } /** * @see setRootPackageAlias * @return bool */ public function isRootPackageAlias() { return $this->rootPackageAlias; } /** * @param Link[] $links * @param string $linkType * * @return Link[] */ protected function replaceSelfVersionDependencies(array $links, $linkType) { if (in_array($linkType, array('conflicts', 'provides', 'replaces'), true)) { $newLinks = array(); foreach ($links as $link) { // link is self.version, but must be replacing also the replaced version if ('self.version' === $link->getPrettyConstraint()) { $newLinks[] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion); } } $links = array_merge($links, $newLinks); } else { foreach ($links as $index => $link) { if ('self.version' === $link->getPrettyConstraint()) { $links[$index] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion); } } } return $links; } /*************************************** * Wrappers around the aliased package * ***************************************/ public function getType() { return $this->aliasOf->getType(); } public function getTargetDir() { return $this->aliasOf->getTargetDir(); } public function getExtra() { return $this->aliasOf->getExtra(); } public function setInstallationSource($type) { $this->aliasOf->setInstallationSource($type); } public function getInstallationSource() { return $this->aliasOf->getInstallationSource(); } public function getSourceType() { return $this->aliasOf->getSourceType(); } public function getSourceUrl() { return $this->aliasOf->getSourceUrl(); } public function getSourceUrls() { return $this->aliasOf->getSourceUrls(); } public function getSourceReference() { return $this->aliasOf->getSourceReference(); } public function setSourceReference($reference) { return $this->aliasOf->setSourceReference($reference); } public function setSourceMirrors($mirrors) { return $this->aliasOf->setSourceMirrors($mirrors); } public function getSourceMirrors() { return $this->aliasOf->getSourceMirrors(); } public function getDistType() { return $this->aliasOf->getDistType(); } public function getDistUrl() { return $this->aliasOf->getDistUrl(); } public function getDistUrls() { return $this->aliasOf->getDistUrls(); } public function getDistReference() { return $this->aliasOf->getDistReference(); } public function setDistReference($reference) { return $this->aliasOf->setDistReference($reference); } public function getDistSha1Checksum() { return $this->aliasOf->getDistSha1Checksum(); } public function setTransportOptions(array $options) { return $this->aliasOf->setTransportOptions($options); } public function getTransportOptions() { return $this->aliasOf->getTransportOptions(); } public function setDistMirrors($mirrors) { return $this->aliasOf->setDistMirrors($mirrors); } public function getDistMirrors() { return $this->aliasOf->getDistMirrors(); } public function getScripts() { return $this->aliasOf->getScripts(); } public function getLicense() { return $this->aliasOf->getLicense(); } public function getAutoload() { return $this->aliasOf->getAutoload(); } public function getDevAutoload() { return $this->aliasOf->getDevAutoload(); } public function getIncludePaths() { return $this->aliasOf->getIncludePaths(); } public function getRepositories() { return $this->aliasOf->getRepositories(); } public function getReleaseDate() { return $this->aliasOf->getReleaseDate(); } public function getBinaries() { return $this->aliasOf->getBinaries(); } public function getKeywords() { return $this->aliasOf->getKeywords(); } public function getDescription() { return $this->aliasOf->getDescription(); } public function getHomepage() { return $this->aliasOf->getHomepage(); } public function getSuggests() { return $this->aliasOf->getSuggests(); } public function getAuthors() { return $this->aliasOf->getAuthors(); } public function getSupport() { return $this->aliasOf->getSupport(); } public function getNotificationUrl() { return $this->aliasOf->getNotificationUrl(); } public function getArchiveExcludes() { return $this->aliasOf->getArchiveExcludes(); } public function isAbandoned() { return $this->aliasOf->isAbandoned(); } public function getReplacementPackage() { return $this->aliasOf->getReplacementPackage(); } public function __toString() { return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Package\Version\VersionParser; use Composer\Util\ComposerMirror; /** * Core package definitions that are needed to resolve dependencies and install packages * * @author Nils Adermann */ class Package extends BasePackage { protected $type; protected $targetDir; protected $installationSource; protected $sourceType; protected $sourceUrl; protected $sourceReference; protected $sourceMirrors; protected $distType; protected $distUrl; protected $distReference; protected $distSha1Checksum; protected $distMirrors; protected $version; protected $prettyVersion; protected $releaseDate; protected $extra = array(); protected $binaries = array(); protected $dev; protected $stability; protected $notificationUrl; /** @var Link[] */ protected $requires = array(); /** @var Link[] */ protected $conflicts = array(); /** @var Link[] */ protected $provides = array(); /** @var Link[] */ protected $replaces = array(); /** @var Link[] */ protected $devRequires = array(); protected $suggests = array(); protected $autoload = array(); protected $devAutoload = array(); protected $includePaths = array(); protected $archiveExcludes = array(); /** * Creates a new in memory package. * * @param string $name The package's name * @param string $version The package's version * @param string $prettyVersion The package's non-normalized version */ public function __construct($name, $version, $prettyVersion) { parent::__construct($name); $this->version = $version; $this->prettyVersion = $prettyVersion; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; } /** * {@inheritDoc} */ public function isDev() { return $this->dev; } /** * @param string $type */ public function setType($type) { $this->type = $type; } /** * {@inheritDoc} */ public function getType() { return $this->type ?: 'library'; } /** * {@inheritDoc} */ public function getStability() { return $this->stability; } /** * @param string $targetDir */ public function setTargetDir($targetDir) { $this->targetDir = $targetDir; } /** * {@inheritDoc} */ public function getTargetDir() { if (null === $this->targetDir) { return; } return ltrim(preg_replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/'); } /** * @param array $extra */ public function setExtra(array $extra) { $this->extra = $extra; } /** * {@inheritDoc} */ public function getExtra() { return $this->extra; } /** * @param array $binaries */ public function setBinaries(array $binaries) { $this->binaries = $binaries; } /** * {@inheritDoc} */ public function getBinaries() { return $this->binaries; } /** * {@inheritDoc} */ public function setInstallationSource($type) { $this->installationSource = $type; } /** * {@inheritDoc} */ public function getInstallationSource() { return $this->installationSource; } /** * @param string $type */ public function setSourceType($type) { $this->sourceType = $type; } /** * {@inheritDoc} */ public function getSourceType() { return $this->sourceType; } /** * @param string $url */ public function setSourceUrl($url) { $this->sourceUrl = $url; } /** * {@inheritDoc} */ public function getSourceUrl() { return $this->sourceUrl; } /** * @param string $reference */ public function setSourceReference($reference) { $this->sourceReference = $reference; } /** * {@inheritDoc} */ public function getSourceReference() { return $this->sourceReference; } /** * @param array|null $mirrors */ public function setSourceMirrors($mirrors) { $this->sourceMirrors = $mirrors; } /** * {@inheritDoc} */ public function getSourceMirrors() { return $this->sourceMirrors; } /** * {@inheritDoc} */ public function getSourceUrls() { return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source'); } /** * @param string $type */ public function setDistType($type) { $this->distType = $type; } /** * {@inheritDoc} */ public function getDistType() { return $this->distType; } /** * @param string $url */ public function setDistUrl($url) { $this->distUrl = $url; } /** * {@inheritDoc} */ public function getDistUrl() { return $this->distUrl; } /** * @param string $reference */ public function setDistReference($reference) { $this->distReference = $reference; } /** * {@inheritDoc} */ public function getDistReference() { return $this->distReference; } /** * @param string $sha1checksum */ public function setDistSha1Checksum($sha1checksum) { $this->distSha1Checksum = $sha1checksum; } /** * {@inheritDoc} */ public function getDistSha1Checksum() { return $this->distSha1Checksum; } /** * @param array|null $mirrors */ public function setDistMirrors($mirrors) { $this->distMirrors = $mirrors; } /** * {@inheritDoc} */ public function getDistMirrors() { return $this->distMirrors; } /** * {@inheritDoc} */ public function getDistUrls() { return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist'); } /** * {@inheritDoc} */ public function getVersion() { return $this->version; } /** * {@inheritDoc} */ public function getPrettyVersion() { return $this->prettyVersion; } /** * Set the releaseDate * * @param \DateTime $releaseDate */ public function setReleaseDate(\DateTime $releaseDate) { $this->releaseDate = $releaseDate; } /** * {@inheritDoc} */ public function getReleaseDate() { return $this->releaseDate; } /** * Set the required packages * * @param Link[] $requires A set of package links */ public function setRequires(array $requires) { $this->requires = $requires; } /** * {@inheritDoc} */ public function getRequires() { return $this->requires; } /** * Set the conflicting packages * * @param Link[] $conflicts A set of package links */ public function setConflicts(array $conflicts) { $this->conflicts = $conflicts; } /** * {@inheritDoc} */ public function getConflicts() { return $this->conflicts; } /** * Set the provided virtual packages * * @param Link[] $provides A set of package links */ public function setProvides(array $provides) { $this->provides = $provides; } /** * {@inheritDoc} */ public function getProvides() { return $this->provides; } /** * Set the packages this one replaces * * @param Link[] $replaces A set of package links */ public function setReplaces(array $replaces) { $this->replaces = $replaces; } /** * {@inheritDoc} */ public function getReplaces() { return $this->replaces; } /** * Set the recommended packages * * @param Link[] $devRequires A set of package links */ public function setDevRequires(array $devRequires) { $this->devRequires = $devRequires; } /** * {@inheritDoc} */ public function getDevRequires() { return $this->devRequires; } /** * Set the suggested packages * * @param array $suggests A set of package names/comments */ public function setSuggests(array $suggests) { $this->suggests = $suggests; } /** * {@inheritDoc} */ public function getSuggests() { return $this->suggests; } /** * Set the autoload mapping * * @param array $autoload Mapping of autoloading rules */ public function setAutoload(array $autoload) { $this->autoload = $autoload; } /** * {@inheritDoc} */ public function getAutoload() { return $this->autoload; } /** * Set the dev autoload mapping * * @param array $devAutoload Mapping of dev autoloading rules */ public function setDevAutoload(array $devAutoload) { $this->devAutoload = $devAutoload; } /** * {@inheritDoc} */ public function getDevAutoload() { return $this->devAutoload; } /** * Sets the list of paths added to PHP's include path. * * @param array $includePaths List of directories. */ public function setIncludePaths(array $includePaths) { $this->includePaths = $includePaths; } /** * {@inheritDoc} */ public function getIncludePaths() { return $this->includePaths; } /** * Sets the notification URL * * @param string $notificationUrl */ public function setNotificationUrl($notificationUrl) { $this->notificationUrl = $notificationUrl; } /** * {@inheritDoc} */ public function getNotificationUrl() { return $this->notificationUrl; } /** * Sets a list of patterns to be excluded from archives * * @param array $excludes */ public function setArchiveExcludes(array $excludes) { $this->archiveExcludes = $excludes; } /** * {@inheritDoc} */ public function getArchiveExcludes() { return $this->archiveExcludes; } /** * Replaces current version and pretty version with passed values. * It also sets stability. * * @param string $version The package's normalized version * @param string $prettyVersion The package's non-normalized version */ public function replaceVersion($version, $prettyVersion) { $this->version = $version; $this->prettyVersion = $prettyVersion; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; } protected function getUrls($url, $mirrors, $ref, $type, $urlType) { if (!$url) { return array(); } $urls = array($url); if ($mirrors) { foreach ($mirrors as $mirror) { if ($urlType === 'dist') { $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type); } elseif ($urlType === 'source' && $type === 'git') { $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type); } elseif ($urlType === 'source' && $type === 'hg') { $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type); } if (!in_array($mirrorUrl, $urls)) { $func = $mirror['preferred'] ? 'array_unshift' : 'array_push'; $func($urls, $mirrorUrl); } } } return $urls; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\DependencyResolver\Pool; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; use Composer\Semver\Constraint\Constraint; /** * Selects the best possible version for a package * * @author Ryan Weaver * @author Jordi Boggiano */ class VersionSelector { private $pool; private $parser; public function __construct(Pool $pool) { $this->pool = $pool; } /** * Given a package name and optional version, returns the latest PackageInterface * that matches. * * @param string $packageName * @param string $targetPackageVersion * @param string $targetPhpVersion * @param string $preferredStability * @return PackageInterface|bool */ public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable') { $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; $candidates = $this->pool->whatProvides(strtolower($packageName), $constraint, true); if ($targetPhpVersion) { $phpConstraint = new Constraint('==', $this->getParser()->normalize($targetPhpVersion)); $candidates = array_filter($candidates, function ($pkg) use ($phpConstraint) { $reqs = $pkg->getRequires(); return !isset($reqs['php']) || $reqs['php']->getConstraint()->matches($phpConstraint); }); } if (!$candidates) { return false; } // select highest version if we have many $package = reset($candidates); $minPriority = BasePackage::$stabilities[$preferredStability]; foreach ($candidates as $candidate) { $candidatePriority = $candidate->getStabilityPriority(); $currentPriority = $package->getStabilityPriority(); // candidate is less stable than our preferred stability, // and current package is more stable than candidate, skip it if ($minPriority < $candidatePriority && $currentPriority < $candidatePriority) { continue; } // candidate is less stable than our preferred stability, // and current package is less stable than candidate, select candidate if ($minPriority < $candidatePriority && $candidatePriority < $currentPriority) { $package = $candidate; continue; } // candidate is more stable than our preferred stability, // and current package is less stable than preferred stability, select candidate if ($minPriority >= $candidatePriority && $minPriority < $currentPriority) { $package = $candidate; continue; } // select highest version of the two if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { $package = $candidate; } } return $package; } /** * Given a concrete version, this returns a ~ constraint (when possible) * that should be used, for example, in composer.json. * * For example: * * 1.2.1 -> ^1.2 * * 1.2 -> ^1.2 * * v3.2.1 -> ^3.2 * * 2.0-beta.1 -> ^2.0@beta * * dev-master -> ^2.1@dev (dev version with alias) * * dev-master -> dev-master (dev versions are untouched) * * @param PackageInterface $package * @return string */ public function findRecommendedRequireVersion(PackageInterface $package) { $version = $package->getVersion(); if (!$package->isDev()) { return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability()); } $loader = new ArrayLoader($this->getParser()); $dumper = new ArrayDumper(); $extra = $loader->getBranchAlias($dumper->dump($package)); if ($extra) { $extra = preg_replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count); if ($count) { $extra = str_replace('.9999999', '.0', $extra); return $this->transformVersion($extra, $extra, 'dev'); } } return $package->getPrettyVersion(); } private function transformVersion($version, $prettyVersion, $stability) { // attempt to transform 2.1.1 to 2.1 // this allows you to upgrade through minor versions $semanticVersionParts = explode('.', $version); // check to see if we have a semver-looking version if (count($semanticVersionParts) == 4 && preg_match('{^0\D?}', $semanticVersionParts[3])) { // remove the last parts (i.e. the patch version number and any extra) if ($semanticVersionParts[0] === '0') { unset($semanticVersionParts[3]); } else { unset($semanticVersionParts[2], $semanticVersionParts[3]); } $version = implode('.', $semanticVersionParts); } else { return $prettyVersion; } // append stability flag if not default if ($stability != 'stable') { $version .= '@'.$stability; } // 2.1 -> ^2.1 return '^' . $version; } private function getParser() { if ($this->parser === null) { $this->parser = new VersionParser(); } return $this->parser; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\Semver\VersionParser as SemverVersionParser; class VersionParser extends SemverVersionParser { private static $constraints = array(); /** * {@inheritDoc} */ public function parseConstraints($constraints) { if (!isset(self::$constraints[$constraints])) { self::$constraints[$constraints] = parent::parseConstraints($constraints); } return self::$constraints[$constraints]; } /** * Parses an array of strings representing package/version pairs. * * The parsing results in an array of arrays, each of which * contain a 'name' key with value and optionally a 'version' key with value. * * @param array $pairs a set of package/version pairs separated by ":", "=" or " " * * @return array[] array of arrays containing a name and (if provided) a version */ public function parseNameVersionPairs(array $pairs) { $pairs = array_values($pairs); $result = array(); for ($i = 0, $count = count($pairs); $i < $count; $i++) { $pair = preg_replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', trim($pairs[$i])); if (false === strpos($pair, ' ') && isset($pairs[$i + 1]) && false === strpos($pairs[$i + 1], '/')) { $pair .= ' '.$pairs[$i + 1]; $i++; } if (strpos($pair, ' ')) { list($name, $version) = explode(" ", $pair, 2); $result[] = array('name' => $name, 'version' => $version); } else { $result[] = array('name' => $pair); } } return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\Config; use Composer\Repository\Vcs\HgDriver; use Composer\IO\NullIO; use Composer\Semver\VersionParser as SemverVersionParser; use Composer\Util\Git as GitUtil; use Composer\Util\ProcessExecutor; use Composer\Util\Svn as SvnUtil; /** * Try to guess the current version number based on different VCS configuration. * * @author Jordi Boggiano * @author Samuel Roze */ class VersionGuesser { /** * @var Config */ private $config; /** * @var ProcessExecutor */ private $process; /** * @var SemverVersionParser */ private $versionParser; /** * @param Config $config * @param ProcessExecutor $process * @param SemverVersionParser $versionParser */ public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser) { $this->config = $config; $this->process = $process; $this->versionParser = $versionParser; } /** * @param array $packageConfig * @param string $path Path to guess into * * @return array versionData, 'version', 'pretty_version' and 'commit' keys */ public function guessVersion(array $packageConfig, $path) { if (function_exists('proc_open')) { $versionData = $this->guessGitVersion($packageConfig, $path); if (null !== $versionData && null !== $versionData['version']) { return $versionData; } $versionData = $this->guessHgVersion($packageConfig, $path); if (null !== $versionData && null !== $versionData['version']) { return $versionData; } $versionData = $this->guessFossilVersion($packageConfig, $path); if (null !== $versionData && null !== $versionData['version']) { return $versionData; } return $this->guessSvnVersion($packageConfig, $path); } } private function guessGitVersion(array $packageConfig, $path) { GitUtil::cleanEnv(); $commit = null; $version = null; $prettyVersion = null; $isDetached = false; // try to fetch current version from git branch if (0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output, $path)) { $branches = array(); $isFeatureBranch = false; // find current branch and collect all branch names foreach ($this->process->splitLines($output) as $branch) { if ($branch && preg_match('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) { if ($match[1] === '(no branch)' || substr($match[1], 0, 10) === '(detached ' || substr($match[1], 0, 17) === '(HEAD detached at') { $version = 'dev-' . $match[2]; $prettyVersion = $version; $isFeatureBranch = true; $isDetached = true; } else { $version = $this->versionParser->normalizeBranch($match[1]); $prettyVersion = 'dev-' . $match[1]; $isFeatureBranch = 0 === strpos($version, 'dev-'); if ('9999999-dev' === $version) { $version = $prettyVersion; } } if ($match[2]) { $commit = $match[2]; } } if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) { $branches[] = $match[1]; } } } if ($isFeatureBranch) { // try to find the best (nearest) version branch to assume this feature's version $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path); $version = $result['version']; $prettyVersion = $result['pretty_version']; } } if (!$version || $isDetached) { $result = $this->versionFromGitTags($path); if ($result) { $version = $result['version']; $prettyVersion = $result['pretty_version']; } } if (!$commit) { $command = 'git log --pretty="%H" -n1 HEAD'; if (0 === $this->process->execute($command, $output, $path)) { $commit = trim($output) ?: null; } } return array('version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion); } private function versionFromGitTags($path) { // try to fetch current version from git tags if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) { try { $version = $this->versionParser->normalize(trim($output)); return array('version' => $version, 'pretty_version' => trim($output)); } catch (\Exception $e) { } } return null; } private function guessHgVersion(array $packageConfig, $path) { // try to fetch current version from hg branch if (0 === $this->process->execute('hg branch', $output, $path)) { $branch = trim($output); $version = $this->versionParser->normalizeBranch($branch); $isFeatureBranch = 0 === strpos($version, 'dev-'); if ('9999999-dev' === $version) { $version = 'dev-' . $branch; } if (!$isFeatureBranch) { return array('version' => $version, 'commit' => null, 'pretty_version' => $version); } // re-use the HgDriver to fetch branches (this properly includes bookmarks) $driver = new HgDriver(array('url' => $path), new NullIO(), $this->config, $this->process); $branches = array_keys($driver->getBranches()); // try to find the best (nearest) version branch to assume this feature's version $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path); $result['commit'] = ''; return $result; } } private function guessFeatureVersion(array $packageConfig, $version, array $branches, $scmCmdline, $path) { $prettyVersion = $version; // ignore feature branches if they have no branch-alias or self.version is used // and find the branch they came from to use as a version instead if ((isset($packageConfig['extra']['branch-alias']) && !isset($packageConfig['extra']['branch-alias'][$version])) || strpos(json_encode($packageConfig), '"self.version"') ) { $branch = preg_replace('{^dev-}', '', $version); $length = PHP_INT_MAX; $nonFeatureBranches = ''; if (!empty($packageConfig['non-feature-branches'])) { $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']); } foreach ($branches as $candidate) { // return directly, if branch is configured to be non-feature branch if ($candidate === $branch && preg_match('{^(' . $nonFeatureBranches . ')$}', $candidate)) { break; } // do not compare against itself or other feature branches if ($candidate === $branch || !preg_match('{^(' . $nonFeatureBranches . '|master|trunk|default|develop|\d+\..+)$}', $candidate, $match)) { continue; } $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline); if (0 !== $this->process->execute($cmdLine, $output, $path)) { continue; } if (strlen($output) < $length) { $length = strlen($output); $version = $this->versionParser->normalizeBranch($candidate); $prettyVersion = 'dev-' . $match[1]; if ('9999999-dev' === $version) { $version = $prettyVersion; } } } } return array('version' => $version, 'pretty_version' => $prettyVersion); } private function guessFossilVersion(array $packageConfig, $path) { $version = null; $prettyVersion = null; // try to fetch current version from fossil if (0 === $this->process->execute('fossil branch list', $output, $path)) { $branch = trim($output); $version = $this->versionParser->normalizeBranch($branch); $prettyVersion = 'dev-' . $branch; if ('9999999-dev' === $version) { $version = $prettyVersion; } } // try to fetch current version from fossil tags if (0 === $this->process->execute('fossil tag list', $output, $path)) { try { $version = $this->versionParser->normalize(trim($output)); $prettyVersion = trim($output); } catch (\Exception $e) { } } return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion); } private function guessSvnVersion(array $packageConfig, $path) { SvnUtil::cleanEnv(); // try to fetch current version from svn if (0 === $this->process->execute('svn info --xml', $output, $path)) { $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk'; $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches'; $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags'; $urlPattern = '#.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))#'; if (preg_match($urlPattern, $output, $matches)) { if (isset($matches[2]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) { // we are in a branches path $version = $this->versionParser->normalizeBranch($matches[3]); $prettyVersion = 'dev-' . $matches[3]; if ('9999999-dev' === $version) { $version = $prettyVersion; } return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion); } $prettyVersion = trim($matches[1]); $version = $this->versionParser->normalize($prettyVersion); return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * @author Jordi Boggiano */ class RootAliasPackage extends AliasPackage implements RootPackageInterface { public function __construct(RootPackageInterface $aliasOf, $version, $prettyVersion) { parent::__construct($aliasOf, $version, $prettyVersion); } /** * {@inheritDoc} */ public function getAliases() { return $this->aliasOf->getAliases(); } /** * {@inheritDoc} */ public function getMinimumStability() { return $this->aliasOf->getMinimumStability(); } /** * {@inheritDoc} */ public function getStabilityFlags() { return $this->aliasOf->getStabilityFlags(); } /** * {@inheritDoc} */ public function getReferences() { return $this->aliasOf->getReferences(); } /** * {@inheritDoc} */ public function getPreferStable() { return $this->aliasOf->getPreferStable(); } /** * {@inheritDoc} */ public function getConfig() { return $this->aliasOf->getConfig(); } /** * {@inheritDoc} */ public function setRequires(array $require) { $this->requires = $this->replaceSelfVersionDependencies($require, 'requires'); $this->aliasOf->setRequires($require); } /** * {@inheritDoc} */ public function setDevRequires(array $devRequire) { $this->devRequires = $this->replaceSelfVersionDependencies($devRequire, 'devRequires'); $this->aliasOf->setDevRequires($devRequire); } /** * {@inheritDoc} */ public function setConflicts(array $conflicts) { $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, 'conflicts'); $this->aliasOf->setConflicts($conflicts); } /** * {@inheritDoc} */ public function setProvides(array $provides) { $this->provides = $this->replaceSelfVersionDependencies($provides, 'provides'); $this->aliasOf->setProvides($provides); } /** * {@inheritDoc} */ public function setReplaces(array $replaces) { $this->replaces = $this->replaceSelfVersionDependencies($replaces, 'replaces'); $this->aliasOf->setReplaces($replaces); } /** * {@inheritDoc} */ public function setRepositories($repositories) { $this->aliasOf->setRepositories($repositories); } /** * {@inheritDoc} */ public function setAutoload(array $autoload) { $this->aliasOf->setAutoload($autoload); } /** * {@inheritDoc} */ public function setDevAutoload(array $devAutoload) { $this->aliasOf->setDevAutoload($devAutoload); } /** * {@inheritDoc} */ public function setStabilityFlags(array $stabilityFlags) { $this->aliasOf->setStabilityFlags($stabilityFlags); } /** * {@inheritDoc} */ public function setSuggests(array $suggests) { $this->aliasOf->setSuggests($suggests); } /** * {@inheritDoc} */ public function setExtra(array $extra) { $this->aliasOf->setExtra($extra); } public function __clone() { parent::__clone(); $this->aliasOf = clone $this->aliasOf; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * Defines package metadata that is not necessarily needed for solving and installing packages * * @author Nils Adermann */ interface CompletePackageInterface extends PackageInterface { /** * Returns the scripts of this package * * @return array array('script name' => array('listeners')) */ public function getScripts(); /** * Returns an array of repositories * * {"": {}} * * @return array Repositories */ public function getRepositories(); /** * Returns the package license, e.g. MIT, BSD, GPL * * @return array The package licenses */ public function getLicense(); /** * Returns an array of keywords relating to the package * * @return array */ public function getKeywords(); /** * Returns the package description * * @return string */ public function getDescription(); /** * Returns the package homepage * * @return string */ public function getHomepage(); /** * Returns an array of authors of the package * * Each item can contain name/homepage/email keys * * @return array */ public function getAuthors(); /** * Returns the support information * * @return array */ public function getSupport(); /** * Returns if the package is abandoned or not * * @return bool */ public function isAbandoned(); /** * If the package is abandoned and has a suggested replacement, this method returns it * * @return string */ public function getReplacementPackage(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\LinkConstraint; use Composer\Semver\Constraint\EmptyConstraint as SemverEmptyConstraint; trigger_error('The ' . __NAMESPACE__ . '\EmptyConstraint class is deprecated, use Composer\Semver\Constraint\EmptyConstraint instead.', E_USER_DEPRECATED); /** * @deprecated use Composer\Semver\Constraint\EmptyConstraint instead */ class EmptyConstraint extends SemverEmptyConstraint implements LinkConstraintInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\LinkConstraint; use Composer\Semver\Constraint\MultiConstraint as SemverMultiConstraint; trigger_error('The ' . __NAMESPACE__ . '\MultiConstraint class is deprecated, use Composer\Semver\Constraint\MultiConstraint instead.', E_USER_DEPRECATED); /** * @deprecated use Composer\Semver\Constraint\MultiConstraint instead */ class MultiConstraint extends SemverMultiConstraint implements LinkConstraintInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\LinkConstraint; use Composer\Semver\Constraint\AbstractConstraint; trigger_error('The ' . __NAMESPACE__ . '\SpecificConstraint abstract class is deprecated, there is no replacement for it.', E_USER_DEPRECATED); /** * @deprecated use Composer\Semver\Constraint\AbstractConstraint instead */ abstract class SpecificConstraint extends AbstractConstraint implements LinkConstraintInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\LinkConstraint; use Composer\Semver\Constraint\ConstraintInterface; trigger_error('The ' . __NAMESPACE__ . '\LinkConstraintInterface interface is deprecated, use Composer\Semver\Constraint\ConstraintInterface instead.', E_USER_DEPRECATED); /** * @deprecated use Composer\Semver\Constraint\ConstraintInterface instead */ interface LinkConstraintInterface extends ConstraintInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\LinkConstraint; use Composer\Semver\Constraint\Constraint; trigger_error('The ' . __NAMESPACE__ . '\VersionConstraint class is deprecated, use Composer\Semver\Constraint\Constraint instead.', E_USER_DEPRECATED); /** * @deprecated use Composer\Semver\Constraint\Constraint instead */ class VersionConstraint extends Constraint implements LinkConstraintInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\IO\IOInterface; use Composer\Util\Filesystem; use Composer\Util\Silencer; use Symfony\Component\Finder\Finder; /** * Reads/writes to a filesystem cache * * @author Jordi Boggiano */ class Cache { private static $cacheCollected = false; private $io; private $root; private $enabled = true; private $whitelist; private $filesystem; /** * @param IOInterface $io * @param string $cacheDir location of the cache * @param string $whitelist List of characters that are allowed in path names (used in a regex character class) * @param Filesystem $filesystem optional filesystem instance */ public function __construct(IOInterface $io, $cacheDir, $whitelist = 'a-z0-9.', Filesystem $filesystem = null) { $this->io = $io; $this->root = rtrim($cacheDir, '/\\') . '/'; $this->whitelist = $whitelist; $this->filesystem = $filesystem ?: new Filesystem(); if (preg_match('{(^|[\\\\/])(\$null|NUL|/dev/null)([\\\\/]|$)}', $cacheDir)) { $this->enabled = false; return; } if ( (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true)) || !is_writable($this->root) ) { $this->io->writeError('Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache'); $this->enabled = false; } } public function isEnabled() { return $this->enabled; } public function getRoot() { return $this->root; } public function read($file) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled && file_exists($this->root . $file)) { $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return file_get_contents($this->root . $file); } return false; } public function write($file, $contents) { if ($this->enabled) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); try { return file_put_contents($this->root . $file, $contents); } catch (\ErrorException $e) { $this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG); if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { // Remove partial file. unlink($this->root . $file); $message = sprintf( 'Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$u bytes of free space available', $this->root . $file, $m[1], $m[2], @disk_free_space($this->root . dirname($file)) ); $this->io->writeError($message); return false; } throw $e; } } return false; } /** * Copy a file into the cache */ public function copyFrom($file, $source) { if ($this->enabled) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); $this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); if (!file_exists($source)) { $this->io->writeError(''.$source.' does not exist, can not write into cache'); } elseif ($this->io->isDebug()) { $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source); } return copy($source, $this->root . $file); } return false; } /** * Copy a file out of the cache */ public function copyTo($file, $target) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled && file_exists($this->root . $file)) { try { touch($this->root . $file, filemtime($this->root . $file), time()); } catch (\ErrorException $e) { // fallback in case the above failed due to incorrect ownership // see https://github.com/composer/composer/issues/4070 Silencer::call('touch', $this->root . $file); } $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return copy($this->root . $file, $target); } return false; } public function gcIsNecessary() { return (!self::$cacheCollected && !mt_rand(0, 50)); } public function remove($file) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled && file_exists($this->root . $file)) { return $this->filesystem->unlink($this->root . $file); } return false; } public function clear() { if ($this->enabled) { return $this->filesystem->removeDirectory($this->root); } return false; } public function gc($ttl, $maxSize) { if ($this->enabled) { $expire = new \DateTime(); $expire->modify('-'.$ttl.' seconds'); $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s')); foreach ($finder as $file) { $this->filesystem->unlink($file->getPathname()); } $totalSize = $this->filesystem->size($this->root); if ($totalSize > $maxSize) { $iterator = $this->getFinder()->sortByAccessedTime()->getIterator(); while ($totalSize > $maxSize && $iterator->valid()) { $filepath = $iterator->current()->getPathname(); $totalSize -= $this->filesystem->size($filepath); $this->filesystem->unlink($filepath); $iterator->next(); } } self::$cacheCollected = true; return true; } return false; } public function sha1($file) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled && file_exists($this->root . $file)) { return sha1_file($this->root . $file); } return false; } public function sha256($file) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled && file_exists($this->root . $file)) { return hash_file('sha256', $this->root . $file); } return false; } protected function getFinder() { return Finder::create()->in($this->root)->files(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Util\IniHelper; use Symfony\Component\Console\Output\OutputInterface; /** * @author John Stevenson */ class XdebugHandler { const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG'; const ENV_VERSION = 'COMPOSER_XDEBUG_VERSION'; const RESTART_ID = 'internal'; private $output; private $loaded; private $envScanDir; private $version; private $tmpIni; /** * Constructor */ public function __construct(OutputInterface $output) { $this->output = $output; $this->loaded = extension_loaded('xdebug'); $this->envScanDir = getenv('PHP_INI_SCAN_DIR'); if ($this->loaded) { $ext = new \ReflectionExtension('xdebug'); $this->version = strval($ext->getVersion()); } } /** * Checks if xdebug is loaded and composer needs to be restarted * * If so, then a tmp ini is created with the xdebug ini entry commented out. * If additional inis have been loaded, these are combined into the tmp ini * and PHP_INI_SCAN_DIR is set to an empty value. Current ini locations are * are stored in COMPOSER_ORIGINAL_INIS, for use in the restarted process. * * This behaviour can be disabled by setting the COMPOSER_ALLOW_XDEBUG * environment variable to 1. This variable is used internally so that the * restarted process is created only once and PHP_INI_SCAN_DIR can be * restored to its original value. */ public function check() { $args = explode('|', strval(getenv(self::ENV_ALLOW)), 2); if ($this->needsRestart($args[0])) { if ($this->prepareRestart()) { $command = $this->getCommand(); $this->restart($command); } return; } // Restore environment variables if we are restarting if (self::RESTART_ID === $args[0]) { putenv(self::ENV_ALLOW); if (false !== $this->envScanDir) { // $args[1] contains the original value if (isset($args[1])) { putenv('PHP_INI_SCAN_DIR='.$args[1]); } else { putenv('PHP_INI_SCAN_DIR'); } } // Clear version if the restart failed to disable xdebug if ($this->loaded) { putenv(self::ENV_VERSION); } } } /** * Executes the restarted command then deletes the tmp ini * * @param string $command */ protected function restart($command) { passthru($command, $exitCode); if (!empty($this->tmpIni)) { @unlink($this->tmpIni); } exit($exitCode); } /** * Returns true if a restart is needed * * @param string $allow Environment value * * @return bool */ private function needsRestart($allow) { if (PHP_SAPI !== 'cli' || !defined('PHP_BINARY')) { return false; } return empty($allow) && $this->loaded; } /** * Returns true if everything was written for the restart * * If any of the following fails (however unlikely) we must return false to * stop potential recursion: * - tmp ini file creation * - environment variable creation * * @return bool */ private function prepareRestart() { $this->tmpIni = ''; $iniPaths = IniHelper::getAll(); $additional = count($iniPaths) > 1; if (empty($iniPaths[0])) { // There is no loaded ini array_shift($iniPaths); } if ($this->writeTmpIni($iniPaths)) { return $this->setEnvironment($additional, $iniPaths); } return false; } /** * Returns true if the tmp ini file was written * * The filename is passed as the -c option when the process restarts. * * @param array $iniFiles The php.ini locations * * @return bool */ private function writeTmpIni(array $iniFiles) { if (!$this->tmpIni = tempnam(sys_get_temp_dir(), '')) { return false; } $content = ''; $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; foreach ($iniFiles as $file) { $data = preg_replace($regex, ';$1', file_get_contents($file)); $content .= $data.PHP_EOL; } $content .= 'allow_url_fopen='.ini_get('allow_url_fopen').PHP_EOL; $content .= 'disable_functions="'.ini_get('disable_functions').'"'.PHP_EOL; $content .= 'memory_limit='.ini_get('memory_limit').PHP_EOL; if (defined('PHP_WINDOWS_VERSION_BUILD')) { // Work-around for PHP windows bug, see issue #6052 $content .= 'opcache.enable_cli=0'.PHP_EOL; } return @file_put_contents($this->tmpIni, $content); } /** * Returns the restart command line * * @return string */ private function getCommand() { $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni); $params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv'])); return implode(' ', array_map(array($this, 'escape'), $params)); } /** * Returns true if the restart environment variables were set * * @param bool $additional Whether there were additional inis * @param array $iniPaths Locations used by the current prcoess * * @return bool */ private function setEnvironment($additional, array $iniPaths) { // Set scan dir to an empty value if additional ini files were used if ($additional && !putenv('PHP_INI_SCAN_DIR=')) { return false; } // Make original inis available to restarted process if (!putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $iniPaths))) { return false; } // Make xdebug version available to restarted process if (!putenv(self::ENV_VERSION.'='.$this->version)) { return false; } // Flag restarted process and save env scan dir state $args = array(self::RESTART_ID); if (false !== $this->envScanDir) { // Save current PHP_INI_SCAN_DIR $args[] = $this->envScanDir; } return putenv(self::ENV_ALLOW.'='.implode('|', $args)); } /** * Returns the restart script arguments, adding --ansi if required * * If we are a terminal with color support we must ensure that the --ansi * option is set, because the restarted output is piped. * * @param array $args The argv array * * @return array */ private function getScriptArgs(array $args) { if (in_array('--no-ansi', $args) || in_array('--ansi', $args)) { return $args; } if ($this->output->isDecorated()) { $offset = count($args) > 1 ? 2 : 1; array_splice($args, $offset, 0, '--ansi'); } return $args; } /** * Escapes a string to be used as a shell argument. * * From https://github.com/johnstevenson/winbox-args * MIT Licensed (c) John Stevenson * * @param string $arg The argument to be escaped * @param bool $meta Additionally escape cmd.exe meta characters * * @return string The escaped argument */ private function escape($arg, $meta = true) { if (!defined('PHP_WINDOWS_VERSION_BUILD')) { return escapeshellarg($arg); } $quote = strpbrk($arg, " \t") !== false || $arg === ''; $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); if ($meta) { $meta = $dquotes || preg_match('/%[^%]+%/', $arg); if (!$meta && !$quote) { $quote = strpbrk($arg, '^&|<>()') !== false; } } if ($quote) { $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg); $arg = '"'.$arg.'"'; } if ($meta) { $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); } return $arg; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Script; /** * The Command Event. * * @deprecated use Composer\Script\Event instead */ class CommandEvent extends Event { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Script; use Composer\Composer; use Composer\IO\IOInterface; use Composer\EventDispatcher\Event as BaseEvent; /** * The script event class * * @author François Pluchino * @author Nils Adermann */ class Event extends BaseEvent { /** * @var Composer The composer instance */ private $composer; /** * @var IOInterface The IO instance */ private $io; /** * @var bool Dev mode flag */ private $devMode; /** * Constructor. * * @param string $name The event name * @param Composer $composer The composer object * @param IOInterface $io The IOInterface object * @param bool $devMode Whether or not we are in dev mode * @param array $args Arguments passed by the user * @param array $flags Optional flags to pass data not as argument */ public function __construct($name, Composer $composer, IOInterface $io, $devMode = false, array $args = array(), array $flags = array()) { parent::__construct($name, $args, $flags); $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; } /** * Returns the composer instance. * * @return Composer */ public function getComposer() { return $this->composer; } /** * Returns the IO instance. * * @return IOInterface */ public function getIO() { return $this->io; } /** * Return the dev mode flag * * @return bool */ public function isDevMode() { return $this->devMode; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Script; use Composer\Installer\PackageEvent as BasePackageEvent; /** * The Package Event. * * @deprecated Use Composer\Installer\PackageEvent instead */ class PackageEvent extends BasePackageEvent { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Script; /** * The Script Events. * * @author François Pluchino * @author Jordi Boggiano */ class ScriptEvents { /** * The PRE_INSTALL_CMD event occurs before the install command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const PRE_INSTALL_CMD = 'pre-install-cmd'; /** * The POST_INSTALL_CMD event occurs after the install command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const POST_INSTALL_CMD = 'post-install-cmd'; /** * The PRE_UPDATE_CMD event occurs before the update command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const PRE_UPDATE_CMD = 'pre-update-cmd'; /** * The POST_UPDATE_CMD event occurs after the update command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const POST_UPDATE_CMD = 'post-update-cmd'; /** * The PRE_STATUS_CMD event occurs before the status command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const PRE_STATUS_CMD = 'pre-status-cmd'; /** * The POST_STATUS_CMD event occurs after the status command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const POST_STATUS_CMD = 'post-status-cmd'; /** * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const PRE_AUTOLOAD_DUMP = 'pre-autoload-dump'; /** * The POST_AUTOLOAD_DUMP event occurs after the autoload file has been generated. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const POST_AUTOLOAD_DUMP = 'post-autoload-dump'; /** * The POST_ROOT_PACKAGE_INSTALL event occurs after the root package has been installed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const POST_ROOT_PACKAGE_INSTALL = 'post-root-package-install'; /** * The POST_CREATE_PROJECT event occurs after the create-project command has been executed. * Note: Event occurs after POST_INSTALL_CMD * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const POST_CREATE_PROJECT_CMD = 'post-create-project-cmd'; /** * The PRE_ARCHIVE_CMD event occurs before the update command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const PRE_ARCHIVE_CMD = 'pre-archive-cmd'; /** * The POST_ARCHIVE_CMD event occurs after the status command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ const POST_ARCHIVE_CMD = 'post-archive-cmd'; /** Deprecated constants below */ /** * The PRE_PACKAGE_INSTALL event occurs before a package is installed. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_INSTALL instead. * @var string */ const PRE_PACKAGE_INSTALL = 'pre-package-install'; /** * The POST_PACKAGE_INSTALL event occurs after a package is installed. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_INSTALL instead. * @var string */ const POST_PACKAGE_INSTALL = 'post-package-install'; /** * The PRE_PACKAGE_UPDATE event occurs before a package is updated. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UPDATE instead. * @var string */ const PRE_PACKAGE_UPDATE = 'pre-package-update'; /** * The POST_PACKAGE_UPDATE event occurs after a package is updated. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UPDATE instead. * @var string */ const POST_PACKAGE_UPDATE = 'post-package-update'; /** * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UNINSTALL instead. * @var string */ const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; /** * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UNINSTALL instead. * @var string */ const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * Tests URLs against no_proxy patterns. */ class NoProxyPattern { /** * @var string[] */ protected $rules = array(); /** * @param string $pattern no_proxy pattern */ public function __construct($pattern) { $this->rules = preg_split("/[\s,]+/", $pattern); } /** * Test a URL against the stored pattern. * * @param string $url * * @return true if the URL matches one of the rules. */ public function test($url) { $host = parse_url($url, PHP_URL_HOST); $port = parse_url($url, PHP_URL_PORT); if (empty($port)) { switch (parse_url($url, PHP_URL_SCHEME)) { case 'http': $port = 80; break; case 'https': $port = 443; break; } } foreach ($this->rules as $rule) { if ($rule == '*') { return true; } $match = false; list($ruleHost) = explode(':', $rule); list($base) = explode('/', $ruleHost); if (filter_var($base, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // ip or cidr match if (!isset($ip)) { $ip = gethostbyname($host); } if (strpos($ruleHost, '/') === false) { $match = $ip === $ruleHost; } else { // gethostbyname() failed to resolve $host to an ip, so we assume // it must be proxied to let the proxy's DNS resolve it if ($ip === $host) { $match = false; } else { // match resolved IP against the rule $match = self::inCIDRBlock($ruleHost, $ip); } } } else { // match end of domain $haystack = '.' . trim($host, '.') . '.'; $needle = '.'. trim($ruleHost, '.') .'.'; $match = stripos(strrev($haystack), strrev($needle)) === 0; } // final port check if ($match && strpos($rule, ':') !== false) { list(, $rulePort) = explode(':', $rule); if (!empty($rulePort) && $port != $rulePort) { $match = false; } } if ($match) { return true; } } return false; } /** * Check an IP address against a CIDR * * http://framework.zend.com/svn/framework/extras/incubator/library/ZendX/Whois/Adapter/Cidr.php * * @param string $cidr IPv4 block in CIDR notation * @param string $ip IPv4 address * * @return bool */ private static function inCIDRBlock($cidr, $ip) { // Get the base and the bits from the CIDR list($base, $bits) = explode('/', $cidr); // Now split it up into it's classes list($a, $b, $c, $d) = explode('.', $base); // Now do some bit shifting/switching to convert to ints $i = ($a << 24) + ($b << 16) + ($c << 8) + $d; $mask = $bits == 0 ? 0 : (~0 << (32 - $bits)); // Here's our lowest int $low = $i & $mask; // Here's our highest int $high = $i | (~$mask & 0xFFFFFFFF); // Now split the ip we're checking against up into classes list($a, $b, $c, $d) = explode('.', $ip); // Now convert the ip we're checking against to an int $check = ($a << 24) + ($b << 16) + ($c << 8) + $d; // If the ip is within the range, including highest/lowest values, // then it's within the CIDR range return $check >= $low && $check <= $high; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * Temporarily suppress PHP error reporting, usually warnings and below. * * @author Niels Keurentjes */ class Silencer { /** * @var int[] Unpop stack */ private static $stack = array(); /** * Suppresses given mask or errors. * * @param int|null $mask Error levels to suppress, default value NULL indicates all warnings and below. * @return int The old error reporting level. */ public static function suppress($mask = null) { if (!isset($mask)) { $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT; } $old = error_reporting(); array_push(self::$stack, $old); error_reporting($old & ~$mask); return $old; } /** * Restores a single state. */ public static function restore() { if (!empty(self::$stack)) { error_reporting(array_pop(self::$stack)); } } /** * Calls a specified function while silencing warnings and below. * * Future improvement: when PHP requirements are raised add Callable type hint (5.4) and variadic parameters (5.6) * * @param callable $callable Function to execute. * @throws \Exception Any exceptions from the callback are rethrown. * @return mixed Return value of the callback. */ public static function call($callable /*, ...$parameters */) { try { self::suppress(); $result = call_user_func_array($callable, array_slice(func_get_args(), 1)); self::restore(); return $result; } catch (\Exception $e) { // Use a finally block for this when requirements are raised to PHP 5.5 self::restore(); throw $e; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\CaBundle\CaBundle; /** * @author Chris Smith */ final class TlsHelper { private static $useOpensslParse; /** * Match hostname against a certificate. * * @param mixed $certificate X.509 certificate * @param string $hostname Hostname in the URL * @param string $cn Set to the common name of the certificate iff match found * * @return bool */ public static function checkCertificateHost($certificate, $hostname, &$cn = null) { $names = self::getCertificateNames($certificate); if (empty($names)) { return false; } $combinedNames = array_merge($names['san'], array($names['cn'])); $hostname = strtolower($hostname); foreach ($combinedNames as $certName) { $matcher = self::certNameMatcher($certName); if ($matcher && $matcher($hostname)) { $cn = $names['cn']; return true; } } return false; } /** * Extract DNS names out of an X.509 certificate. * * @param mixed $certificate X.509 certificate * * @return array|null */ public static function getCertificateNames($certificate) { if (is_array($certificate)) { $info = $certificate; } elseif (CaBundle::isOpensslParseSafe()) { $info = openssl_x509_parse($certificate, false); } if (!isset($info['subject']['commonName'])) { return null; } $commonName = strtolower($info['subject']['commonName']); $subjectAltNames = array(); if (isset($info['extensions']['subjectAltName'])) { $subjectAltNames = preg_split('{\s*,\s*}', $info['extensions']['subjectAltName']); $subjectAltNames = array_filter(array_map(function ($name) { if (0 === strpos($name, 'DNS:')) { return strtolower(ltrim(substr($name, 4))); } return null; }, $subjectAltNames)); $subjectAltNames = array_values($subjectAltNames); } return array( 'cn' => $commonName, 'san' => $subjectAltNames, ); } /** * Get the certificate pin. * * By Kevin McArthur of StormTide Digital Studios Inc. * @KevinSMcArthur / https://github.com/StormTide * * See http://tools.ietf.org/html/draft-ietf-websec-key-pinning-02 * * This method was adapted from Sslurp. * https://github.com/EvanDotPro/Sslurp * * (c) Evan Coury * * For the full copyright and license information, please see below: * * Copyright (c) 2013, Evan Coury * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ public static function getCertificateFingerprint($certificate) { $pubkeydetails = openssl_pkey_get_details(openssl_get_publickey($certificate)); $pubkeypem = $pubkeydetails['key']; //Convert PEM to DER before SHA1'ing $start = '-----BEGIN PUBLIC KEY-----'; $end = '-----END PUBLIC KEY-----'; $pemtrim = substr($pubkeypem, (strpos($pubkeypem, $start) + strlen($start)), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1)); $der = base64_decode($pemtrim); return sha1($der); } /** * Test if it is safe to use the PHP function openssl_x509_parse(). * * This checks if OpenSSL extensions is vulnerable to remote code execution * via the exploit documented as CVE-2013-6420. * * @return bool */ public static function isOpensslParseSafe() { return CaBundle::isOpensslParseSafe(); } /** * Convert certificate name into matching function. * * @param string $certName CN/SAN * * @return callable|null */ private static function certNameMatcher($certName) { $wildcards = substr_count($certName, '*'); if (0 === $wildcards) { // Literal match. return function ($hostname) use ($certName) { return $hostname === $certName; }; } if (1 === $wildcards) { $components = explode('.', $certName); if (3 > count($components)) { // Must have 3+ components return; } $firstComponent = $components[0]; // Wildcard must be the last character. if ('*' !== $firstComponent[strlen($firstComponent) - 1]) { return; } $wildcardRegex = preg_quote($certName); $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex); $wildcardRegex = "{^{$wildcardRegex}$}"; return function ($hostname) use ($wildcardRegex) { return 1 === preg_match($wildcardRegex, $hostname); }; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * Provides ini file location functions that work with and without a restart. * When the process has restarted it uses a tmp ini and stores the original * ini locations in an environment variable. * * @author John Stevenson */ class IniHelper { const ENV_ORIGINAL = 'COMPOSER_ORIGINAL_INIS'; /** * Returns an array of php.ini locations with at least one entry * * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. * The loaded ini location is the first entry and may be empty. * * @return array */ public static function getAll() { if ($env = strval(getenv(self::ENV_ORIGINAL))) { return explode(PATH_SEPARATOR, $env); } $paths = array(strval(php_ini_loaded_file())); if ($scanned = php_ini_scanned_files()) { $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); } return $paths; } /** * Describes the location of the loaded php.ini file * * @return string */ public static function getMessage() { $paths = self::getAll(); if (empty($paths[0])) { return 'A php.ini file does not exist. You will have to create one.'; } return 'The php.ini used by your command-line PHP is: '.$paths[0]; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\Downloader\TransportException; /** * @author Jordi Boggiano */ class GitHub { protected $io; protected $config; protected $process; protected $remoteFilesystem; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking */ public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor; $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); } /** * Attempts to authorize a GitHub domain via OAuth * * @param string $originUrl The host this GitHub instance is located at * @return bool true on success */ public function authorizeOAuth($originUrl) { if (!in_array($originUrl, $this->config->get('github-domains'))) { return false; } // if available use token from git config if (0 === $this->process->execute('git config github.accesstoken', $output)) { $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic'); return true; } return false; } /** * Authorizes a GitHub domain interactively via OAuth * * @param string $originUrl The host this GitHub instance is located at * @param string $message The reason this authorization is required * @throws \RuntimeException * @throws TransportException|\Exception * @return bool true on success */ public function authorizeOAuthInteractively($originUrl, $message = null) { if ($message) { $this->io->writeError($message); } $note = 'Composer'; if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute('hostname', $output)) { $note .= ' on ' . trim($output); } $note .= ' ' . date('Y-m-d Hi'); $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note)); $this->io->writeError(sprintf('Head to %s', $url)); $this->io->writeError(sprintf('to retrieve a token. It will be stored in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName())); $token = trim($this->io->askAndHideAnswer('Token (hidden): ')); if (!$token) { $this->io->writeError('No token given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); return false; } $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic'); try { $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/'; $this->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl, false, array( 'retry-auth-failure' => false, )); } catch (TransportException $e) { if (in_array($e->getCode(), array(403, 401))) { $this->io->writeError('Invalid token provided.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); return false; } throw $e; } // store value in user config $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl); $this->config->getAuthConfigSource()->addConfigSetting('github-oauth.'.$originUrl, $token); $this->io->writeError('Token stored successfully.'); return true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\Downloader\TransportException; /** * @author Paul Wenke */ class Bitbucket { private $io; private $config; private $process; private $remoteFilesystem; private $token = array(); private $time; const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token'; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking * @param int $time Timestamp, injectable for mocking */ public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null, $time = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor; $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); $this->time = $time; } /** * @return string */ public function getToken() { if (!isset($this->token['access_token'])) { return ''; } return $this->token['access_token']; } /** * Attempts to authorize a Bitbucket domain via OAuth * * @param string $originUrl The host this Bitbucket instance is located at * @return bool true on success */ public function authorizeOAuth($originUrl) { if ($originUrl !== 'bitbucket.org') { return false; } // if available use token from git config if (0 === $this->process->execute('git config bitbucket.accesstoken', $output)) { $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output)); return true; } return false; } /** * @param string $originUrl * @return bool */ private function requestAccessToken($originUrl) { try { $json = $this->remoteFilesystem->getContents($originUrl, self::OAUTH2_ACCESS_TOKEN_URL, false, array( 'retry-auth-failure' => false, 'http' => array( 'method' => 'POST', 'content' => 'grant_type=client_credentials', ), )); $this->token = json_decode($json, true); } catch (TransportException $e) { if ($e->getCode() === 400) { $this->io->writeError('Invalid OAuth consumer provided.'); $this->io->writeError('This can have two reasons:'); $this->io->writeError('1. You are authenticating with a bitbucket username/password combination'); $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url'); return false; } elseif (in_array($e->getCode(), array(403, 401))) { $this->io->writeError('Invalid OAuth consumer provided.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return false; } throw $e; } return true; } /** * Authorizes a Bitbucket domain interactively via OAuth * * @param string $originUrl The host this Bitbucket instance is located at * @param string $message The reason this authorization is required * @throws \RuntimeException * @throws TransportException|\Exception * @return bool true on success */ public function authorizeOAuthInteractively($originUrl, $message = null) { if ($message) { $this->io->writeError($message); } $url = 'https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html'; $this->io->writeError(sprintf('Follow the instructions on %s', $url)); $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName())); $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)'); $consumerKey = trim($this->io->askAndHideAnswer('Consumer Key (hidden): ')); if (!$consumerKey) { $this->io->writeError('No consumer key given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return false; } $consumerSecret = trim($this->io->askAndHideAnswer('Consumer Secret (hidden): ')); if (!$consumerSecret) { $this->io->writeError('No consumer secret given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return false; } $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); if (!$this->requestAccessToken($originUrl)) { return false; } // store value in user config $this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret); // Remove conflicting basic auth credentials (if available) $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl); $this->io->writeError('Consumer stored successfully.'); return true; } /** * Retrieves an access token from Bitbucket. * * @param string $originUrl * @param string $consumerKey * @param string $consumerSecret * @return string */ public function requestToken($originUrl, $consumerKey, $consumerSecret) { if (!empty($this->token) || $this->getTokenFromConfig($originUrl)) { return $this->token['access_token']; } $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); if (!$this->requestAccessToken($originUrl)) { return ''; } $this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret); return $this->token['access_token']; } /** * Store the new/updated credentials to the configuration * @param string $originUrl * @param string $consumerKey * @param string $consumerSecret */ private function storeInAuthConfig($originUrl, $consumerKey, $consumerSecret) { $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl); $time = null === $this->time ? time() : $this->time; $consumer = array( "consumer-key" => $consumerKey, "consumer-secret" => $consumerSecret, "access-token" => $this->token['access_token'], "access-token-expiration" => $time + $this->token['expires_in'], ); $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer); } /** * @param string $originUrl * @return bool */ private function getTokenFromConfig($originUrl) { $authConfig = $this->config->get('bitbucket-oauth'); if ( !isset($authConfig[$originUrl]['access-token']) || !isset($authConfig[$originUrl]['access-token-expiration']) || time() > $authConfig[$originUrl]['access-token-expiration'] ) { return false; } $this->token = array( 'access_token' => $authConfig[$originUrl]['access-token'], ); return true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\IO\IOInterface; use Composer\Config; use Composer\Factory; use Composer\Downloader\TransportException; use Composer\Json\JsonFile; /** * @author Roshan Gautam */ class GitLab { protected $io; protected $config; protected $process; protected $remoteFilesystem; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking */ public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor(); $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); } /** * Attempts to authorize a GitLab domain via OAuth. * * @param string $originUrl The host this GitLab instance is located at * * @return bool true on success */ public function authorizeOAuth($originUrl) { if (!in_array($originUrl, $this->config->get('gitlab-domains'), true)) { return false; } // if available use token from git config if (0 === $this->process->execute('git config gitlab.accesstoken', $output)) { $this->io->setAuthentication($originUrl, trim($output), 'oauth2'); return true; } // if available use token from composer config $authTokens = $this->config->get('gitlab-token'); if (isset($authTokens[$originUrl])) { $this->io->setAuthentication($originUrl, $authTokens[$originUrl], 'private-token'); return true; } return false; } /** * Authorizes a GitLab domain interactively via OAuth. * * @param string $scheme Scheme used in the origin URL * @param string $originUrl The host this GitLab instance is located at * @param string $message The reason this authorization is required * * @throws \RuntimeException * @throws TransportException|\Exception * * @return bool true on success */ public function authorizeOAuthInteractively($scheme, $originUrl, $message = null) { if ($message) { $this->io->writeError($message); } $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName())); $this->io->writeError('To revoke access to this token you can visit '.$originUrl.'/profile/applications'); $attemptCounter = 0; while ($attemptCounter++ < 5) { try { $response = $this->createToken($scheme, $originUrl); } catch (TransportException $e) { // 401 is bad credentials, // 403 is max login attempts exceeded if (in_array($e->getCode(), array(403, 401))) { if (401 === $e->getCode()) { $this->io->writeError('Bad credentials.'); } else { $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); } $this->io->writeError('You can also manually create a personal token at '.$scheme.'://'.$originUrl.'/profile/personal_access_tokens'); $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' "'); continue; } throw $e; } $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); // store value in user config in auth file $this->config->getAuthConfigSource()->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']); return true; } throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.'); } private function createToken($scheme, $originUrl) { $username = $this->io->ask('Username: '); $password = $this->io->askAndHideAnswer('Password: '); $headers = array('Content-Type: application/x-www-form-urlencoded'); $apiUrl = $originUrl; $data = http_build_query(array( 'username' => $username, 'password' => $password, 'grant_type' => 'password', ), null, '&'); $options = array( 'retry-auth-failure' => false, 'http' => array( 'method' => 'POST', 'header' => $headers, 'content' => $data, ), ); $json = $this->remoteFilesystem->getContents($originUrl, $scheme.'://'.$apiUrl.'/oauth/token', false, $options); $this->io->writeError('Token successfully created'); return JsonFile::parseJson($json); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Composer; /** * Allows the creation of a basic context supporting http proxy * * @author Jordan Alliot * @author Markus Tacker */ final class StreamContextFactory { /** * Creates a context supporting HTTP proxies * * @param string $url URL the context is to be used for * @param array $defaultOptions Options to merge with the default * @param array $defaultParams Parameters to specify on the context * @throws \RuntimeException if https proxy required and OpenSSL uninstalled * @return resource Default context */ public static function getContext($url, array $defaultOptions = array(), array $defaultParams = array()) { $options = array('http' => array( // specify defaults again to try and work better with curlwrappers enabled 'follow_location' => 1, 'max_redirects' => 20, )); // Handle HTTP_PROXY/http_proxy on CLI only for security reasons if (PHP_SAPI === 'cli' && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']); } // Prefer CGI_HTTP_PROXY if available if (!empty($_SERVER['CGI_HTTP_PROXY'])) { $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']); } // Override with HTTPS proxy if present and URL is https if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) { $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']); } // Remove proxy if URL matches no_proxy directive if (!empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) { $pattern = new NoProxyPattern($_SERVER['no_proxy']); if ($pattern->test($url)) { unset($proxy); } } if (!empty($proxy)) { $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : ''; $proxyURL .= isset($proxy['host']) ? $proxy['host'] : ''; if (isset($proxy['port'])) { $proxyURL .= ":" . $proxy['port']; } elseif ('http://' == substr($proxyURL, 0, 7)) { $proxyURL .= ":80"; } elseif ('https://' == substr($proxyURL, 0, 8)) { $proxyURL .= ":443"; } // http(s):// is not supported in proxy $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL); if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) { throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); } $options['http']['proxy'] = $proxyURL; // enabled request_fulluri unless it is explicitly disabled switch (parse_url($url, PHP_URL_SCHEME)) { case 'http': // default request_fulluri to true $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI'); if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) { $options['http']['request_fulluri'] = true; } break; case 'https': // default request_fulluri to true $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI'); if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) { $options['http']['request_fulluri'] = true; } break; } // add SNI opts for https URLs if ('https' === parse_url($url, PHP_URL_SCHEME)) { $options['ssl']['SNI_enabled'] = true; if (PHP_VERSION_ID < 50600) { $options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST); } } // handle proxy auth if present if (isset($proxy['user'])) { $auth = urldecode($proxy['user']); if (isset($proxy['pass'])) { $auth .= ':' . urldecode($proxy['pass']); } $auth = base64_encode($auth); // Preserve headers if already set in default options if (isset($defaultOptions['http']['header'])) { if (is_string($defaultOptions['http']['header'])) { $defaultOptions['http']['header'] = array($defaultOptions['http']['header']); } $defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}"; } else { $options['http']['header'] = array("Proxy-Authorization: Basic {$auth}"); } } } $options = array_replace_recursive($options, $defaultOptions); if (isset($options['http']['header'])) { $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); } if (defined('HHVM_VERSION')) { $phpVersion = 'HHVM ' . HHVM_VERSION; } else { $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; } if (!isset($options['http']['header']) || false === strpos(strtolower(implode('', $options['http']['header'])), 'user-agent')) { $options['http']['header'][] = sprintf( 'User-Agent: Composer/%s (%s; %s; %s%s)', Composer::VERSION === '@package_version@' ? 'source' : Composer::VERSION, function_exists('php_uname') ? php_uname('s') : 'Unknown', function_exists('php_uname') ? php_uname('r') : 'Unknown', $phpVersion, getenv('CI') ? '; CI' : '' ); } return stream_context_create($options, $defaultParams); } /** * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and * NOT at the end of the array * * This method fixes the array by moving the content-type header to the end * * @link https://bugs.php.net/bug.php?id=61548 * @param $header * @return array */ private static function fixHttpHeaderField($header) { if (!is_array($header)) { $header = explode("\r\n", $header); } uasort($header, function ($el) { return preg_match('{^content-type}i', $el) ? 1 : -1; }); return $header; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Finder\Finder; /** * @author Jordi Boggiano * @author Johannes M. Schmitt */ class Filesystem { private $processExecutor; public function __construct(ProcessExecutor $executor = null) { $this->processExecutor = $executor ?: new ProcessExecutor(); } public function remove($file) { if (is_dir($file)) { return $this->removeDirectory($file); } if (file_exists($file)) { return $this->unlink($file); } return false; } /** * Checks if a directory is empty * * @param string $dir * @return bool */ public function isDirEmpty($dir) { $finder = Finder::create() ->ignoreVCS(false) ->ignoreDotFiles(false) ->depth(0) ->in($dir); return count($finder) === 0; } public function emptyDirectory($dir, $ensureDirectoryExists = true) { if (file_exists($dir) && is_link($dir)) { $this->unlink($dir); } if ($ensureDirectoryExists) { $this->ensureDirectoryExists($dir); } if (is_dir($dir)) { $finder = Finder::create() ->ignoreVCS(false) ->ignoreDotFiles(false) ->depth(0) ->in($dir); foreach ($finder as $path) { $this->remove((string) $path); } } } /** * Recursively remove a directory * * Uses the process component if proc_open is enabled on the PHP * installation. * * @param string $directory * @throws \RuntimeException * @return bool */ public function removeDirectory($directory) { if ($this->isSymlinkedDirectory($directory)) { return $this->unlinkSymlinkedDirectory($directory); } if ($this->isJunction($directory)) { return $this->removeJunction($directory); } if (!file_exists($directory) || !is_dir($directory)) { return true; } if (preg_match('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) { throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.'); } if (!function_exists('proc_open')) { return $this->removeDirectoryPhp($directory); } if (Platform::isWindows()) { $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory))); } else { $cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory)); } $result = $this->getProcess()->execute($cmd, $output) === 0; // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache(); if ($result && !file_exists($directory)) { return true; } return $this->removeDirectoryPhp($directory); } /** * Recursively delete directory using PHP iterators. * * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files * before directories, creating a single non-recursive loop * to delete files/directories in the correct order. * * @param string $directory * @return bool */ public function removeDirectoryPhp($directory) { try { $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); } catch (\UnexpectedValueException $e) { // re-try once after clearing the stat cache if it failed as it // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009 clearstatcache(); usleep(100000); if (!is_dir($directory)) { return true; } $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); } $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach ($ri as $file) { if ($file->isDir()) { $this->rmdir($file->getPathname()); } else { $this->unlink($file->getPathname()); } } return $this->rmdir($directory); } public function ensureDirectoryExists($directory) { if (!is_dir($directory)) { if (file_exists($directory)) { throw new \RuntimeException( $directory.' exists and is not a directory.' ); } if (!@mkdir($directory, 0777, true)) { throw new \RuntimeException( $directory.' does not exist and could not be created.' ); } } } /** * Attempts to unlink a file and in case of failure retries after 350ms on windows * * @param string $path * @throws \RuntimeException * @return bool */ public function unlink($path) { if (!@$this->unlinkImplementation($path)) { // retry after a bit on windows since it tends to be touchy with mass removals if (!Platform::isWindows() || (usleep(350000) && !@$this->unlinkImplementation($path))) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . @$error['message']; if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } throw new \RuntimeException($message); } } return true; } /** * Attempts to rmdir a file and in case of failure retries after 350ms on windows * * @param string $path * @throws \RuntimeException * @return bool */ public function rmdir($path) { if (!@rmdir($path)) { // retry after a bit on windows since it tends to be touchy with mass removals if (!Platform::isWindows() || (usleep(350000) && !@rmdir($path))) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . @$error['message']; if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } throw new \RuntimeException($message); } } return true; } /** * Copy then delete is a non-atomic version of {@link rename}. * * Some systems can't rename and also don't have proc_open, * which requires this solution. * * @param string $source * @param string $target */ public function copyThenRemove($source, $target) { if (!is_dir($source)) { copy($source, $target); $this->unlink($source); return; } $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST); $this->ensureDirectoryExists($target); foreach ($ri as $file) { $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName(); if ($file->isDir()) { $this->ensureDirectoryExists($targetPath); } else { copy($file->getPathname(), $targetPath); } } $this->removeDirectoryPhp($source); } public function rename($source, $target) { if (true === @rename($source, $target)) { return; } if (!function_exists('proc_open')) { return $this->copyThenRemove($source, $target); } if (Platform::isWindows()) { // Try to copy & delete - this is a workaround for random "Access denied" errors. $command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target)); $result = $this->processExecutor->execute($command, $output); // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache(); if (0 === $result) { $this->remove($source); return; } } else { // We do not use PHP's "rename" function here since it does not support // the case where $source, and $target are located on different partitions. $command = sprintf('mv %s %s', ProcessExecutor::escape($source), ProcessExecutor::escape($target)); $result = $this->processExecutor->execute($command, $output); // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache(); if (0 === $result) { return; } } return $this->copyThenRemove($source, $target); } /** * Returns the shortest path from $from to $to * * @param string $from * @param string $to * @param bool $directories if true, the source/target are considered to be directories * @throws \InvalidArgumentException * @return string */ public function findShortestPath($from, $to, $directories = false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } $from = lcfirst($this->normalizePath($from)); $to = lcfirst($this->normalizePath($to)); if ($directories) { $from = rtrim($from, '/') . '/dummy_file'; } if (dirname($from) === dirname($to)) { return './'.basename($to); } $commonPath = $to; while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath)) { $commonPath = strtr(dirname($commonPath), '\\', '/'); } if (0 !== strpos($from, $commonPath) || '/' === $commonPath) { return $to; } $commonPath = rtrim($commonPath, '/') . '/'; $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/'); $commonPathCode = str_repeat('../', $sourcePathDepth); return ($commonPathCode . substr($to, strlen($commonPath))) ?: './'; } /** * Returns PHP code that, when executed in $from, will return the path to $to * * @param string $from * @param string $to * @param bool $directories if true, the source/target are considered to be directories * @param bool $staticCode * @throws \InvalidArgumentException * @return string */ public function findShortestPathCode($from, $to, $directories = false, $staticCode = false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } $from = lcfirst($this->normalizePath($from)); $to = lcfirst($this->normalizePath($to)); if ($from === $to) { return $directories ? '__DIR__' : '__FILE__'; } $commonPath = $to; while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) { $commonPath = strtr(dirname($commonPath), '\\', '/'); } if (0 !== strpos($from, $commonPath) || '/' === $commonPath || '.' === $commonPath) { return var_export($to, true); } $commonPath = rtrim($commonPath, '/') . '/'; if (strpos($to, $from.'/') === 0) { return '__DIR__ . '.var_export(substr($to, strlen($from)), true); } $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/') + $directories; if ($staticCode) { $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'"; } else { $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth); } $relTarget = substr($to, strlen($commonPath)); return $commonPathCode . (strlen($relTarget) ? '.' . var_export('/' . $relTarget, true) : ''); } /** * Checks if the given path is absolute * * @param string $path * @return bool */ public function isAbsolutePath($path) { return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':'; } /** * Returns size of a file or directory specified by path. If a directory is * given, it's size will be computed recursively. * * @param string $path Path to the file or directory * @throws \RuntimeException * @return int */ public function size($path) { if (!file_exists($path)) { throw new \RuntimeException("$path does not exist."); } if (is_dir($path)) { return $this->directorySize($path); } return filesize($path); } /** * Normalize a path. This replaces backslashes with slashes, removes ending * slash and collapses redundant separators and up-level references. * * @param string $path Path to the file or directory * @return string */ public function normalizePath($path) { $parts = array(); $path = strtr($path, '\\', '/'); $prefix = ''; $absolute = false; // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: if (preg_match('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { $prefix = $match[1]; $path = substr($path, strlen($prefix)); } if (substr($path, 0, 1) === '/') { $absolute = true; $path = substr($path, 1); } $up = false; foreach (explode('/', $path) as $chunk) { if ('..' === $chunk && ($absolute || $up)) { array_pop($parts); $up = !(empty($parts) || '..' === end($parts)); } elseif ('.' !== $chunk && '' !== $chunk) { $parts[] = $chunk; $up = '..' !== $chunk; } } return $prefix.($absolute ? '/' : '').implode('/', $parts); } /** * Return if the given path is local * * @param string $path * @return bool */ public static function isLocalPath($path) { return (bool) preg_match('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); } public static function getPlatformPath($path) { if (Platform::isWindows()) { $path = preg_replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path); } return preg_replace('{^file://}i', '', $path); } protected function directorySize($directory) { $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); $size = 0; foreach ($ri as $file) { if ($file->isFile()) { $size += $file->getSize(); } } return $size; } protected function getProcess() { return new ProcessExecutor; } /** * delete symbolic link implementation (commonly known as "unlink()") * * symbolic links on windows which link to directories need rmdir instead of unlink * * @param string $path * * @return bool */ private function unlinkImplementation($path) { if (Platform::isWindows() && is_dir($path) && is_link($path)) { return rmdir($path); } return unlink($path); } /** * Creates a relative symlink from $link to $target * * @param string $target The path of the binary file to be symlinked * @param string $link The path where the symlink should be created * @return bool */ public function relativeSymlink($target, $link) { $cwd = getcwd(); $relativePath = $this->findShortestPath($link, $target); chdir(dirname($link)); $result = @symlink($relativePath, $link); chdir($cwd); return (bool) $result; } /** * return true if that directory is a symlink. * * @param string $directory * * @return bool */ public function isSymlinkedDirectory($directory) { if (!is_dir($directory)) { return false; } $resolved = $this->resolveSymlinkedDirectorySymlink($directory); return is_link($resolved); } /** * @param string $directory * * @return bool */ private function unlinkSymlinkedDirectory($directory) { $resolved = $this->resolveSymlinkedDirectorySymlink($directory); return $this->unlink($resolved); } /** * resolve pathname to symbolic link of a directory * * @param string $pathname directory path to resolve * * @return string resolved path to symbolic link or original pathname (unresolved) */ private function resolveSymlinkedDirectorySymlink($pathname) { if (!is_dir($pathname)) { return $pathname; } $resolved = rtrim($pathname, '/'); if (!strlen($resolved)) { return $pathname; } return $resolved; } /** * Creates an NTFS junction. * * @param string $target * @param string $junction */ public function junction($target, $junction) { if (!Platform::isWindows()) { throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__)); } if (!is_dir($target)) { throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target); } $cmd = sprintf('mklink /J %s %s', ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)), ProcessExecutor::escape(realpath($target))); if ($this->getProcess()->execute($cmd, $output) !== 0) { throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target); } clearstatcache(true, $junction); } /** * Returns whether the target directory is a Windows NTFS Junction. * * @param string $junction Path to check. * @return bool */ public function isJunction($junction) { if (!Platform::isWindows()) { return false; } if (!is_dir($junction) || is_link($junction)) { return false; } /** * According to MSDN at https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx we can detect a junction now * using the 'mode' value from stat: "The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit * is set if path specifies an ordinary file or a device." We have just tested for a directory above, so if * we have a directory that isn't one according to lstat(...) we must have a junction. * * #define _S_IFDIR 0x4000 * #define _S_IFREG 0x8000 * * Stat cache should be cleared before to avoid accidentally reading wrong information from previous installs. */ clearstatcache(true, $junction); $stat = lstat($junction); return !($stat['mode'] & 0xC000); } /** * Removes a Windows NTFS junction. * * @param string $junction * @return bool */ public function removeJunction($junction) { if (!Platform::isWindows()) { return false; } $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR); if (!$this->isJunction($junction)) { throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction)); } $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape($junction)); clearstatcache(true, $junction); return ($this->getProcess()->execute($cmd, $output) === 0); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * Composer mirror utilities * * @author Jordi Boggiano */ class ComposerMirror { public static function processUrl($mirrorUrl, $packageName, $version, $reference, $type) { if ($reference) { $reference = preg_match('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : md5($reference); } $version = strpos($version, '/') === false ? $version : md5($version); return str_replace( array('%package%', '%version%', '%reference%', '%type%'), array($packageName, $version, $reference, $type), $mirrorUrl ); } public static function processGitUrl($mirrorUrl, $packageName, $url, $type) { if (preg_match('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url, $match)) { $url = 'gh-'.$match[1].'/'.$match[2]; } elseif (preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$#', $url, $match)) { $url = 'bb-'.$match[1].'/'.$match[2]; } else { $url = preg_replace('{[^a-z0-9_.-]}i', '-', trim($url, '/')); } return str_replace( array('%package%', '%normalizedUrl%', '%type%'), array($packageName, $url, $type), $mirrorUrl ); } public static function processHgUrl($mirrorUrl, $packageName, $url, $type) { return self::processGitUrl($mirrorUrl, $packageName, $url, $type); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Symfony\Component\Process\Process; use Symfony\Component\Process\ProcessUtils; use Composer\IO\IOInterface; /** * @author Robert Schönthal */ class ProcessExecutor { protected static $timeout = 300; protected $captureOutput; protected $errorOutput; protected $io; public function __construct(IOInterface $io = null) { $this->io = $io; } /** * runs a process on the commandline * * @param string $command the command to execute * @param mixed $output the output will be written into this var if passed by ref * if a callable is passed it will be used as output handler * @param string $cwd the working directory * @return int statuscode */ public function execute($command, &$output = null, $cwd = null) { if ($this->io && $this->io->isDebug()) { $safeCommand = preg_replace_callback('{://(?P[^:/\s]+):(?P[^@\s/]+)@}i', function ($m) { if (preg_match('{^[a-f0-9]{12,}$}', $m['user'])) { return '://***:***@'; } return '://'.$m['user'].':***@'; }, $command); $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand); } // make sure that null translate to the proper directory in case the dir is a symlink // and we call a git command, because msysgit does not handle symlinks properly if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) { $cwd = realpath(getcwd()); } $this->captureOutput = count(func_get_args()) > 1; $this->errorOutput = null; $process = new Process($command, $cwd, null, null, static::getTimeout()); $callback = is_callable($output) ? $output : array($this, 'outputHandler'); $process->run($callback); if ($this->captureOutput && !is_callable($output)) { $output = $process->getOutput(); } $this->errorOutput = $process->getErrorOutput(); return $process->getExitCode(); } public function splitLines($output) { $output = trim($output); return ((string) $output === '') ? array() : preg_split('{\r?\n}', $output); } /** * Get any error output from the last command * * @return string */ public function getErrorOutput() { return $this->errorOutput; } public function outputHandler($type, $buffer) { if ($this->captureOutput) { return; } if (null === $this->io) { echo $buffer; return; } if (Process::ERR === $type) { $this->io->writeError($buffer, false); } else { $this->io->write($buffer, false); } } public static function getTimeout() { return static::$timeout; } public static function setTimeout($timeout) { static::$timeout = $timeout; } /** * Escapes a string to be used as a shell argument. * * @param string $argument The argument that will be escaped * * @return string The escaped argument */ public static function escape($argument) { return ProcessUtils::escapeArgument($argument); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * Platform helper for uniform platform-specific tests. * * @author Niels Keurentjes */ class Platform { /** * Parses tildes and environment variables in paths. * * @param string $path * @return string */ public static function expandPath($path) { if (preg_match('#^~[\\/]#', $path)) { return self::getUserDirectory() . substr($path, 1); } return preg_replace_callback('#^(\$|(?P%))(?P\w++)(?(percent)%)(?P.*)#', function ($matches) { // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons if (Platform::isWindows() && $matches['var'] == 'HOME') { return (getenv('HOME') ?: getenv('USERPROFILE')) . $matches['path']; } return getenv($matches['var']) . $matches['path']; }, $path); } /** * @throws \RuntimeException If the user home could not reliably be determined * @return string The formal user home as detected from environment parameters */ public static function getUserDirectory() { if (false !== ($home = getenv('HOME'))) { return $home; } if (self::isWindows() && false !== ($home = getenv('USERPROFILE'))) { return $home; } if (function_exists('posix_getuid') && function_exists('posix_getpwuid')) { $info = posix_getpwuid(posix_getuid()); return $info['dir']; } throw new \RuntimeException('Could not determine user directory'); } /** * @return bool Whether the host machine is running a Windows OS */ public static function isWindows() { return defined('PHP_WINDOWS_VERSION_BUILD'); } /** * @param string $str * @return int return a guaranteed binary length of the string, regardless of silly mbstring configs */ public static function strlen($str) { static $useMbString = null; if (null === $useMbString) { $useMbString = function_exists('mb_strlen') && ini_get('mbstring.func_overload'); } if ($useMbString) { return mb_strlen($str, '8bit'); } return strlen($str); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Package\Loader\InvalidPackageException; use Composer\Json\JsonValidationException; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Spdx\SpdxLicenses; /** * Validates a composer configuration. * * @author Robert Schönthal * @author Jordi Boggiano */ class ConfigValidator { private $io; public function __construct(IOInterface $io) { $this->io = $io; } /** * Validates the config, and returns the result. * * @param string $file The path to the file * @param int $arrayLoaderValidationFlags Flags for ArrayLoader validation * * @return array a triple containing the errors, publishable errors, and warnings */ public function validate($file, $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL) { $errors = array(); $publishErrors = array(); $warnings = array(); // validate json schema $laxValid = false; try { $json = new JsonFile($file, null, $this->io); $manifest = $json->read(); $json->validateSchema(JsonFile::LAX_SCHEMA); $laxValid = true; $json->validateSchema(); } catch (JsonValidationException $e) { foreach ($e->getErrors() as $message) { if ($laxValid) { $publishErrors[] = $message; } else { $errors[] = $message; } } } catch (\Exception $e) { $errors[] = $e->getMessage(); return array($errors, $publishErrors, $warnings); } // validate actual data if (!empty($manifest['license'])) { // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer if (is_array($manifest['license'])) { foreach ($manifest['license'] as $key => $license) { if ('proprietary' === $license) { unset($manifest['license'][$key]); } } } $licenseValidator = new SpdxLicenses(); if ('proprietary' !== $manifest['license'] && array() !== $manifest['license'] && !$licenseValidator->validate($manifest['license']) && $licenseValidator->validate(trim($manifest['license']))) { $warnings[] = sprintf( 'License %s must not contain extra spaces, make sure to trim it.', json_encode($manifest['license']) ); } elseif ('proprietary' !== $manifest['license'] && array() !== $manifest['license'] && !$licenseValidator->validate($manifest['license'])) { $warnings[] = sprintf( 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . 'If the software is closed-source, you may use "proprietary" as license.', json_encode($manifest['license']) ); } } else { $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; } if (isset($manifest['version'])) { $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.'; } if (!empty($manifest['name']) && preg_match('{[A-Z]}', $manifest['name'])) { $suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']); $suggestName = strtolower($suggestName); $publishErrors[] = sprintf( 'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.', $manifest['name'], $suggestName ); } if (!empty($manifest['type']) && $manifest['type'] == 'composer-installer') { $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation."; } // check for require-dev overrides if (isset($manifest['require']) && isset($manifest['require-dev'])) { $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']); if (!empty($requireOverrides)) { $plural = (count($requireOverrides) > 1) ? 'are' : 'is'; $warnings[] = implode(', ', array_keys($requireOverrides)). " {$plural} required both in require and require-dev, this can lead to unexpected behavior"; } } // check for commit references $require = isset($manifest['require']) ? $manifest['require'] : array(); $requireDev = isset($manifest['require-dev']) ? $manifest['require-dev'] : array(); $packages = array_merge($require, $requireDev); foreach ($packages as $package => $version) { if (preg_match('/#/', $version) === 1) { $warnings[] = sprintf( 'The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.', $package ); } } // check for empty psr-0/psr-4 namespace prefixes if (isset($manifest['autoload']['psr-0'][''])) { $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; } if (isset($manifest['autoload']['psr-4'][''])) { $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance"; } try { $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags); if (!isset($manifest['version'])) { $manifest['version'] = '1.0.0'; } if (!isset($manifest['name'])) { $manifest['name'] = 'dummy/dummy'; } $loader->load($manifest); } catch (InvalidPackageException $e) { $errors = array_merge($errors, $e->getErrors()); } $warnings = array_merge($warnings, $loader->getWarnings()); return array($errors, $publishErrors, $warnings); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; /** * @author Till Klampaeckel * @author Jordi Boggiano */ class Svn { const MAX_QTY_AUTH_TRIES = 5; /** * @var array */ protected $credentials; /** * @var bool */ protected $hasAuth; /** * @var \Composer\IO\IOInterface */ protected $io; /** * @var string */ protected $url; /** * @var bool */ protected $cacheCredentials = true; /** * @var ProcessExecutor */ protected $process; /** * @var int */ protected $qtyAuthTries = 0; /** * @var \Composer\Config */ protected $config; /** * @param string $url * @param \Composer\IO\IOInterface $io * @param Config $config * @param ProcessExecutor $process */ public function __construct($url, IOInterface $io, Config $config, ProcessExecutor $process = null) { $this->url = $url; $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor; } public static function cleanEnv() { // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 putenv("DYLD_LIBRARY_PATH"); unset($_SERVER['DYLD_LIBRARY_PATH']); } /** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @param bool $verbose Output all output to the user * * @throws \RuntimeException * @return string */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $svnCommand = $this->getCommand($command, $url, $path); $output = null; $io = $this->io; $handler = function ($type, $buffer) use (&$output, $io, $verbose) { if ($type !== 'out') { return; } if ('Redirecting to URL ' === substr($buffer, 0, 19)) { return; } $output .= $buffer; if ($verbose) { $io->writeError($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); if (0 === $status) { return $output; } $errorOutput = $this->process->getErrorOutput(); $fullOutput = implode("\n", array($output, $errorOutput)); // the error is not auth-related if (false === stripos($fullOutput, 'Could not authenticate to server:') && false === stripos($fullOutput, 'authorization failed') && false === stripos($fullOutput, 'svn: E170001:') && false === stripos($fullOutput, 'svn: E215004:')) { throw new \RuntimeException($fullOutput); } if (!$this->hasAuth()) { $this->doAuthDance(); } // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { // restart the process return $this->execute($command, $url, $cwd, $path, $verbose); } throw new \RuntimeException( 'wrong credentials provided ('.$fullOutput.')' ); } /** * @param bool $cacheCredentials */ public function setCacheCredentials($cacheCredentials) { $this->cacheCredentials = $cacheCredentials; } /** * Repositories requests credentials, let's put them in. * * @throws \RuntimeException * @return \Composer\Util\Svn */ protected function doAuthDance() { // cannot ask for credentials in non interactive mode if (!$this->io->isInteractive()) { throw new \RuntimeException( 'can not ask for authentication in non interactive mode' ); } $this->io->writeError("The Subversion server ({$this->url}) requested credentials:"); $this->hasAuth = true; $this->credentials['username'] = $this->io->ask("Username: "); $this->credentials['password'] = $this->io->askAndHideAnswer("Password: "); $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ", true); return $this; } /** * A method to create the svn commands run. * * @param string $cmd Usually 'svn ls' or something like that. * @param string $url Repo URL. * @param string $path Target for a checkout * * @return string */ protected function getCommand($cmd, $url, $path = null) { $cmd = sprintf('%s %s%s %s', $cmd, '--non-interactive ', $this->getCredentialString(), ProcessExecutor::escape($url) ); if ($path) { $cmd .= ' ' . ProcessExecutor::escape($path); } return $cmd; } /** * Return the credential string for the svn command. * * Adds --no-auth-cache when credentials are present. * * @return string */ protected function getCredentialString() { if (!$this->hasAuth()) { return ''; } return sprintf( ' %s--username %s --password %s ', $this->getAuthCache(), ProcessExecutor::escape($this->getUsername()), ProcessExecutor::escape($this->getPassword()) ); } /** * Get the password for the svn command. Can be empty. * * @throws \LogicException * @return string */ protected function getPassword() { if ($this->credentials === null) { throw new \LogicException("No svn auth detected."); } return isset($this->credentials['password']) ? $this->credentials['password'] : ''; } /** * Get the username for the svn command. * * @throws \LogicException * @return string */ protected function getUsername() { if ($this->credentials === null) { throw new \LogicException("No svn auth detected."); } return $this->credentials['username']; } /** * Detect Svn Auth. * * @return bool */ protected function hasAuth() { if (null !== $this->hasAuth) { return $this->hasAuth; } if (false === $this->createAuthFromConfig()) { $this->createAuthFromUrl(); } return $this->hasAuth; } /** * Return the no-auth-cache switch. * * @return string */ protected function getAuthCache() { return $this->cacheCredentials ? '' : '--no-auth-cache '; } /** * Create the auth params from the configuration file. * * @return bool */ private function createAuthFromConfig() { if (!$this->config->has('http-basic')) { return $this->hasAuth = false; } $authConfig = $this->config->get('http-basic'); $host = parse_url($this->url, PHP_URL_HOST); if (isset($authConfig[$host])) { $this->credentials['username'] = $authConfig[$host]['username']; $this->credentials['password'] = $authConfig[$host]['password']; return $this->hasAuth = true; } return $this->hasAuth = false; } /** * Create the auth params from the url * * @return bool */ private function createAuthFromUrl() { $uri = parse_url($this->url); if (empty($uri['user'])) { return $this->hasAuth = false; } $this->credentials['username'] = $uri['user']; if (!empty($uri['pass'])) { $this->credentials['password'] = $uri['pass']; } return $this->hasAuth = true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Spdx\SpdxLicenses; trigger_error('The ' . __NAMESPACE__ . '\SpdxLicense class is deprecated, use Composer\Spdx\SpdxLicenses instead.', E_USER_DEPRECATED); /** * @deprecated use Composer\Spdx\SpdxLicenses instead */ class SpdxLicense extends SpdxLicenses { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; /** * @author Jordi Boggiano */ class Git { private static $version; /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var Filesystem */ protected $filesystem; public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs) { $this->io = $io; $this->config = $config; $this->process = $process; $this->filesystem = $fs; } public function runCommand($commandCallable, $url, $cwd, $initialClone = false) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); if ($initialClone) { $origCwd = $cwd; $cwd = null; } if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) { throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } if (!$initialClone) { // capture username/password from URL if there is one $this->process->execute('git remote -v', $output, $cwd); if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) { $this->io->setAuthentication($match[3], urldecode($match[1]), urldecode($match[2])); } } $protocols = $this->config->get('github-protocols'); if (!is_array($protocols)) { throw new \RuntimeException('Config value "github-protocols" must be an array, got ' . gettype($protocols)); } // public github, autoswitch protocols if (preg_match('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { $messages = array(); foreach ($protocols as $protocol) { if ('ssh' === $protocol) { $protoUrl = "git@" . $match[1] . ":" . $match[2]; } else { $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; } if (0 === $this->process->execute(call_user_func($commandCallable, $protoUrl), $ignoredOutput, $cwd)) { return; } $messages[] = '- ' . $protoUrl . "\n" . preg_replace('#^#m', ' ', $this->process->getErrorOutput()); if ($initialClone) { $this->filesystem->removeDirectory($origCwd); } } // failed to checkout, first check git accessibility $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); } // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https $bypassSshForGitHub = preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); $command = call_user_func($commandCallable, $url); $auth = null; if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { // private github repository without git access, try https with auth if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)) { if (!$this->io->hasAuthentication($match[1])) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process); $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $gitHubUtil->authorizeOAuthInteractively($match[1], $message); } } if ($this->io->hasAuthentication($match[1])) { $auth = $this->io->getAuthentication($match[1]); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; $command = call_user_func($commandCallable, $authUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } } } elseif (preg_match('{^https://(bitbucket\.org)/(.*)(\.git)?$}U', $url, $match)) { //bitbucket oauth $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process); if (!$this->io->hasAuthentication($match[1])) { $message = 'Enter your Bitbucket credentials to access private repos'; if (!$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $bitbucketUtil->authorizeOAuthInteractively($match[1], $message); $accessToken = $bitbucketUtil->getToken(); $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); } } else { //We're authenticating with a locally stored consumer. $auth = $this->io->getAuthentication($match[1]); //We already have an access_token from a previous request. if ($auth['username'] !== 'x-token-auth') { $accessToken = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']); if (! empty($accessToken)) { $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); } } } if ($this->io->hasAuthentication($match[1])) { $auth = $this->io->getAuthentication($match[1]); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; $command = call_user_func($commandCallable, $authUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } } else { // Falling back to ssh $sshUrl = 'git@bitbucket.org:' . $match[2] . '.git'; $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); $command = call_user_func($commandCallable, $sshUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } } } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate if (strpos($match[2], '@')) { list($authParts, $match[2]) = explode('@', $match[2], 2); } $storeAuth = false; if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); } elseif ($this->io->isInteractive()) { $defaultUsername = null; if (isset($authParts) && $authParts) { if (false !== strpos($authParts, ':')) { list($defaultUsername, ) = explode(':', $authParts, 2); } else { $defaultUsername = $authParts; } } $this->io->writeError(' Authentication required (' . parse_url($url, PHP_URL_HOST) . '):'); $auth = array( 'username' => $this->io->ask(' Username: ', $defaultUsername), 'password' => $this->io->askAndHideAnswer(' Password: '), ); $storeAuth = $this->config->get('store-auths'); } if ($auth) { $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3]; $command = call_user_func($commandCallable, $authUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { $this->io->setAuthentication($match[2], $auth['username'], $auth['password']); $authHelper = new AuthHelper($this->io, $this->config); $authHelper->storeAuth($match[2], $storeAuth); return; } } } if ($initialClone) { $this->filesystem->removeDirectory($origCwd); } $this->throwException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(), $url); } } public function syncMirror($url, $dir) { // update the repo if it is a valid git repository if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') { try { $commandCallable = function ($url) { return sprintf('git remote set-url origin %s && git remote update --prune origin', ProcessExecutor::escape($url)); }; $this->runCommand($commandCallable, $url, $dir); } catch (\Exception $e) { return false; } return true; } // clean up directory and do a fresh clone into it $this->filesystem->removeDirectory($dir); $commandCallable = function ($url) use ($dir) { return sprintf('git clone --mirror %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($dir)); }; $this->runCommand($commandCallable, $url, $dir, true); return true; } private function isAuthenticationFailure($url, &$match) { if (!preg_match('{(https?://)([^/]+)(.*)$}i', $url, $match)) { return false; } $authFailures = array( 'fatal: Authentication failed', 'remote error: Invalid username or password.', 'error: 401 Unauthorized', 'fatal: unable to access', ); foreach ($authFailures as $authFailure) { if (strpos($this->process->getErrorOutput(), $authFailure) !== false) { return true; } } return false; } public static function cleanEnv() { if (PHP_VERSION_ID < 50400 && ini_get('safe_mode') && false === strpos(ini_get('safe_mode_allowed_env_vars'), 'GIT_ASKPASS')) { throw new \RuntimeException('safe_mode is enabled and safe_mode_allowed_env_vars does not contain GIT_ASKPASS, can not set env var. You can disable safe_mode with "-dsafe_mode=0" when running composer'); } // added in git 1.7.1, prevents prompting the user for username/password if (getenv('GIT_ASKPASS') !== 'echo') { putenv('GIT_ASKPASS=echo'); unset($_SERVER['GIT_ASKPASS']); } // clean up rogue git env vars in case this is running in a git hook if (getenv('GIT_DIR')) { putenv('GIT_DIR'); unset($_SERVER['GIT_DIR']); } if (getenv('GIT_WORK_TREE')) { putenv('GIT_WORK_TREE'); unset($_SERVER['GIT_WORK_TREE']); } // Run processes with predictable LANGUAGE if (getenv('LANGUAGE') !== 'C') { putenv('LANGUAGE=C'); } // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 putenv("DYLD_LIBRARY_PATH"); unset($_SERVER['DYLD_LIBRARY_PATH']); } public static function getGitHubDomainsRegex(Config $config) { return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')'; } public static function sanitizeUrl($message) { return preg_replace_callback('{://(?P[^@]+?):(?P.+?)@}', function ($m) { if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) { return '://***:***@'; } return '://' . $m[1] . ':***@'; }, $message); } private function throwException($message, $url) { // git might delete a directory when it fails and php will not know clearstatcache(); if (0 !== $this->process->execute('git --version', $ignoredOutput)) { throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); } throw new \RuntimeException(self::sanitizeUrl($message)); } /** * Retrieves the current git version. * * @return string|null The git version number. */ public function getVersion() { if (isset(self::$version)) { return self::$version; } if (0 !== $this->process->execute('git --version', $output)) { return; } if (preg_match('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) { return self::$version = $matches[1]; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; /** * @author Jordi Boggiano */ class AuthHelper { protected $io; protected $config; public function __construct(IOInterface $io, Config $config) { $this->io = $io; $this->config = $config; } public function storeAuth($originUrl, $storeAuth) { $store = false; $configSource = $this->config->getAuthConfigSource(); if ($storeAuth === true) { $store = $configSource; } elseif ($storeAuth === 'prompt') { $answer = $this->io->askAndValidate( 'Do you want to store credentials for '.$originUrl.' in '.$configSource->getName().' ? [Yn] ', function ($value) { $input = strtolower(substr(trim($value), 0, 1)); if (in_array($input, array('y','n'))) { return $input; } throw new \RuntimeException('Please answer (y)es or (n)o'); }, null, 'y' ); if ($answer === 'y') { $store = $configSource; } } if ($store) { $store->addConfigSetting( 'http-basic.'.$originUrl, $this->io->getAuthentication($originUrl) ); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\IO\IOInterface; use Symfony\Component\Process\Process; /** * @author Matt Whittom */ class Perforce { protected $path; protected $p4Depot; protected $p4Client; protected $p4User; protected $p4Password; protected $p4Port; protected $p4Stream; protected $p4ClientSpec; protected $p4DepotType; protected $p4Branch; protected $process; protected $uniquePerforceClientName; protected $windowsFlag; protected $commandResult; protected $io; protected $filesystem; public function __construct($repoConfig, $port, $path, ProcessExecutor $process, $isWindows, IOInterface $io) { $this->windowsFlag = $isWindows; $this->p4Port = $port; $this->initializePath($path); $this->process = $process; $this->initialize($repoConfig); $this->io = $io; } public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io) { return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io); } public static function checkServerExists($url, ProcessExecutor $processExecutor) { $output = null; return 0 === $processExecutor->execute('p4 -p ' . $url . ' info -s', $output); } public function initialize($repoConfig) { $this->uniquePerforceClientName = $this->generateUniquePerforceClientName(); if (!$repoConfig) { return; } if (isset($repoConfig['unique_perforce_client_name'])) { $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name']; } if (isset($repoConfig['depot'])) { $this->p4Depot = $repoConfig['depot']; } if (isset($repoConfig['branch'])) { $this->p4Branch = $repoConfig['branch']; } if (isset($repoConfig['p4user'])) { $this->p4User = $repoConfig['p4user']; } else { $this->p4User = $this->getP4variable('P4USER'); } if (isset($repoConfig['p4password'])) { $this->p4Password = $repoConfig['p4password']; } } public function initializeDepotAndBranch($depot, $branch) { if (isset($depot)) { $this->p4Depot = $depot; } if (isset($branch)) { $this->p4Branch = $branch; } } public function generateUniquePerforceClientName() { return gethostname() . "_" . time(); } public function cleanupClientSpec() { $client = $this->getClient(); $task = 'client -d ' . $client; $useP4Client = false; $command = $this->generateP4Command($task, $useP4Client); $this->executeCommand($command); $clientSpec = $this->getP4ClientSpec(); $fileSystem = $this->getFilesystem(); $fileSystem->remove($clientSpec); } protected function executeCommand($command) { $this->commandResult = ""; $exit_code = $this->process->execute($command, $this->commandResult); return $exit_code; } public function getClient() { if (!isset($this->p4Client)) { $cleanStreamName = str_replace('@', '', str_replace('/', '_', str_replace('//', '', $this->getStream()))); $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName; } return $this->p4Client; } protected function getPath() { return $this->path; } public function initializePath($path) { $this->path = $path; $fs = $this->getFilesystem(); $fs->ensureDirectoryExists($path); } protected function getPort() { return $this->p4Port; } public function setStream($stream) { $this->p4Stream = $stream; $index = strrpos($stream, '/'); //Stream format is //depot/stream, while non-streaming depot is //depot if ($index > 2) { $this->p4DepotType = 'stream'; } } public function isStream() { return (strcmp($this->p4DepotType, 'stream') === 0); } public function getStream() { if (!isset($this->p4Stream)) { if ($this->isStream()) { $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch; } else { $this->p4Stream = '//' . $this->p4Depot; } } return $this->p4Stream; } public function getStreamWithoutLabel($stream) { $index = strpos($stream, '@'); if ($index === false) { return $stream; } return substr($stream, 0, $index); } public function getP4ClientSpec() { $p4clientSpec = $this->path . '/' . $this->getClient() . '.p4.spec'; return $p4clientSpec; } public function getUser() { return $this->p4User; } public function setUser($user) { $this->p4User = $user; } public function queryP4User() { $this->getUser(); if (strlen($this->p4User) > 0) { return; } $this->p4User = $this->getP4variable('P4USER'); if (strlen($this->p4User) > 0) { return; } $this->p4User = $this->io->ask('Enter P4 User:'); if ($this->windowsFlag) { $command = 'p4 set P4USER=' . $this->p4User; } else { $command = 'export P4USER=' . $this->p4User; } $this->executeCommand($command); } protected function getP4variable($name) { if ($this->windowsFlag) { $command = 'p4 set'; $this->executeCommand($command); $result = trim($this->commandResult); $resArray = explode(PHP_EOL, $result); foreach ($resArray as $line) { $fields = explode('=', $line); if (strcmp($name, $fields[0]) == 0) { $index = strpos($fields[1], ' '); if ($index === false) { $value = $fields[1]; } else { $value = substr($fields[1], 0, $index); } $value = trim($value); return $value; } } return null; } else { $command = 'echo $' . $name; $this->executeCommand($command); $result = trim($this->commandResult); return $result; } } public function queryP4Password() { if (isset($this->p4Password)) { return $this->p4Password; } $password = $this->getP4variable('P4PASSWD'); if (strlen($password) <= 0) { $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': '); } $this->p4Password = $password; return $password; } public function generateP4Command($command, $useClient = true) { $p4Command = 'p4 '; $p4Command = $p4Command . '-u ' . $this->getUser() . ' '; if ($useClient) { $p4Command = $p4Command . '-c ' . $this->getClient() . ' '; } $p4Command = $p4Command . '-p ' . $this->getPort() . ' '; $p4Command = $p4Command . $command; return $p4Command; } public function isLoggedIn() { $command = $this->generateP4Command('login -s', false); $exitCode = $this->executeCommand($command); if ($exitCode) { $errorOutput = $this->process->getErrorOutput(); $index = strpos($errorOutput, $this->getUser()); if ($index === false) { $index = strpos($errorOutput, 'p4'); if ($index === false) { return false; } throw new \Exception('p4 command not found in path: ' . $errorOutput); } throw new \Exception('Invalid user name: ' . $this->getUser()); } return true; } public function connectClient() { $p4CreateClientCommand = $this->generateP4Command( 'client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec()) ); $this->executeCommand($p4CreateClientCommand); } public function syncCodeBase($sourceReference) { $prevDir = getcwd(); chdir($this->path); $p4SyncCommand = $this->generateP4Command('sync -f '); if (null !== $sourceReference) { $p4SyncCommand = $p4SyncCommand . '@' . $sourceReference; } $this->executeCommand($p4SyncCommand); chdir($prevDir); } public function writeClientSpecToFile($spec) { fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL); fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL); fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL); fwrite($spec, 'Owner: ' . $this->getUser() . PHP_EOL . PHP_EOL); fwrite($spec, 'Description:' . PHP_EOL); fwrite($spec, ' Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL); fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL); fwrite($spec, 'Options: noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL); fwrite($spec, 'SubmitOptions: revertunchanged' . PHP_EOL . PHP_EOL); fwrite($spec, 'LineEnd: local' . PHP_EOL . PHP_EOL); if ($this->isStream()) { fwrite($spec, 'Stream:' . PHP_EOL); fwrite($spec, ' ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL); } else { fwrite( $spec, 'View: ' . $this->getStream() . '/... //' . $this->getClient() . '/... ' . PHP_EOL ); } } public function writeP4ClientSpec() { $clientSpec = $this->getP4ClientSpec(); $spec = fopen($clientSpec, 'w'); try { $this->writeClientSpecToFile($spec); } catch (\Exception $e) { fclose($spec); throw $e; } fclose($spec); } protected function read($pipe, $name) { if (feof($pipe)) { return; } $line = fgets($pipe); while ($line !== false) { $line = fgets($pipe); } return; } public function windowsLogin($password) { $command = $this->generateP4Command(' login -a'); $process = new Process($command, null, null, $password); return $process->run(); } public function p4Login() { $this->queryP4User(); if (!$this->isLoggedIn()) { $password = $this->queryP4Password(); if ($this->windowsFlag) { $this->windowsLogin($password); } else { $command = 'echo ' . $password . ' | ' . $this->generateP4Command(' login -a', false); $exitCode = $this->executeCommand($command); $result = trim($this->commandResult); if ($exitCode) { throw new \Exception("Error logging in:" . $this->process->getErrorOutput()); } } } } public function getComposerInformation($identifier) { $composerFileContent = $this->getFileContent('composer.json', $identifier); if (!$composerFileContent) { return; } return json_decode($composerFileContent, true); } public function getFileContent($file, $identifier) { $path = $this->getFilePath($file, $identifier); $command = $this->generateP4Command(' print ' . $path); $this->executeCommand($command); $result = $this->commandResult; if (!trim($result)) { return null; } return $result; } public function getFilePath($file, $identifier) { $index = strpos($identifier, '@'); if ($index === false) { $path = $identifier. '/' . $file; return $path; } else { $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index); $command = $this->generateP4Command(' files ' . $path, false); $this->executeCommand($command); $result = $this->commandResult; $index2 = strpos($result, 'no such file(s).'); if ($index2 === false) { $index3 = strpos($result, 'change'); if ($index3 !== false) { $phrase = trim(substr($result, $index3)); $fields = explode(' ', $phrase); return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1]; } } } return null; } public function getBranches() { $possibleBranches = array(); if (!$this->isStream()) { $possibleBranches[$this->p4Branch] = $this->getStream(); } else { $command = $this->generateP4Command('streams //' . $this->p4Depot . '/...'); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); foreach ($resArray as $line) { $resBits = explode(' ', $line); if (count($resBits) > 4) { $branch = preg_replace('/[^A-Za-z0-9 ]/', '', $resBits[4]); $possibleBranches[$branch] = $resBits[1]; } } } $command = $this->generateP4Command('changes '. $this->getStream() . '/...', false); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); $lastCommit = $resArray[0]; $lastCommitArr = explode(' ', $lastCommit); $lastCommitNum = $lastCommitArr[1]; $branches = array('master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum); return $branches; } public function getTags() { $command = $this->generateP4Command('labels'); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); $tags = array(); foreach ($resArray as $line) { $index = strpos($line, 'Label'); if (!($index === false)) { $fields = explode(' ', $line); $tags[$fields[1]] = $this->getStream() . '@' . $fields[1]; } } return $tags; } public function checkStream() { $command = $this->generateP4Command('depots', false); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); foreach ($resArray as $line) { $index = strpos($line, 'Depot'); if (!($index === false)) { $fields = explode(' ', $line); if (strcmp($this->p4Depot, $fields[1]) === 0) { $this->p4DepotType = $fields[3]; return $this->isStream(); } } } return false; } /** * @param $reference * @return mixed|null */ protected function getChangeList($reference) { $index = strpos($reference, '@'); if ($index === false) { return null; } $label = substr($reference, $index); $command = $this->generateP4Command(' changes -m1 ' . $label); $this->executeCommand($command); $changes = $this->commandResult; if (strpos($changes, 'Change') !== 0) { return null; } $fields = explode(' ', $changes); $changeList = $fields[1]; return $changeList; } /** * @param $fromReference * @param $toReference * @return mixed|null */ public function getCommitLogs($fromReference, $toReference) { $fromChangeList = $this->getChangeList($fromReference); if ($fromChangeList === null) { return null; } $toChangeList = $this->getChangeList($toReference); if ($toChangeList === null) { return null; } $index = strpos($fromReference, '@'); $main = substr($fromReference, 0, $index) . '/...'; $command = $this->generateP4Command('filelog ' . $main . '@' . $fromChangeList. ',' . $toChangeList); $this->executeCommand($command); $result = $this->commandResult; return $result; } public function getFilesystem() { if (empty($this->filesystem)) { $this->filesystem = new Filesystem($this->process); } return $this->filesystem; } public function setFilesystem(Filesystem $fs) { $this->filesystem = $fs; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\IO\IOInterface; /** * Convert PHP errors into exceptions * * @author Artem Lopata */ class ErrorHandler { private static $io; /** * Error handler * * @param int $level Level of the error raised * @param string $message Error message * @param string $file Filename that the error was raised in * @param int $line Line number the error was raised at * * @static * @throws \ErrorException */ public static function handle($level, $message, $file, $line) { // error code is not included in error_reporting if (!(error_reporting() & $level)) { return; } if (ini_get('xdebug.scream')) { $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be". "\na legitimately suppressed error that you were not supposed to see."; } if ($level !== E_DEPRECATED && $level !== E_USER_DEPRECATED) { throw new \ErrorException($message, 0, $level, $file, $line); } if (self::$io) { self::$io->writeError('Deprecation Notice: '.$message.' in '.$file.':'.$line.''); if (self::$io->isVerbose()) { self::$io->writeError('Stack trace:'); self::$io->writeError(array_filter(array_map(function ($a) { if (isset($a['line'], $a['file'])) { return ' '.$a['file'].':'.$a['line'].''; } return null; }, array_slice(debug_backtrace(), 2)))); } } } /** * Register error handler. * * @param IOInterface|null $io */ public static function register(IOInterface $io = null) { set_error_handler(array(__CLASS__, 'handle')); error_reporting(E_ALL | E_STRICT); self::$io = $io; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; use Psr\Log\LoggerInterface; /** * @author François Pluchino * @author Jordi Boggiano * @author Nils Adermann */ class RemoteFilesystem { private $io; private $config; private $scheme; private $bytesMax; private $originUrl; private $fileUrl; private $fileName; private $retry; private $progress; private $lastProgress; private $options = array(); private $peerCertificateMap = array(); private $disableTls = false; private $retryAuthFailure; private $lastHeaders; private $storeAuth; private $degradedMode = false; private $redirects; private $maxRedirects = 20; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The config * @param array $options The options * @param bool $disableTls */ public function __construct(IOInterface $io, Config $config = null, array $options = array(), $disableTls = false) { $this->io = $io; // Setup TLS options // The cafile option can be set via config.json if ($disableTls === false) { $this->options = $this->getTlsDefaults($options); } else { $this->disableTls = true; } // handle the other externally set options normally. $this->options = array_replace_recursive($this->options, $options); $this->config = $config; } /** * Copy the remote file in local. * * @param string $originUrl The origin URL * @param string $fileUrl The file URL * @param string $fileName the local filename * @param bool $progress Display the progression * @param array $options Additional context options * * @return bool true */ public function copy($originUrl, $fileUrl, $fileName, $progress = true, $options = array()) { return $this->get($originUrl, $fileUrl, $options, $fileName, $progress); } /** * Get the content. * * @param string $originUrl The origin URL * @param string $fileUrl The file URL * @param bool $progress Display the progression * @param array $options Additional context options * * @return bool|string The content */ public function getContents($originUrl, $fileUrl, $progress = true, $options = array()) { return $this->get($originUrl, $fileUrl, $options, null, $progress); } /** * Retrieve the options set in the constructor * * @return array Options */ public function getOptions() { return $this->options; } /** * Merges new options * * @return array $options */ public function setOptions(array $options) { $this->options = array_replace_recursive($this->options, $options); } public function isTlsDisabled() { return $this->disableTls === true; } /** * Returns the headers of the last request * * @return array */ public function getLastHeaders() { return $this->lastHeaders; } /** * @param array $headers array of returned headers like from getLastHeaders() * @param string $name header name (case insensitive) * @return string|null */ public function findHeaderValue(array $headers, $name) { $value = null; foreach ($headers as $header) { if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) { $value = $match[1]; } elseif (preg_match('{^HTTP/}i', $header)) { // In case of redirects, http_response_headers contains the headers of all responses // so we reset the flag when a new response is being parsed as we are only interested in the last response $value = null; } } return $value; } /** * @param array $headers array of returned headers like from getLastHeaders() * @return int|null */ public function findStatusCode(array $headers) { $value = null; foreach ($headers as $header) { if (preg_match('{^HTTP/\S+ (\d+)}i', $header, $match)) { // In case of redirects, http_response_headers contains the headers of all responses // so we can not return directly and need to keep iterating $value = (int) $match[1]; } } return $value; } /** * Get file content or copy action. * * @param string $originUrl The origin URL * @param string $fileUrl The file URL * @param array $additionalOptions context options * @param string $fileName the local filename * @param bool $progress Display the progression * * @throws TransportException|\Exception * @throws TransportException When the file could not be downloaded * * @return bool|string */ protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true) { if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) { $originUrl = 'github.com'; } $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME); $this->bytesMax = 0; $this->originUrl = $originUrl; $this->fileUrl = $fileUrl; $this->fileName = $fileName; $this->progress = $progress; $this->lastProgress = null; $this->retryAuthFailure = true; $this->lastHeaders = array(); $this->redirects = 1; // The first request counts. // capture username/password from URL if there is one if (preg_match('{^https?://(.+):(.+)@([^/]+)}i', $fileUrl, $match)) { $this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2])); } $tempAdditionalOptions = $additionalOptions; if (isset($tempAdditionalOptions['retry-auth-failure'])) { $this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure']; unset($tempAdditionalOptions['retry-auth-failure']); } $isRedirect = false; if (isset($tempAdditionalOptions['redirects'])) { $this->redirects = $tempAdditionalOptions['redirects']; $isRedirect = true; unset($tempAdditionalOptions['redirects']); } $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions); unset($tempAdditionalOptions); $userlandFollow = isset($options['http']['follow_location']) && !$options['http']['follow_location']; $origFileUrl = $fileUrl; if (isset($options['github-token'])) { // only add the access_token if it is actually a github URL (in case we were redirected to S3) if (preg_match('{^https?://([a-z0-9-]+\.)*github\.com/}', $fileUrl)) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token']; } unset($options['github-token']); } if (isset($options['gitlab-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token']; unset($options['gitlab-token']); } if (isset($options['http'])) { $options['http']['ignore_errors'] = true; } if ($this->degradedMode && substr($fileUrl, 0, 21) === 'http://packagist.org/') { // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol $fileUrl = 'http://' . gethostbyname('packagist.org') . substr($fileUrl, 20); $degradedPackagist = true; } $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); $actualContextOptions = stream_context_get_options($ctx); $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : ''; $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $origFileUrl . $usingProxy, true, IOInterface::DEBUG); unset($origFileUrl, $actualContextOptions); // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 if ((substr($fileUrl, 0, 23) !== 'http://packagist.org/p/' || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) { $this->config->prohibitUrlByConfig($fileUrl, $this->io); } if ($this->progress && !$isRedirect) { $this->io->writeError("Downloading (connecting...)", false); } $errorMessage = ''; $errorCode = 0; $result = false; set_error_handler(function ($code, $msg) use (&$errorMessage) { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); }); try { $result = file_get_contents($fileUrl, false, $ctx); $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null; if ($contentLength && Platform::strlen($result) < $contentLength) { // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength); $e->setHeaders($http_response_header); $e->setStatusCode($this->findStatusCode($http_response_header)); $e->setResponse($result); $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG); throw $e; } if (PHP_VERSION_ID < 50600 && !empty($options['ssl']['peer_fingerprint'])) { // Emulate fingerprint validation on PHP < 5.6 $params = stream_context_get_params($ctx); $expectedPeerFingerprint = $options['ssl']['peer_fingerprint']; $peerFingerprint = TlsHelper::getCertificateFingerprint($params['options']['ssl']['peer_certificate']); // Constant time compare??! if ($expectedPeerFingerprint !== $peerFingerprint) { throw new TransportException('Peer fingerprint did not match'); } } } catch (\Exception $e) { if ($e instanceof TransportException && !empty($http_response_header[0])) { $e->setHeaders($http_response_header); $e->setStatusCode($this->findStatusCode($http_response_header)); } if ($e instanceof TransportException && $result !== false) { $e->setResponse($result); } $result = false; } if ($errorMessage && !ini_get('allow_url_fopen')) { $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')'; } restore_error_handler(); if (isset($e) && !$this->retry) { if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = true; $this->io->writeError(''); $this->io->writeError(array( ''.$e->getMessage().'', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', )); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } $statusCode = null; $contentType = null; if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); $contentType = $this->findHeaderValue($http_response_header, 'content-type'); } // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' && !$this->isPublicBitBucketDownload($fileUrl) && substr($fileUrl, -4) === '.zip' && $contentType && preg_match('{^text/html\b}i', $contentType) ) { $result = false; if ($this->retryAuthFailure) { $this->promptAuthAndRetry(401); } } // check for gitlab 404 when downloading archives if ($statusCode === 404 && $this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true) && false !== strpos($fileUrl, 'archive.zip') ) { $result = false; if ($this->retryAuthFailure) { $this->promptAuthAndRetry(401); } } // handle 3xx redirects for php<5.6, 304 Not Modified is excluded $hasFollowedRedirect = false; if ($userlandFollow && $statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) { $hasFollowedRedirect = true; $result = $this->handleRedirect($http_response_header, $additionalOptions, $result); } // fail 4xx and 5xx responses and capture the response if ($statusCode && $statusCode >= 400 && $statusCode <= 599) { if (!$this->retry) { if ($this->progress && !$this->retry && !$isRedirect) { $this->io->overwriteError("Downloading (failed)", false); } $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode); $e->setHeaders($http_response_header); $e->setResponse($result); $e->setStatusCode($statusCode); throw $e; } $result = false; } if ($this->progress && !$this->retry && !$isRedirect) { $this->io->overwriteError("Downloading (".($result === false ? 'failed' : '100%').")", false); } // decode gzip if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) { $contentEncoding = $this->findHeaderValue($http_response_header, 'content-encoding'); $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding); if ($decode) { try { if (PHP_VERSION_ID >= 50400) { $result = zlib_decode($result); } else { // work around issue with gzuncompress & co that do not work with all gzip checksums $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); } if (!$result) { throw new TransportException('Failed to decode zlib stream'); } } catch (\Exception $e) { if ($this->degradedMode) { throw $e; } $this->degradedMode = true; $this->io->writeError(array( '', 'Failed to decode response: '.$e->getMessage().'', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', )); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } } } // handle copy command if download was successful if (false !== $result && null !== $fileName && !$isRedirect) { if ('' === $result) { throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response'); } $errorMessage = ''; set_error_handler(function ($code, $msg) use (&$errorMessage) { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg); }); $result = (bool) file_put_contents($fileName, $result); restore_error_handler(); if (false === $result) { throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage); } } // Handle SSL cert match issues if (false === $result && false !== strpos($errorMessage, 'Peer certificate') && PHP_VERSION_ID < 50600) { // Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6 // The procedure to handle sAN for older PHP's is: // // 1. Open socket to remote server and fetch certificate (disabling peer // validation because PHP errors without giving up the certificate.) // // 2. Verifying the domain in the URL against the names in the sAN field. // If there is a match record the authority [host/port], certificate // common name, and certificate fingerprint. // // 3. Retry the original request but changing the CN_match parameter to // the common name extracted from the certificate in step 2. // // 4. To prevent any attempt at being hoodwinked by switching the // certificate between steps 2 and 3 the fingerprint of the certificate // presented in step 3 is compared against the one recorded in step 2. if (CaBundle::isOpensslParseSafe()) { $certDetails = $this->getCertificateCnAndFp($this->fileUrl, $options); if ($certDetails) { $this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails; $this->retry = true; } } else { $this->io->writeError(''); $this->io->writeError(sprintf( 'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.', PHP_VERSION )); } } if ($this->retry) { $this->retry = false; $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); if ($this->storeAuth && $this->config) { $authHelper = new AuthHelper($this->io, $this->config); $authHelper->storeAuth($this->originUrl, $this->storeAuth); $this->storeAuth = false; } return $result; } if (false === $result) { $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode); if (!empty($http_response_header[0])) { $e->setHeaders($http_response_header); } if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = true; $this->io->writeError(''); $this->io->writeError(array( ''.$e->getMessage().'', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', )); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } if (!empty($http_response_header[0])) { $this->lastHeaders = $http_response_header; } return $result; } /** * Get notification action. * * @param int $notificationCode The notification code * @param int $severity The severity level * @param string $message The message * @param int $messageCode The message code * @param int $bytesTransferred The loaded size * @param int $bytesMax The total size * @throws TransportException */ protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) { switch ($notificationCode) { case STREAM_NOTIFY_FAILURE: if (400 === $messageCode) { // This might happen if your host is secured by ssl client certificate authentication // but you do not send an appropriate certificate throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode); } // intentional fallthrough to the next case as the notificationCode // isn't always consistent and we should inspect the messageCode for 401s case STREAM_NOTIFY_AUTH_REQUIRED: if (401 === $messageCode) { // Bail if the caller is going to handle authentication failures itself. if (!$this->retryAuthFailure) { break; } $this->promptAuthAndRetry($messageCode); } break; case STREAM_NOTIFY_AUTH_RESULT: if (403 === $messageCode) { // Bail if the caller is going to handle authentication failures itself. if (!$this->retryAuthFailure) { break; } $this->promptAuthAndRetry($messageCode, $message); } break; case STREAM_NOTIFY_FILE_SIZE_IS: $this->bytesMax = $bytesMax; break; case STREAM_NOTIFY_PROGRESS: if ($this->bytesMax > 0 && $this->progress) { $progression = min(100, round($bytesTransferred / $this->bytesMax * 100)); if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) { $this->lastProgress = $progression; $this->io->overwriteError("Downloading ($progression%)", false); } } break; default: break; } } protected function promptAuthAndRetry($httpStatus, $reason = null) { if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) { $message = "\n".'Could not fetch '.$this->fileUrl.', please create a GitHub OAuth token '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit'); $gitHubUtil = new GitHub($this->io, $this->config, null); if (!$gitHubUtil->authorizeOAuth($this->originUrl) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message)) ) { throw new TransportException('Could not authenticate against '.$this->originUrl, 401); } } elseif ($this->config && in_array($this->originUrl, $this->config->get('gitlab-domains'), true)) { $message = "\n".'Could not fetch '.$this->fileUrl.', enter your ' . $this->originUrl . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit'); $gitLabUtil = new GitLab($this->io, $this->config, null); if ($this->io->hasAuthentication($this->originUrl) && ($auth = $this->io->getAuthentication($this->originUrl)) && $auth['password'] === 'private-token') { throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus); } if (!$gitLabUtil->authorizeOAuth($this->originUrl) && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, $message)) ) { throw new TransportException('Could not authenticate against '.$this->originUrl, 401); } } elseif ($this->config && $this->originUrl === 'bitbucket.org') { $askForOAuthToken = true; if ($this->io->hasAuthentication($this->originUrl)) { $auth = $this->io->getAuthentication($this->originUrl); if ($auth['username'] !== 'x-token-auth') { $bitbucketUtil = new Bitbucket($this->io, $this->config); $accessToken = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']); if (!empty($accessToken)) { $this->io->setAuthentication($this->originUrl, 'x-token-auth', $accessToken); $askForOAuthToken = false; } } else { throw new TransportException('Could not authenticate against ' . $this->originUrl, 401); } } if ($askForOAuthToken) { $message = "\n".'Could not fetch ' . $this->fileUrl . ', please create a bitbucket OAuth token to ' . (($httpStatus === 401 || $httpStatus === 403) ? 'access private repos' : 'go over the API rate limit'); $bitBucketUtil = new Bitbucket($this->io, $this->config); if (! $bitBucketUtil->authorizeOAuth($this->originUrl) && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($this->originUrl, $message)) ) { throw new TransportException('Could not authenticate against ' . $this->originUrl, 401); } } } else { // 404s are only handled for github if ($httpStatus === 404) { return; } // fail if the console is not interactive if (!$this->io->isInteractive()) { if ($httpStatus === 401) { $message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console to authenticate"; } if ($httpStatus === 403) { $message = "The '" . $this->fileUrl . "' URL could not be accessed: " . $reason; } throw new TransportException($message, $httpStatus); } // fail if we already have auth if ($this->io->hasAuthentication($this->originUrl)) { throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus); } $this->io->overwriteError(''); $this->io->writeError(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthentication($this->originUrl, $username, $password); $this->storeAuth = $this->config->get('store-auths'); } $this->retry = true; throw new TransportException('RETRY'); } protected function getOptionsForUrl($originUrl, $additionalOptions) { $tlsOptions = array(); // Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN if ($this->disableTls === false && PHP_VERSION_ID < 50600 && !stream_is_local($this->fileUrl)) { $host = parse_url($this->fileUrl, PHP_URL_HOST); if (PHP_VERSION_ID >= 50304) { // Must manually follow when setting CN_match because this causes all // redirects to be validated against the same CN_match value. $userlandFollow = true; } else { // PHP < 5.3.4 does not support follow_location, for those people // do some really nasty hard coded transformations. These will // still breakdown if the site redirects to a domain we don't // expect. if ($host === 'github.com' || $host === 'api.github.com') { $host = '*.github.com'; } } $tlsOptions['ssl']['CN_match'] = $host; $tlsOptions['ssl']['SNI_server_name'] = $host; $urlAuthority = $this->getUrlAuthority($this->fileUrl); if (isset($this->peerCertificateMap[$urlAuthority])) { // Handle subjectAltName on lesser PHP's. $certMap = $this->peerCertificateMap[$urlAuthority]; $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf( 'Using %s as CN for subjectAltName enabled host %s', $certMap['cn'], $urlAuthority ), true, IOInterface::DEBUG); $tlsOptions['ssl']['CN_match'] = $certMap['cn']; $tlsOptions['ssl']['peer_fingerprint'] = $certMap['fp']; } } $headers = array(); if (extension_loaded('zlib')) { $headers[] = 'Accept-Encoding: gzip'; } $options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions); if (!$this->degradedMode) { // degraded mode disables HTTP/1.1 which causes issues with some bad // proxies/software due to the use of chunked encoding $options['http']['protocol_version'] = 1.1; $headers[] = 'Connection: close'; } if (isset($userlandFollow)) { $options['http']['follow_location'] = 0; } if ($this->io->hasAuthentication($originUrl)) { $auth = $this->io->getAuthentication($originUrl); if ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) { $options['github-token'] = $auth['username']; } elseif ($this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)) { if ($auth['password'] === 'oauth2') { $headers[] = 'Authorization: Bearer '.$auth['username']; } elseif ($auth['password'] === 'private-token') { $headers[] = 'PRIVATE-TOKEN: '.$auth['username']; } } elseif ('bitbucket.org' === $originUrl && $this->fileUrl !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username'] ) { if (!$this->isPublicBitBucketDownload($this->fileUrl)) { $headers[] = 'Authorization: Bearer ' . $auth['password']; } } else { $authStr = base64_encode($auth['username'] . ':' . $auth['password']); $headers[] = 'Authorization: Basic '.$authStr; } } if (isset($options['http']['header']) && !is_array($options['http']['header'])) { $options['http']['header'] = explode("\r\n", trim($options['http']['header'], "\r\n")); } foreach ($headers as $header) { $options['http']['header'][] = $header; } return $options; } private function handleRedirect(array $http_response_header, array $additionalOptions, $result) { if ($locationHeader = $this->findHeaderValue($http_response_header, 'location')) { if (parse_url($locationHeader, PHP_URL_SCHEME)) { // Absolute URL; e.g. https://example.com/composer $targetUrl = $locationHeader; } elseif (parse_url($locationHeader, PHP_URL_HOST)) { // Scheme relative; e.g. //example.com/foo $targetUrl = $this->scheme.':'.$locationHeader; } elseif ('/' === $locationHeader[0]) { // Absolute path; e.g. /foo $urlHost = parse_url($this->fileUrl, PHP_URL_HOST); // Replace path using hostname as an anchor. $targetUrl = preg_replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl); } else { // Relative path; e.g. foo // This actually differs from PHP which seems to add duplicate slashes. $targetUrl = preg_replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl); } } if (!empty($targetUrl)) { $this->redirects++; $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $targetUrl), true, IOInterface::DEBUG); $additionalOptions['redirects'] = $this->redirects; return $this->get($this->originUrl, $targetUrl, $additionalOptions, $this->fileName, $this->progress); } if (!$this->retry) { $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$http_response_header[0].')'); $e->setHeaders($http_response_header); $e->setResponse($result); throw $e; } return false; } /** * @param array $options * * @return array */ private function getTlsDefaults(array $options) { $ciphers = implode(':', array( 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'DHE-RSA-AES128-GCM-SHA256', 'DHE-DSS-AES128-GCM-SHA256', 'kEDH+AESGCM', 'ECDHE-RSA-AES128-SHA256', 'ECDHE-ECDSA-AES128-SHA256', 'ECDHE-RSA-AES128-SHA', 'ECDHE-ECDSA-AES128-SHA', 'ECDHE-RSA-AES256-SHA384', 'ECDHE-ECDSA-AES256-SHA384', 'ECDHE-RSA-AES256-SHA', 'ECDHE-ECDSA-AES256-SHA', 'DHE-RSA-AES128-SHA256', 'DHE-RSA-AES128-SHA', 'DHE-DSS-AES128-SHA256', 'DHE-RSA-AES256-SHA256', 'DHE-DSS-AES256-SHA', 'DHE-RSA-AES256-SHA', 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-SHA256', 'AES256-SHA256', 'AES128-SHA', 'AES256-SHA', 'AES', 'CAMELLIA', 'DES-CBC3-SHA', '!aNULL', '!eNULL', '!EXPORT', '!DES', '!RC4', '!MD5', '!PSK', '!aECDH', '!EDH-DSS-DES-CBC3-SHA', '!EDH-RSA-DES-CBC3-SHA', '!KRB5-DES-CBC3-SHA', )); /** * CN_match and SNI_server_name are only known once a URL is passed. * They will be set in the getOptionsForUrl() method which receives a URL. * * cafile or capath can be overridden by passing in those options to constructor. */ $defaults = array( 'ssl' => array( 'ciphers' => $ciphers, 'verify_peer' => true, 'verify_depth' => 7, 'SNI_enabled' => true, 'capture_peer_cert' => true, ), ); if (isset($options['ssl'])) { $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']); } $caBundleLogger = $this->io instanceof LoggerInterface ? $this->io : null; /** * Attempt to find a local cafile or throw an exception if none pre-set * The user may go download one if this occurs. */ if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { $result = CaBundle::getSystemCaRootBundlePath($caBundleLogger); if (preg_match('{^phar://}', $result)) { $hash = hash_file('sha256', $result); $targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert-' . $hash . '.pem'; if (!file_exists($targetPath) || $hash !== hash_file('sha256', $targetPath)) { $this->streamCopy($result, $targetPath); chmod($targetPath, 0666); } $defaults['ssl']['cafile'] = $targetPath; } elseif (is_dir($result)) { $defaults['ssl']['capath'] = $result; } else { $defaults['ssl']['cafile'] = $result; } } if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $caBundleLogger))) { throw new TransportException('The configured cafile was not valid or could not be read.'); } if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) { throw new TransportException('The configured capath was not valid or could not be read.'); } /** * Disable TLS compression to prevent CRIME attacks where supported. */ if (PHP_VERSION_ID >= 50413) { $defaults['ssl']['disable_compression'] = true; } return $defaults; } /** * Uses stream_copy_to_stream instead of copy to work around https://bugs.php.net/bug.php?id=64634 * * @param string $source * @param string $target */ private function streamCopy($source, $target) { $source = fopen($source, 'r'); $target = fopen($target, 'w+'); stream_copy_to_stream($source, $target); fclose($source); fclose($target); unset($source, $target); } /** * Fetch certificate common name and fingerprint for validation of SAN. * * @todo Remove when PHP 5.6 is minimum supported version. */ private function getCertificateCnAndFp($url, $options) { if (PHP_VERSION_ID >= 50600) { throw new \BadMethodCallException(sprintf( '%s must not be used on PHP >= 5.6', __METHOD__ )); } $context = StreamContextFactory::getContext($url, $options, array('options' => array( 'ssl' => array( 'capture_peer_cert' => true, 'verify_peer' => false, // Yes this is fucking insane! But PHP is lame. ), ), )); // Ideally this would just use stream_socket_client() to avoid sending a // HTTP request but that does not capture the certificate. if (false === $handle = @fopen($url, 'rb', false, $context)) { return; } // Close non authenticated connection without reading any content. fclose($handle); $handle = null; $params = stream_context_get_params($context); if (!empty($params['options']['ssl']['peer_certificate'])) { $peerCertificate = $params['options']['ssl']['peer_certificate']; if (TlsHelper::checkCertificateHost($peerCertificate, parse_url($url, PHP_URL_HOST), $commonName)) { return array( 'cn' => $commonName, 'fp' => TlsHelper::getCertificateFingerprint($peerCertificate), ); } } } private function getUrlAuthority($url) { $defaultPorts = array( 'ftp' => 21, 'http' => 80, 'https' => 443, 'ssh2.sftp' => 22, 'ssh2.scp' => 22, ); $scheme = parse_url($url, PHP_URL_SCHEME); if (!isset($defaultPorts[$scheme])) { throw new \InvalidArgumentException(sprintf( 'Could not get default port for unknown scheme: %s', $scheme )); } $defaultPort = $defaultPorts[$scheme]; $port = parse_url($url, PHP_URL_PORT) ?: $defaultPort; return parse_url($url, PHP_URL_HOST).':'.$port; } /** * @link https://github.com/composer/composer/issues/5584 * * @param string $urlToBitBucketFile URL to a file at bitbucket.org. * * @return bool Whether the given URL is a public BitBucket download which requires no authentication. */ private function isPublicBitBucketDownload($urlToBitBucketFile) { $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST); if (strpos($domain, 'bitbucket.org') === false) { // Bitbucket downloads are hosted on amazonaws. // We do not need to authenticate there at all return true; } $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} $pathParts = explode('/', $path); if (count($pathParts) >= 4 && $pathParts[3] == 'downloads') { return true; } return false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Config; /** * Configuration Source Interface * * @author Jordi Boggiano * @author Beau Simensen */ interface ConfigSourceInterface { /** * Add a repository * * @param string $name Name * @param array $config Configuration */ public function addRepository($name, $config); /** * Remove a repository * * @param string $name */ public function removeRepository($name); /** * Add a config setting * * @param string $name Name * @param string $value Value */ public function addConfigSetting($name, $value); /** * Remove a config setting * * @param string $name */ public function removeConfigSetting($name); /** * Add a property * * @param string $name Name * @param string $value Value */ public function addProperty($name, $value); /** * Remove a property * * @param string $name */ public function removeProperty($name); /** * Add a package link * * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) * @param string $name Name * @param string $value Value */ public function addLink($type, $name, $value); /** * Remove a package link * * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) * @param string $name Name */ public function removeLink($type, $name); /** * Gives a user-friendly name to this source (file path or so) * * @return string */ public function getName(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Config; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Util\Silencer; /** * JSON Configuration Source * * @author Jordi Boggiano * @author Beau Simensen */ class JsonConfigSource implements ConfigSourceInterface { /** * @var JsonFile */ private $file; /** * @var bool */ private $authConfig; /** * Constructor * * @param JsonFile $file * @param bool $authConfig */ public function __construct(JsonFile $file, $authConfig = false) { $this->file = $file; $this->authConfig = $authConfig; } /** * {@inheritdoc} */ public function getName() { return $this->file->getPath(); } /** * {@inheritdoc} */ public function addRepository($name, $config) { $this->manipulateJson('addRepository', $name, $config, function (&$config, $repo, $repoConfig) { // if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have // to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation if (isset($config['repositories'])) { foreach ($config['repositories'] as $index => $val) { if ($index === $repo) { continue; } if (is_numeric($index) && ($val === array('packagist' => false) || $val === array('packagist.org' => false))) { unset($config['repositories'][$index]); $config['repositories']['packagist.org'] = false; break; } } } $config['repositories'][$repo] = $repoConfig; }); } /** * {@inheritdoc} */ public function removeRepository($name) { $this->manipulateJson('removeRepository', $name, function (&$config, $repo) { unset($config['repositories'][$repo]); }); } /** * {@inheritdoc} */ public function addConfigSetting($name, $value) { $authConfig = $this->authConfig; $this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) use ($authConfig) { if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|platform)\.}', $key)) { list($key, $host) = explode('.', $key, 2); if ($authConfig) { $config[$key][$host] = $val; } else { $config['config'][$key][$host] = $val; } } else { $config['config'][$key] = $val; } }); } /** * {@inheritdoc} */ public function removeConfigSetting($name) { $authConfig = $this->authConfig; $this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) use ($authConfig) { if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|platform)\.}', $key)) { list($key, $host) = explode('.', $key, 2); if ($authConfig) { unset($config[$key][$host]); } else { unset($config['config'][$key][$host]); } } else { unset($config['config'][$key]); } }); } /** * {@inheritdoc} */ public function addProperty($name, $value) { $this->manipulateJson('addProperty', $name, $value, function (&$config, $key, $val) { if (substr($key, 0, 6) === 'extra.') { $bits = explode('.', $key); $last = array_pop($bits); $arr = &$config['extra']; foreach ($bits as $bit) { if (!isset($arr[$bit])) { $arr[$bit] = array(); } $arr = &$arr[$bit]; } $arr[$last] = $val; } else { $config[$key] = $val; } }); } /** * {@inheritdoc} */ public function removeProperty($name) { $authConfig = $this->authConfig; $this->manipulateJson('removeProperty', $name, function (&$config, $key) { if (substr($key, 0, 6) === 'extra.') { $bits = explode('.', $key); $last = array_pop($bits); $arr = &$config['extra']; foreach ($bits as $bit) { if (!isset($arr[$bit])) { return; } $arr = &$arr[$bit]; } unset($arr[$last]); } else { unset($config[$key]); } }); } /** * {@inheritdoc} */ public function addLink($type, $name, $value) { $this->manipulateJson('addLink', $type, $name, $value, function (&$config, $type, $name, $value) { $config[$type][$name] = $value; }); } /** * {@inheritdoc} */ public function removeLink($type, $name) { $this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) { unset($config[$type][$name]); }); } protected function manipulateJson($method, $args, $fallback) { $args = func_get_args(); // remove method & fallback array_shift($args); $fallback = array_pop($args); if ($this->file->exists()) { if (!is_writable($this->file->getPath())) { throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath())); } if (!is_readable($this->file->getPath())) { throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath())); } $contents = file_get_contents($this->file->getPath()); } elseif ($this->authConfig) { $contents = "{\n}\n"; } else { $contents = "{\n \"config\": {\n }\n}\n"; } $manipulator = new JsonManipulator($contents); $newFile = !$this->file->exists(); // override manipulator method for auth config files if ($this->authConfig && $method === 'addConfigSetting') { $method = 'addSubNode'; list($mainNode, $name) = explode('.', $args[0], 2); $args = array($mainNode, $name, $args[1]); } elseif ($this->authConfig && $method === 'removeConfigSetting') { $method = 'removeSubNode'; list($mainNode, $name) = explode('.', $args[0], 2); $args = array($mainNode, $name); } // try to update cleanly if (call_user_func_array(array($manipulator, $method), $args)) { file_put_contents($this->file->getPath(), $manipulator->getContents()); } else { // on failed clean update, call the fallback and rewrite the whole file $config = $this->file->read(); $this->arrayUnshiftRef($args, $config); call_user_func_array($fallback, $args); $this->file->write($config); } if ($newFile) { Silencer::call('chmod', $this->file->getPath(), 0600); } } /** * Prepend a reference to an element to the beginning of an array. * * @param array $array * @param mixed $value * @return array */ private function arrayUnshiftRef(&$array, &$value) { $return = array_unshift($array, ''); $array[0] = &$value; return $return; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; /** * The Installer Events. * * @author François Pluchino */ class InstallerEvents { /** * The PRE_DEPENDENCIES_SOLVING event occurs as a installer begins * resolve operations. * * The event listener method receives a * Composer\Installer\InstallerEvent instance. * * @var string */ const PRE_DEPENDENCIES_SOLVING = 'pre-dependencies-solving'; /** * The POST_DEPENDENCIES_SOLVING event occurs as a installer after * resolve operations. * * The event listener method receives a * Composer\Installer\InstallerEvent instance. * * @var string */ const POST_DEPENDENCIES_SOLVING = 'post-dependencies-solving'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Package\PackageInterface; /** * Interface for the package installation manager that handle binary installation. * * @author Jordi Boggiano */ interface BinaryPresenceInterface { /** * Make sure binaries are installed for a given package. * * @param PackageInterface $package package instance */ public function ensureBinariesPresence(PackageInterface $package); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\Silencer; /** * Utility to handle installation of package "bin"/binaries * * @author Jordi Boggiano * @author Konstantin Kudryashov * @author Helmut Hummel */ class BinaryInstaller { protected $binDir; protected $binCompat; protected $io; protected $filesystem; /** * @param IOInterface $io * @param string $binDir * @param string $binCompat * @param Filesystem $filesystem */ public function __construct(IOInterface $io, $binDir, $binCompat, Filesystem $filesystem = null) { $this->binDir = $binDir; $this->binCompat = $binCompat; $this->io = $io; $this->filesystem = $filesystem ?: new Filesystem(); } public function installBinaries(PackageInterface $package, $installPath, $warnOnOverwrite = true) { $binaries = $this->getBinaries($package); if (!$binaries) { return; } foreach ($binaries as $bin) { $binPath = $installPath.'/'.$bin; if (!file_exists($binPath)) { $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package'); continue; } // in case a custom installer returned a relative path for the // $package, we can now safely turn it into a absolute path (as we // already checked the binary's existence). The following helpers // will require absolute paths to work properly. $binPath = realpath($binPath); $this->initializeBinDir(); $link = $this->binDir.'/'.basename($bin); if (file_exists($link)) { if (is_link($link)) { // likely leftover from a previous install, make sure // that the target is still executable in case this // is a fresh install of the vendor. Silencer::call('chmod', $link, 0777 & ~umask()); } if ($warnOnOverwrite) { $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file'); } continue; } if ($this->binCompat === "auto") { if (Platform::isWindows()) { $this->installFullBinaries($binPath, $link, $bin, $package); } else { $this->installSymlinkBinaries($binPath, $link); } } elseif ($this->binCompat === "full") { $this->installFullBinaries($binPath, $link, $bin, $package); } Silencer::call('chmod', $link, 0777 & ~umask()); } } public function removeBinaries(PackageInterface $package) { $this->initializeBinDir(); $binaries = $this->getBinaries($package); if (!$binaries) { return; } foreach ($binaries as $bin) { $link = $this->binDir.'/'.basename($bin); if (is_link($link) || file_exists($link)) { $this->filesystem->unlink($link); } if (file_exists($link.'.bat')) { $this->filesystem->unlink($link.'.bat'); } } // attempt removing the bin dir in case it is left empty if ((is_dir($this->binDir)) && ($this->filesystem->isDirEmpty($this->binDir))) { Silencer::call('rmdir', $this->binDir); } } public static function determineBinaryCaller($bin) { if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) { return 'call'; } $handle = fopen($bin, 'r'); $line = fgets($handle); fclose($handle); if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) { return trim($match[1]); } return 'php'; } protected function getBinaries(PackageInterface $package) { return $package->getBinaries(); } protected function installFullBinaries($binPath, $link, $bin, PackageInterface $package) { // add unixy support for cygwin and similar environments if ('.bat' !== substr($binPath, -4)) { $this->installUnixyProxyBinaries($binPath, $link); @chmod($link, 0777 & ~umask()); $link .= '.bat'; if (file_exists($link)) { $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); } } if (!file_exists($link)) { file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link)); } } protected function installSymlinkBinaries($binPath, $link) { if (!$this->filesystem->relativeSymlink($binPath, $link)) { $this->installUnixyProxyBinaries($binPath, $link); } } protected function installUnixyProxyBinaries($binPath, $link) { file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); } protected function initializeBinDir() { $this->filesystem->ensureDirectoryExists($this->binDir); $this->binDir = realpath($this->binDir); } protected function generateWindowsProxyCode($bin, $link) { $binPath = $this->filesystem->findShortestPath($link, $bin); $caller = self::determineBinaryCaller($bin); return "@ECHO OFF\r\n". "setlocal DISABLEDELAYEDEXPANSION\r\n". "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n". "{$caller} \"%BIN_TARGET%\" %*\r\n"; } protected function generateUnixyProxyCode($bin, $link) { $binPath = $this->filesystem->findShortestPath($link, $bin); $binDir = ProcessExecutor::escape(dirname($binPath)); $binFile = basename($binPath); $proxyCode = << /dev/null; cd $binDir && pwd) # See if we are running in Cygwin by checking for cygpath program if command -v 'cygpath' >/dev/null 2>&1; then # Cygwin paths start with /cygdrive/ which will break windows PHP, # so we need to translate the dir path to windows format. However # we could be using cygwin PHP which does not require this, so we # test if the path to PHP starts with /cygdrive/ rather than /usr/bin if [[ $(which php) == /cygdrive/* ]]; then dir=$(cygpath -m "\$dir"); fi fi dir=$(echo \$dir | sed 's/ /\ /g') "\${dir}/$binFile" "$@" PROXY; return $proxyCode; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\EventDispatcher\Event; use Composer\IO\IOInterface; use Composer\Repository\CompositeRepository; /** * An event for all installer. * * @author François Pluchino */ class InstallerEvent extends Event { /** * @var Composer */ private $composer; /** * @var IOInterface */ private $io; /** * @var bool */ private $devMode; /** * @var PolicyInterface */ private $policy; /** * @var Pool */ private $pool; /** * @var CompositeRepository */ private $installedRepo; /** * @var Request */ private $request; /** * @var OperationInterface[] */ private $operations; /** * Constructor. * * @param string $eventName * @param Composer $composer * @param IOInterface $io * @param bool $devMode * @param PolicyInterface $policy * @param Pool $pool * @param CompositeRepository $installedRepo * @param Request $request * @param OperationInterface[] $operations */ public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) { parent::__construct($eventName); $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; $this->policy = $policy; $this->pool = $pool; $this->installedRepo = $installedRepo; $this->request = $request; $this->operations = $operations; } /** * @return Composer */ public function getComposer() { return $this->composer; } /** * @return IOInterface */ public function getIO() { return $this->io; } /** * @return bool */ public function isDevMode() { return $this->devMode; } /** * @return PolicyInterface */ public function getPolicy() { return $this->policy; } /** * @return Pool */ public function getPool() { return $this->pool; } /** * @return CompositeRepository */ public function getInstalledRepo() { return $this->installedRepo; } /** * @return Request */ public function getRequest() { return $this->request; } /** * @return OperationInterface[] */ public function getOperations() { return $this->operations; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; /** * Installer for plugin packages * * @author Jordi Boggiano * @author Nils Adermann */ class PluginInstaller extends LibraryInstaller { private $installationManager; /** * Initializes Plugin installer. * * @param IOInterface $io * @param Composer $composer * @param string $type */ public function __construct(IOInterface $io, Composer $composer, $type = 'library') { parent::__construct($io, $composer, 'composer-plugin'); $this->installationManager = $composer->getInstallationManager(); } /** * {@inheritDoc} */ public function supports($packageType) { return $packageType === 'composer-plugin' || $packageType === 'composer-installer'; } /** * {@inheritDoc} */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } parent::install($repo, $package); try { $this->composer->getPluginManager()->registerPackage($package, true); } catch (\Exception $e) { // Rollback installation $this->io->writeError('Plugin installation failed, rolling back'); parent::uninstall($repo, $package); throw $e; } } /** * {@inheritDoc} */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { $extra = $target->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } parent::update($repo, $initial, $target); $this->composer->getPluginManager()->registerPackage($target, true); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Composer; use Composer\Downloader\PearPackageExtractor; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\Util\Platform; use Composer\Util\Filesystem; /** * Package installation manager. * * @author Jordi Boggiano * @author Konstantin Kudryashov */ class PearInstaller extends LibraryInstaller { /** * Initializes library installer. * * @param IOInterface $io io instance * @param Composer $composer * @param string $type package type that this installer handles */ public function __construct(IOInterface $io, Composer $composer, $type = 'pear-library') { $filesystem = new Filesystem(); $binaryInstaller = new PearBinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), rtrim($composer->getConfig()->get('vendor-dir'), '/'), $composer->getConfig()->get('bin-compat'), $filesystem, $this); parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller); } /** * {@inheritDoc} */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { $this->uninstall($repo, $initial); $this->install($repo, $target); } protected function installCode(PackageInterface $package) { parent::installCode($package); $isWindows = Platform::isWindows(); $php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php'); if (!$isWindows) { $php_bin = '/usr/bin/env ' . $php_bin; } $installPath = $this->getInstallPath($package); $vars = array( 'os' => $isWindows ? 'windows' : 'linux', 'php_bin' => $php_bin, 'pear_php' => $installPath, 'php_dir' => $installPath, 'bin_dir' => $installPath . '/bin', 'data_dir' => $installPath . '/data', 'version' => $package->getPrettyVersion(), ); $packageArchive = $this->getInstallPath($package).'/'.pathinfo($package->getDistUrl(), PATHINFO_BASENAME); $pearExtractor = new PearPackageExtractor($packageArchive); $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars); $this->io->writeError(' Cleaning up', true, IOInterface::VERBOSE); $this->filesystem->unlink($packageArchive); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Repository\RepositoryInterface; use Symfony\Component\Console\Formatter\OutputFormatter; /** * Add suggested packages from different places to output them in the end. * * @author Haralan Dobrev */ class SuggestedPackagesReporter { /** * @var array */ protected $suggestedPackages = array(); /** * @var IOInterface */ private $io; public function __construct(IOInterface $io) { $this->io = $io; } /** * @return array Suggested packages with source, target and reason keys. */ public function getPackages() { return $this->suggestedPackages; } /** * Add suggested packages to be listed after install * * Could be used to add suggested packages both from the installer * or from CreateProjectCommand. * * @param string $source Source package which made the suggestion * @param string $target Target package to be suggested * @param string $reason Reason the target package to be suggested * @return SuggestedPackagesReporter */ public function addPackage($source, $target, $reason) { $this->suggestedPackages[] = array( 'source' => $source, 'target' => $target, 'reason' => $reason, ); return $this; } /** * Add all suggestions from a package. * * @param PackageInterface $package * @return SuggestedPackagesReporter */ public function addSuggestionsFromPackage(PackageInterface $package) { $source = $package->getPrettyName(); foreach ($package->getSuggests() as $target => $reason) { $this->addPackage( $source, $target, $reason ); } return $this; } /** * Output suggested packages. * Do not list the ones already installed if installed repository provided. * * @param RepositoryInterface $installedRepo Installed packages * @return SuggestedPackagesReporter */ public function output(RepositoryInterface $installedRepo = null) { $suggestedPackages = $this->getPackages(); $installedPackages = array(); if (null !== $installedRepo && ! empty($suggestedPackages)) { foreach ($installedRepo->getPackages() as $package) { $installedPackages = array_merge( $installedPackages, $package->getNames() ); } } foreach ($suggestedPackages as $suggestion) { if (in_array($suggestion['target'], $installedPackages)) { continue; } $this->io->writeError(sprintf( '%s suggests installing %s (%s)', $suggestion['source'], $this->escapeOutput($suggestion['target']), $this->escapeOutput($suggestion['reason']) )); } return $this; } /** * @param string $string * @return string */ private function escapeOutput($string) { return OutputFormatter::escape( $this->removeControlCharacters($string) ); } /** * @param string $string * @return string */ private function removeControlCharacters($string) { return preg_replace( '/[[:cntrl:]]/', '', str_replace("\n", ' ', $string) ); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; /** * Package Events. * * @author Jordi Boggiano */ class PackageEvents { /** * The PRE_PACKAGE_INSTALL event occurs before a package is installed. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ const PRE_PACKAGE_INSTALL = 'pre-package-install'; /** * The POST_PACKAGE_INSTALL event occurs after a package is installed. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ const POST_PACKAGE_INSTALL = 'post-package-install'; /** * The PRE_PACKAGE_UPDATE event occurs before a package is updated. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ const PRE_PACKAGE_UPDATE = 'pre-package-update'; /** * The POST_PACKAGE_UPDATE event occurs after a package is updated. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ const POST_PACKAGE_UPDATE = 'post-package-update'; /** * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; /** * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\ProcessExecutor; /** * Utility to handle installation of package "bin"/binaries for PEAR packages * * @author Jordi Boggiano */ class PearBinaryInstaller extends BinaryInstaller { private $installer; private $vendorDir; /** * @param IOInterface $io * @param string $binDir * @param string $vendorDir * @param string $binCompat * @param Filesystem $filesystem * @param PearInstaller $installer */ public function __construct(IOInterface $io, $binDir, $vendorDir, $binCompat, Filesystem $filesystem, PearInstaller $installer) { parent::__construct($io, $binDir, $binCompat, $filesystem); $this->installer = $installer; $this->vendorDir = $vendorDir; } protected function getBinaries(PackageInterface $package) { $binariesPath = $this->installer->getInstallPath($package) . '/bin/'; $binaries = array(); if (file_exists($binariesPath)) { foreach (new \FilesystemIterator($binariesPath, \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO) as $fileName => $value) { if (!$value->isDir()) { $binaries[] = 'bin/'.$fileName; } } } return $binaries; } protected function initializeBinDir() { parent::initializeBinDir(); file_put_contents($this->binDir.'/composer-php', $this->generateUnixyPhpProxyCode()); @chmod($this->binDir.'/composer-php', 0777); file_put_contents($this->binDir.'/composer-php.bat', $this->generateWindowsPhpProxyCode()); @chmod($this->binDir.'/composer-php.bat', 0777); } protected function generateWindowsProxyCode($bin, $link) { $binPath = $this->filesystem->findShortestPath($link, $bin); if ('.bat' === substr($bin, -4)) { $caller = 'call'; } else { $handle = fopen($bin, 'r'); $line = fgets($handle); fclose($handle); if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) { $caller = trim($match[1]); } else { $caller = 'php'; } if ($caller === 'php') { return "@echo off\r\n". "pushd .\r\n". "cd %~dp0\r\n". "set PHP_PROXY=%CD%\\composer-php.bat\r\n". "cd ".ProcessExecutor::escape(dirname($binPath))."\r\n". "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n". "popd\r\n". "%PHP_PROXY% \"%BIN_TARGET%\" %*\r\n"; } } return "@echo off\r\n". "pushd .\r\n". "cd %~dp0\r\n". "cd ".ProcessExecutor::escape(dirname($binPath))."\r\n". "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n". "popd\r\n". $caller." \"%BIN_TARGET%\" %*\r\n"; } private function generateWindowsPhpProxyCode() { $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true); return "@echo off\r\n" . "setlocal enabledelayedexpansion\r\n" . "set BIN_DIR=%~dp0\r\n" . "set VENDOR_DIR=%BIN_DIR%\\".$binToVendor."\r\n" . "set DIRS=.\r\n" . "FOR /D %%V IN (%VENDOR_DIR%\\*) DO (\r\n" . " FOR /D %%P IN (%%V\\*) DO (\r\n" . " set DIRS=!DIRS!;%%~fP\r\n" . " )\r\n" . ")\r\n" . "php.exe -d include_path=!DIRS! %*\r\n"; } private function generateUnixyPhpProxyCode() { $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true); return "#!/usr/bin/env sh\n". "SRC_DIR=`pwd`\n". "BIN_DIR=`dirname $0`\n". "VENDOR_DIR=\$BIN_DIR/".escapeshellarg($binToVendor)."\n". "DIRS=\"\"\n". "for vendor in \$VENDOR_DIR/*; do\n". " if [ -d \"\$vendor\" ]; then\n". " for package in \$vendor/*; do\n". " if [ -d \"\$package\" ]; then\n". " DIRS=\"\${DIRS}:\${package}\"\n". " fi\n". " done\n". " fi\n". "done\n". "php -d include_path=\".\$DIRS\" $@\n"; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; /** * Does not install anything but marks packages installed in the repo * * Useful for dry runs * * @author Jordi Boggiano */ class NoopInstaller implements InstallerInterface { /** * {@inheritDoc} */ public function supports($packageType) { return true; } /** * {@inheritDoc} */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package); } /** * {@inheritDoc} */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } } /** * {@inheritDoc} */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); } $repo->removePackage($initial); if (!$repo->hasPackage($target)) { $repo->addPackage(clone $target); } } /** * {@inheritDoc} */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: '.$package); } $repo->removePackage($package); } /** * {@inheritDoc} */ public function getInstallPath(PackageInterface $package) { $targetDir = $package->getTargetDir(); return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : ''); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Package\PackageInterface; use Composer\Downloader\DownloadManager; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; /** * Project Installer is used to install a single package into a directory as * root project. * * @author Benjamin Eberlei */ class ProjectInstaller implements InstallerInterface { private $installPath; private $downloadManager; private $filesystem; public function __construct($installPath, DownloadManager $dm) { $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/'; $this->downloadManager = $dm; $this->filesystem = new Filesystem; } /** * Decides if the installer supports the given type * * @param string $packageType * @return bool */ public function supports($packageType) { return true; } /** * {@inheritDoc} */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return false; } /** * {@inheritDoc} */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $installPath = $this->installPath; if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) { throw new \InvalidArgumentException("Project directory $installPath is not empty."); } if (!is_dir($installPath)) { mkdir($installPath, 0777, true); } $this->downloadManager->download($package, $installPath); } /** * {@inheritDoc} */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { throw new \InvalidArgumentException("not supported"); } /** * {@inheritDoc} */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { throw new \InvalidArgumentException("not supported"); } /** * Returns the installation path of a package * * @param PackageInterface $package * @return string path */ public function getInstallPath(PackageInterface $package) { return $this->installPath; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\Repository\CompositeRepository; /** * The Package Event. * * @author Jordi Boggiano */ class PackageEvent extends InstallerEvent { /** * @var OperationInterface The package instance */ private $operation; /** * Constructor. * * @param string $eventName * @param Composer $composer * @param IOInterface $io * @param bool $devMode * @param PolicyInterface $policy * @param Pool $pool * @param CompositeRepository $installedRepo * @param Request $request * @param OperationInterface[] $operations * @param OperationInterface $operation */ public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation) { parent::__construct($eventName, $composer, $io, $devMode, $policy, $pool, $installedRepo, $request, $operations); $this->operation = $operation; } /** * Returns the package instance. * * @return OperationInterface */ public function getOperation() { return $this->operation; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use InvalidArgumentException; /** * Interface for the package installation manager. * * @author Konstantin Kudryashov * @author Jordi Boggiano */ interface InstallerInterface { /** * Decides if the installer supports the given type * * @param string $packageType * @return bool */ public function supports($packageType); /** * Checks that provided package is installed. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance * * @return bool */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Installs specific package. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Updates specific package. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $initial already installed package version * @param PackageInterface $target updated version * * @throws InvalidArgumentException if $initial package is not installed */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target); /** * Uninstalls specific package. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Returns the installation path of a package * * @param PackageInterface $package * @return string path */ public function getInstallPath(PackageInterface $package); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; /** * Metapackage installation manager. * * @author Martin Hasoň */ class MetapackageInstaller implements InstallerInterface { /** * {@inheritDoc} */ public function supports($packageType) { return $packageType === 'metapackage'; } /** * {@inheritDoc} */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package); } /** * {@inheritDoc} */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $repo->addPackage(clone $package); } /** * {@inheritDoc} */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); } $repo->removePackage($initial); $repo->addPackage(clone $target); } /** * {@inheritDoc} */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: '.$package); } $repo->removePackage($package); } /** * {@inheritDoc} */ public function getInstallPath(PackageInterface $package) { return ''; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Repository\RepositoryInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\Util\StreamContextFactory; /** * Package operation manager. * * @author Konstantin Kudryashov * @author Jordi Boggiano * @author Nils Adermann */ class InstallationManager { private $installers = array(); private $cache = array(); private $notifiablePackages = array(); public function reset() { $this->notifiablePackages = array(); } /** * Adds installer * * @param InstallerInterface $installer installer instance */ public function addInstaller(InstallerInterface $installer) { array_unshift($this->installers, $installer); $this->cache = array(); } /** * Removes installer * * @param InstallerInterface $installer installer instance */ public function removeInstaller(InstallerInterface $installer) { if (false !== ($key = array_search($installer, $this->installers, true))) { array_splice($this->installers, $key, 1); $this->cache = array(); } } /** * Disables plugins. * * We prevent any plugins from being instantiated by simply * deactivating the installer for them. This ensure that no third-party * code is ever executed. */ public function disablePlugins() { foreach ($this->installers as $i => $installer) { if (!$installer instanceof PluginInstaller) { continue; } unset($this->installers[$i]); } } /** * Returns installer for a specific package type. * * @param string $type package type * * @throws \InvalidArgumentException if installer for provided type is not registered * @return InstallerInterface */ public function getInstaller($type) { $type = strtolower($type); if (isset($this->cache[$type])) { return $this->cache[$type]; } foreach ($this->installers as $installer) { if ($installer->supports($type)) { return $this->cache[$type] = $installer; } } throw new \InvalidArgumentException('Unknown installer type: '.$type); } /** * Checks whether provided package is installed in one of the registered installers. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance * * @return bool */ public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { if ($package instanceof AliasPackage) { return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf()); } return $this->getInstaller($package->getType())->isInstalled($repo, $package); } /** * Install binary for the given package. * If the installer associated to this package doesn't handle that function, it'll do nothing. * * @param PackageInterface $package Package instance */ public function ensureBinariesPresence(PackageInterface $package) { try { $installer = $this->getInstaller($package->getType()); } catch (\InvalidArgumentException $e) { // no installer found for the current package type (@see `getInstaller()`) return; } // if the given installer support installing binaries if ($installer instanceof BinaryPresenceInterface) { $installer->ensureBinariesPresence($package); } } /** * Executes solver operation. * * @param RepositoryInterface $repo repository in which to check * @param OperationInterface $operation operation instance */ public function execute(RepositoryInterface $repo, OperationInterface $operation) { $method = $operation->getJobType(); $this->$method($repo, $operation); } /** * Executes install operation. * * @param RepositoryInterface $repo repository in which to check * @param InstallOperation $operation operation instance */ public function install(RepositoryInterface $repo, InstallOperation $operation) { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); $installer->install($repo, $package); $this->markForNotification($package); } /** * Executes update operation. * * @param RepositoryInterface $repo repository in which to check * @param UpdateOperation $operation operation instance */ public function update(RepositoryInterface $repo, UpdateOperation $operation) { $initial = $operation->getInitialPackage(); $target = $operation->getTargetPackage(); $initialType = $initial->getType(); $targetType = $target->getType(); if ($initialType === $targetType) { $installer = $this->getInstaller($initialType); $installer->update($repo, $initial, $target); $this->markForNotification($target); } else { $this->getInstaller($initialType)->uninstall($repo, $initial); $this->getInstaller($targetType)->install($repo, $target); } } /** * Uninstalls package. * * @param RepositoryInterface $repo repository in which to check * @param UninstallOperation $operation operation instance */ public function uninstall(RepositoryInterface $repo, UninstallOperation $operation) { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); $installer->uninstall($repo, $package); } /** * Executes markAliasInstalled operation. * * @param RepositoryInterface $repo repository in which to check * @param MarkAliasInstalledOperation $operation operation instance */ public function markAliasInstalled(RepositoryInterface $repo, MarkAliasInstalledOperation $operation) { $package = $operation->getPackage(); if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } } /** * Executes markAlias operation. * * @param RepositoryInterface $repo repository in which to check * @param MarkAliasUninstalledOperation $operation operation instance */ public function markAliasUninstalled(RepositoryInterface $repo, MarkAliasUninstalledOperation $operation) { $package = $operation->getPackage(); $repo->removePackage($package); } /** * Returns the installation path of a package * * @param PackageInterface $package * @return string path */ public function getInstallPath(PackageInterface $package) { $installer = $this->getInstaller($package->getType()); return $installer->getInstallPath($package); } public function notifyInstalls(IOInterface $io) { foreach ($this->notifiablePackages as $repoUrl => $packages) { $repositoryName = parse_url($repoUrl, PHP_URL_HOST); if ($io->hasAuthentication($repositoryName)) { $auth = $io->getAuthentication($repositoryName); $authStr = base64_encode($auth['username'] . ':' . $auth['password']); $authHeader = 'Authorization: Basic '.$authStr; } // non-batch API, deprecated if (strpos($repoUrl, '%package%')) { foreach ($packages as $package) { $url = str_replace('%package%', $package->getPrettyName(), $repoUrl); $params = array( 'version' => $package->getPrettyVersion(), 'version_normalized' => $package->getVersion(), ); $opts = array('http' => array( 'method' => 'POST', 'header' => array('Content-type: application/x-www-form-urlencoded'), 'content' => http_build_query($params, '', '&'), 'timeout' => 3, ), ); if (isset($authHeader)) { $opts['http']['header'][] = $authHeader; } $context = StreamContextFactory::getContext($url, $opts); @file_get_contents($url, false, $context); } continue; } $postData = array('downloads' => array()); foreach ($packages as $package) { $postData['downloads'][] = array( 'name' => $package->getPrettyName(), 'version' => $package->getVersion(), ); } $opts = array('http' => array( 'method' => 'POST', 'header' => array('Content-Type: application/json'), 'content' => json_encode($postData), 'timeout' => 6, ), ); if (isset($authHeader)) { $opts['http']['header'][] = $authHeader; } $context = StreamContextFactory::getContext($repoUrl, $opts); @file_get_contents($repoUrl, false, $context); } $this->reset(); } private function markForNotification(PackageInterface $package) { if ($package->getNotificationUrl()) { $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\Silencer; /** * Package installation manager. * * @author Jordi Boggiano * @author Konstantin Kudryashov */ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface { protected $composer; protected $vendorDir; protected $binDir; protected $downloadManager; protected $io; protected $type; protected $filesystem; protected $binCompat; protected $binaryInstaller; /** * Initializes library installer. * * @param IOInterface $io * @param Composer $composer * @param string $type * @param Filesystem $filesystem * @param BinaryInstaller $binaryInstaller */ public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, BinaryInstaller $binaryInstaller = null) { $this->composer = $composer; $this->downloadManager = $composer->getDownloadManager(); $this->io = $io; $this->type = $type; $this->filesystem = $filesystem ?: new Filesystem(); $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/'); $this->binaryInstaller = $binaryInstaller ?: new BinaryInstaller($this->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem); } /** * {@inheritDoc} */ public function supports($packageType) { return $packageType === $this->type || null === $this->type; } /** * {@inheritDoc} */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package) && is_readable($this->getInstallPath($package)); } /** * {@inheritDoc} */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); // remove the binaries if it appears the package files are missing if (!is_readable($downloadPath) && $repo->hasPackage($package)) { $this->binaryInstaller->removeBinaries($package); } $this->installCode($package); $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package)); if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } } /** * {@inheritDoc} */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); } $this->initializeVendorDir(); $this->binaryInstaller->removeBinaries($initial); $this->updateCode($initial, $target); $this->binaryInstaller->installBinaries($target, $this->getInstallPath($target)); $repo->removePackage($initial); if (!$repo->hasPackage($target)) { $repo->addPackage(clone $target); } } /** * {@inheritDoc} */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: '.$package); } $this->removeCode($package); $this->binaryInstaller->removeBinaries($package); $repo->removePackage($package); $downloadPath = $this->getPackageBasePath($package); if (strpos($package->getName(), '/')) { $packageVendorDir = dirname($downloadPath); if (is_dir($packageVendorDir) && $this->filesystem->isDirEmpty($packageVendorDir)) { Silencer::call('rmdir', $packageVendorDir); } } } /** * {@inheritDoc} */ public function getInstallPath(PackageInterface $package) { $this->initializeVendorDir(); $basePath = ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName(); $targetDir = $package->getTargetDir(); return $basePath . ($targetDir ? '/'.$targetDir : ''); } /** * Make sure binaries are installed for a given package. * * @param PackageInterface $package Package instance */ public function ensureBinariesPresence(PackageInterface $package) { $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), false); } /** * Returns the base path of the package without target-dir path * * It is used for BC as getInstallPath tends to be overridden by * installer plugins but not getPackageBasePath * * @param PackageInterface $package * @return string */ protected function getPackageBasePath(PackageInterface $package) { $installPath = $this->getInstallPath($package); $targetDir = $package->getTargetDir(); if ($targetDir) { return preg_replace('{/*'.str_replace('/', '/+', preg_quote($targetDir)).'/?$}', '', $installPath); } return $installPath; } protected function installCode(PackageInterface $package) { $downloadPath = $this->getInstallPath($package); $this->downloadManager->download($package, $downloadPath); } protected function updateCode(PackageInterface $initial, PackageInterface $target) { $initialDownloadPath = $this->getInstallPath($initial); $targetDownloadPath = $this->getInstallPath($target); if ($targetDownloadPath !== $initialDownloadPath) { // if the target and initial dirs intersect, we force a remove + install // to avoid the rename wiping the target dir as part of the initial dir cleanup if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath ) { $this->removeCode($initial); $this->installCode($target); return; } $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); } $this->downloadManager->update($initial, $target, $targetDownloadPath); } protected function removeCode(PackageInterface $package) { $downloadPath = $this->getPackageBasePath($package); $this->downloadManager->remove($package, $downloadPath); } protected function initializeVendorDir() { $this->filesystem->ensureDirectoryExists($this->vendorDir); $this->vendorDir = realpath($this->vendorDir); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Console; use Symfony\Component\Console\Formatter\OutputFormatter; /** * @author Jordi Boggiano */ class HtmlOutputFormatter extends OutputFormatter { private static $availableForegroundColors = array( 30 => 'black', 31 => 'red', 32 => 'green', 33 => 'yellow', 34 => 'blue', 35 => 'magenta', 36 => 'cyan', 37 => 'white', ); private static $availableBackgroundColors = array( 40 => 'black', 41 => 'red', 42 => 'green', 43 => 'yellow', 44 => 'blue', 45 => 'magenta', 46 => 'cyan', 47 => 'white', ); private static $availableOptions = array( 1 => 'bold', 4 => 'underscore', //5 => 'blink', //7 => 'reverse', //8 => 'conceal' ); /** * @param array $styles Array of "name => FormatterStyle" instances */ public function __construct(array $styles = array()) { parent::__construct(true, $styles); } public function format($message) { $formatted = parent::format($message); $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)'; return preg_replace_callback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", array($this, 'formatHtml'), $formatted); } private function formatHtml($matches) { $out = ''.$matches[2].''; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Console; use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Command; use Composer\Composer; use Composer\Factory; use Composer\IO\IOInterface; use Composer\IO\ConsoleIO; use Composer\Json\JsonValidationException; use Composer\Util\ErrorHandler; use Composer\EventDispatcher\ScriptExecutionException; use Composer\Exception\NoSslException; /** * The console application that handles the commands * * @author Ryan Weaver * @author Jordi Boggiano * @author François Pluchino */ class Application extends BaseApplication { /** * @var Composer */ protected $composer; /** * @var IOInterface */ protected $io; private static $logo = ' ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ '; private $hasPluginCommands = false; private $disablePluginsByDefault = false; public function __construct() { static $shutdownRegistered = false; if (function_exists('ini_set') && extension_loaded('xdebug')) { ini_set('xdebug.show_exception_trace', false); ini_set('xdebug.scream', false); } if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { date_default_timezone_set(Silencer::call('date_default_timezone_get')); } if (!$shutdownRegistered) { $shutdownRegistered = true; register_shutdown_function(function () { $lastError = error_get_last(); if ($lastError && $lastError['message'] && (strpos($lastError['message'], 'Allowed memory') !== false /*Zend PHP out of memory error*/ || strpos($lastError['message'], 'exceeded memory') !== false /*HHVM out of memory errors*/)) { echo "\n". 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.'; } }); } parent::__construct('Composer', Composer::VERSION); } /** * {@inheritDoc} */ public function run(InputInterface $input = null, OutputInterface $output = null) { if (null === $output) { $output = Factory::createOutput(); } return parent::run($input, $output); } /** * {@inheritDoc} */ public function doRun(InputInterface $input, OutputInterface $output) { $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); $io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); ErrorHandler::register($io); // switch working dir if ($newWorkDir = $this->getNewWorkingDir($input)) { $oldWorkingDir = getcwd(); chdir($newWorkDir); $io->writeError('Changed CWD to ' . getcwd(), true, IOInterface::DEBUG); } // determine command name to be executed without including plugin commands $commandName = ''; if ($name = $this->getCommandName($input)) { try { $commandName = $this->find($name)->getName(); } catch (\InvalidArgumentException $e) { } } // prompt user for dir change if no composer.json is present in current dir if ($io->isInteractive() && !$newWorkDir && !in_array($commandName, array('', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project'), true) && !file_exists(Factory::getComposerFile())) { $dir = dirname(getcwd()); $home = realpath(getenv('HOME') ?: getenv('USERPROFILE') ?: '/'); // abort when we reach the home dir or top of the filesystem while (dirname($dir) !== $dir && $dir !== $home) { if (file_exists($dir.'/'.Factory::getComposerFile())) { if ($io->askConfirmation('No composer.json in current directory, do you want to use the one at '.$dir.'? [Y,n]? ', true)) { $oldWorkingDir = getcwd(); chdir($dir); } break; } $dir = dirname($dir); } } if (!$this->disablePluginsByDefault && !$this->hasPluginCommands && 'global' !== $commandName) { try { foreach ($this->getPluginCommands() as $command) { if ($this->has($command->getName())) { $io->writeError('Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped'); } else { $this->add($command); } } } catch (NoSslException $e) { // suppress these as they are not relevant at this point } $this->hasPluginCommands = true; } // determine command name to be executed incl plugin commands, and check if it's a proxy command $isProxyCommand = false; if ($name = $this->getCommandName($input)) { try { $command = $this->find($name); $commandName = $command->getName(); $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand()); } catch (\InvalidArgumentException $e) { } } if (!$isProxyCommand) { $io->writeError(sprintf( 'Running %s (%s) with %s on %s', Composer::VERSION, Composer::RELEASE_DATE, defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION, function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS' ), true, IOInterface::DEBUG); if (PHP_VERSION_ID < 50302) { $io->writeError('Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.'); } if (extension_loaded('xdebug') && !getenv('COMPOSER_DISABLE_XDEBUG_WARN')) { $io->writeError('You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug'); } if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) { $io->writeError(sprintf('Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); } if (getenv('COMPOSER_NO_INTERACTION')) { $input->setInteractive(false); } if (!Platform::isWindows() && function_exists('exec') && !getenv('COMPOSER_ALLOW_SUPERUSER')) { if (function_exists('posix_getuid') && posix_getuid() === 0) { if ($commandName !== 'self-update' && $commandName !== 'selfupdate') { $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); } if ($uid = (int) getenv('SUDO_UID')) { // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on // ref. https://github.com/composer/composer/issues/5119 Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1"); } } // Silently clobber any remaining sudo leases on the current user as well to avoid privilege escalations Silencer::call('exec', 'sudo -K > /dev/null 2>&1'); } // Check system temp folder for usability as it can cause weird runtime issues otherwise Silencer::call(function () use ($io) { $tempfile = sys_get_temp_dir() . '/temp-' . md5(microtime()); if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) == __FILE__) && unlink($tempfile) && !file_exists($tempfile))) { $io->writeError(sprintf('PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini', sys_get_temp_dir())); } }); // add non-standard scripts as own commands $file = Factory::getComposerFile(); if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) { if (isset($composer['scripts']) && is_array($composer['scripts'])) { foreach ($composer['scripts'] as $script => $dummy) { if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { if ($this->has($script)) { $io->writeError('A script named '.$script.' would override a Composer command and has been skipped'); } else { $this->add(new Command\ScriptAliasCommand($script)); } } } } } } try { if ($input->hasParameterOption('--profile')) { $startTime = microtime(true); $this->io->enableDebugging($startTime); } $result = parent::doRun($input, $output); if (isset($oldWorkingDir)) { chdir($oldWorkingDir); } if (isset($startTime)) { $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MB), time: '.round(microtime(true) - $startTime, 2).'s'); } restore_error_handler(); return $result; } catch (ScriptExecutionException $e) { return $e->getCode(); } catch (\Exception $e) { $this->hintCommonErrors($e); restore_error_handler(); throw $e; } } /** * @param InputInterface $input * @throws \RuntimeException * @return string */ private function getNewWorkingDir(InputInterface $input) { $workingDir = $input->getParameterOption(array('--working-dir', '-d')); if (false !== $workingDir && !is_dir($workingDir)) { throw new \RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.'); } return $workingDir; } /** * {@inheritDoc} */ private function hintCommonErrors($exception) { $io = $this->getIO(); Silencer::suppress(); try { $composer = $this->getComposer(false, true); if ($composer) { $config = $composer->getConfig(); $minSpaceFree = 1024 * 1024; if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) ) { $io->writeError('The disk hosting '.$dir.' is full, this may be the cause of the following exception', true, IOInterface::QUIET); } } } catch (\Exception $e) { } Silencer::restore(); if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', true, IOInterface::QUIET); } if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { $io->writeError('The following exception is caused by a lack of memory or swap, or not having swap configured', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details', true, IOInterface::QUIET); } } /** * @param bool $required * @param bool|null $disablePlugins * @throws JsonValidationException * @return \Composer\Composer */ public function getComposer($required = true, $disablePlugins = null) { if (null === $disablePlugins) { $disablePlugins = $this->disablePluginsByDefault; } if (null === $this->composer) { try { $this->composer = Factory::create($this->io, null, $disablePlugins); } catch (\InvalidArgumentException $e) { if ($required) { $this->io->writeError($e->getMessage()); exit(1); } } catch (JsonValidationException $e) { $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); $message = $e->getMessage() . ':' . PHP_EOL . $errors; throw new JsonValidationException($message); } } return $this->composer; } /** * Removes the cached composer instance */ public function resetComposer() { $this->composer = null; } /** * @return IOInterface */ public function getIO() { return $this->io; } public function getHelp() { return self::$logo . parent::getHelp(); } /** * Initializes all the composer commands. */ protected function getDefaultCommands() { $commands = array_merge(parent::getDefaultCommands(), array( new Command\AboutCommand(), new Command\ConfigCommand(), new Command\DependsCommand(), new Command\ProhibitsCommand(), new Command\InitCommand(), new Command\InstallCommand(), new Command\CreateProjectCommand(), new Command\UpdateCommand(), new Command\SearchCommand(), new Command\ValidateCommand(), new Command\ShowCommand(), new Command\SuggestsCommand(), new Command\RequireCommand(), new Command\DumpAutoloadCommand(), new Command\StatusCommand(), new Command\ArchiveCommand(), new Command\DiagnoseCommand(), new Command\RunScriptCommand(), new Command\LicensesCommand(), new Command\GlobalCommand(), new Command\ClearCacheCommand(), new Command\RemoveCommand(), new Command\HomeCommand(), new Command\ExecCommand(), new Command\OutdatedCommand(), )); if ('phar:' === substr(__FILE__, 0, 5)) { $commands[] = new Command\SelfUpdateCommand(); } return $commands; } /** * {@inheritDoc} */ public function getLongVersion() { if (Composer::BRANCH_ALIAS_VERSION) { return sprintf( '%s version %s (%s) %s', $this->getName(), Composer::BRANCH_ALIAS_VERSION, $this->getVersion(), Composer::RELEASE_DATE ); } return parent::getLongVersion() . ' ' . Composer::RELEASE_DATE; } /** * {@inheritDoc} */ protected function getDefaultInputDefinition() { $definition = parent::getDefaultInputDefinition(); $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); return $definition; } private function getPluginCommands() { $commands = array(); $composer = $this->getComposer(false, false); if (null === $composer) { $composer = Factory::createGlobal($this->io, false); } if (null !== $composer) { $pm = $composer->getPluginManager(); foreach ($pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', array('composer' => $composer, 'io' => $this->io)) as $capability) { $newCommands = $capability->getCommands(); if (!is_array($newCommands)) { throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' failed to return an array from getCommands'); } foreach ($newCommands as $command) { if (!$command instanceof Command\BaseCommand) { throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' returned an invalid value, we expected an array of Composer\Command\BaseCommand objects'); } } $commands = array_merge($commands, $newCommands); } } return $commands; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Json\JsonFile; use Composer\Spdx\SpdxLicenses; use Composer\CaBundle\CaBundle; use Symfony\Component\Finder\Finder; use Symfony\Component\Process\Process; use Seld\PharUtils\Timestamps; /** * The Compiler class compiles composer into a phar * * @author Fabien Potencier * @author Jordi Boggiano */ class Compiler { private $version; private $branchAliasVersion = ''; private $versionDate; /** * Compiles composer into a single phar file * * @param string $pharFile The full path to the file to create * @throws \RuntimeException */ public function compile($pharFile = 'composer.phar') { if (file_exists($pharFile)) { unlink($pharFile); } $process = new Process('git log --pretty="%H" -n1 HEAD', __DIR__); if ($process->run() != 0) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->version = trim($process->getOutput()); $process = new Process('git log -n1 --pretty=%ci HEAD', __DIR__); if ($process->run() != 0) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->versionDate = new \DateTime(trim($process->getOutput())); $this->versionDate->setTimezone(new \DateTimeZone('UTC')); $process = new Process('git describe --tags --exact-match HEAD'); if ($process->run() == 0) { $this->version = trim($process->getOutput()); } else { // get branch-alias defined in composer.json for dev-master (if any) $localConfig = __DIR__.'/../../composer.json'; $file = new JsonFile($localConfig); $localConfig = $file->read(); if (isset($localConfig['extra']['branch-alias']['dev-master'])) { $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-master']; } } $phar = new \Phar($pharFile, 0, 'composer.phar'); $phar->setSignatureAlgorithm(\Phar::SHA1); $phar->startBuffering(); $finderSort = function ($a, $b) { return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/')); }; $finder = new Finder(); $finder->files() ->ignoreVCS(true) ->name('*.php') ->notName('Compiler.php') ->notName('ClassLoader.php') ->in(__DIR__.'/..') ->sort($finderSort) ; foreach ($finder as $file) { $this->addFile($phar, $file); } $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false); $finder = new Finder(); $finder->files() ->name('*.json') ->in(__DIR__.'/../../res') ->in(SpdxLicenses::getResourcesDir()) ->sort($finderSort) ; foreach ($finder as $file) { $this->addFile($phar, $file, false); } $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/seld/cli-prompt/res/hiddeninput.exe'), false); $finder = new Finder(); $finder->files() ->ignoreVCS(true) ->name('*.php') ->name('LICENSE') ->exclude('Tests') ->exclude('tests') ->exclude('docs') ->in(__DIR__.'/../../vendor/symfony/') ->in(__DIR__.'/../../vendor/seld/jsonlint/') ->in(__DIR__.'/../../vendor/seld/cli-prompt/') ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/') ->in(__DIR__.'/../../vendor/composer/spdx-licenses/') ->in(__DIR__.'/../../vendor/composer/semver/') ->in(__DIR__.'/../../vendor/composer/ca-bundle/') ->in(__DIR__.'/../../vendor/psr/') ->sort($finderSort) ; foreach ($finder as $file) { $this->addFile($phar, $file); } $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/autoload.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_namespaces.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_psr4.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_classmap.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_files.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_real.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_static.php')); if (file_exists(__DIR__.'/../../vendor/composer/include_paths.php')) { $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/include_paths.php')); } $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/ClassLoader.php')); $this->addFile($phar, new \SplFileInfo(CaBundle::getBundledCaBundlePath()), false); $this->addComposerBin($phar); // Stubs $phar->setStub($this->getStub()); $phar->stopBuffering(); // disabled for interoperability with systems without gzip ext // $phar->compressFiles(\Phar::GZ); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false); unset($phar); // re-sign the phar with reproducible timestamp / signature $util = new Timestamps($pharFile); $util->updateTimestamps($this->versionDate); $util->save($pharFile, \Phar::SHA1); } /** * @param \SplFileInfo $file * @return string */ private function getRelativeFilePath($file) { $realPath = $file->getRealPath(); $pathPrefix = dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR; $pos = strpos($realPath, $pathPrefix); $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; return strtr($relativePath, '\\', '/'); } private function addFile($phar, $file, $strip = true) { $path = $this->getRelativeFilePath($file); $content = file_get_contents($file); if ($strip) { $content = $this->stripWhitespace($content); } elseif ('LICENSE' === basename($file)) { $content = "\n".$content."\n"; } if ($path === 'src/Composer/Composer.php') { $content = str_replace('@package_version@', $this->version, $content); $content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content); $content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content); } $phar->addFromString($path, $content); } private function addComposerBin($phar) { $content = file_get_contents(__DIR__.'/../../bin/composer'); $content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content); $phar->addFromString('bin/composer', $content); } /** * Removes whitespace from a PHP source string while preserving line numbers. * * @param string $source A PHP string * @return string The PHP string with the whitespace removed */ private function stripWhitespace($source) { if (!function_exists('token_get_all')) { return $source; } $output = ''; foreach (token_get_all($source) as $token) { if (is_string($token)) { $output .= $token; } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { $output .= str_repeat("\n", substr_count($token[1], "\n")); } elseif (T_WHITESPACE === $token[0]) { // reduce wide spaces $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); // normalize newlines to \n $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); // trim leading spaces $whitespace = preg_replace('{\n +}', "\n", $whitespace); $output .= $whitespace; } else { $output .= $token[1]; } } return $output; } private function getStub() { $stub = <<<'EOF' #!/usr/bin/env php * Jordi Boggiano * * For the full copyright and license information, please view * the license that is located at the bottom of this file. */ // Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 if (extension_loaded('apc') && ini_get('apc.enable_cli') && ini_get('apc.cache_by_default')) { if (version_compare(phpversion('apc'), '3.0.12', '>=')) { ini_set('apc.cache_by_default', 0); } else { fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL); fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL); } } Phar::mapPhar('composer.phar'); EOF; // add warning once the phar is older than 60 days if (preg_match('{^[a-f0-9]+$}', $this->version)) { $warningTime = $this->versionDate->format('U') + 60 * 86400; $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n"; } return $stub . <<<'EOF' require 'phar://composer.phar/bin/composer'; __HALT_COMPILER(); EOF; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Installable repository interface. * * Just used to tag installed repositories so the base classes can act differently on Alias packages * * @author Jordi Boggiano */ interface InstalledRepositoryInterface extends WritableRepositoryInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\Util\Git as GitUtil; use Composer\IO\IOInterface; use Composer\Cache; use Composer\Config; /** * @author Jordi Boggiano */ class GitDriver extends VcsDriver { protected $cache; protected $tags; protected $branches; protected $rootIdentifier; protected $repoDir; protected $infoCache = array(); /** * {@inheritDoc} */ public function initialize() { if (Filesystem::isLocalPath($this->url)) { $this->url = preg_replace('{[\\/]\.git/?$}', '', $this->url); $this->repoDir = $this->url; $cacheUrl = realpath($this->url); } else { $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; GitUtil::cleanEnv(); $fs = new Filesystem(); $fs->ensureDirectoryExists(dirname($this->repoDir)); if (!is_writable(dirname($this->repoDir))) { throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.'); } if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) { throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); if (!$gitUtil->syncMirror($this->url, $this->repoDir)) { $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated'); } $cacheUrl = $this->url; } $this->getTags(); $this->getBranches(); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl)); } /** * {@inheritDoc} */ public function getRootIdentifier() { if (null === $this->rootIdentifier) { $this->rootIdentifier = 'master'; // select currently checked out branch if master is not available $this->process->execute('git branch --no-color', $output, $this->repoDir); $branches = $this->process->splitLines($output); if (!in_array('* master', $branches)) { foreach ($branches as $branch) { if ($branch && preg_match('{^\* +(\S+)}', $branch, $match)) { $this->rootIdentifier = $match[1]; break; } } } } return $this->rootIdentifier; } /** * {@inheritDoc} */ public function getUrl() { return $this->url; } /** * {@inheritDoc} */ public function getSource($identifier) { return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier); } /** * {@inheritDoc} */ public function getDist($identifier) { return null; } /** * {@inheritdoc} */ public function getFileContent($file, $identifier) { $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir); if (!trim($content)) { return null; } return $content; } /** * {@inheritdoc} */ public function getChangeDate($identifier) { $this->process->execute(sprintf( 'git log -1 --format=%%at %s', ProcessExecutor::escape($identifier) ), $output, $this->repoDir); return new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); } /** * {@inheritDoc} */ public function getTags() { if (null === $this->tags) { $this->tags = array(); $this->process->execute('git show-ref --tags --dereference', $output, $this->repoDir); foreach ($output = $this->process->splitLines($output) as $tag) { if ($tag && preg_match('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { $this->tags[$match[2]] = $match[1]; } } } return $this->tags; } /** * {@inheritDoc} */ public function getBranches() { if (null === $this->branches) { $branches = array(); $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) { $branches[$match[1]] = $match[2]; } } } $this->branches = $branches; } return $this->branches; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if (preg_match('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) { return true; } // local filesystem if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!is_dir($url)) { return false; } $process = new ProcessExecutor($io); // check whether there is a git repo in that path if ($process->execute('git tag', $output, $url) === 0) { return true; } } if (!$deep) { return false; } $process = new ProcessExecutor($io); if ($process->execute('git ls-remote --heads ' . ProcessExecutor::escape($url), $output) === 0) { return true; } return false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Cache; use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\Util\Bitbucket; abstract class BitbucketDriver extends VcsDriver { /** @var Cache */ protected $cache; protected $owner; protected $repository; protected $hasIssues; protected $rootIdentifier; protected $tags; protected $branches; protected $infoCache = array(); protected $branchesUrl = ''; protected $tagsUrl = ''; protected $homeUrl = ''; protected $website = ''; protected $cloneHttpsUrl = ''; /** * @var VcsDriver */ protected $fallbackDriver; /** @var string|null if set either git or hg */ protected $vcsType; /** * {@inheritDoc} */ public function initialize() { preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#', $this->url, $match); $this->owner = $match[1]; $this->repository = $match[2]; $this->originUrl = 'bitbucket.org'; $this->cache = new Cache( $this->io, implode('/', array( $this->config->get('cache-repo-dir'), $this->originUrl, $this->owner, $this->repository, )) ); } /** * {@inheritDoc} */ public function getUrl() { if ($this->fallbackDriver) { return $this->fallbackDriver->getUrl(); } return $this->cloneHttpsUrl; } /** * Attempts to fetch the repository data via the BitBucket API and * sets some parameters which are used in other methods * * @return bool */ protected function getRepoData() { $resource = sprintf( 'https://api.bitbucket.org/2.0/repositories/%s/%s?%s', $this->owner, $this->repository, http_build_query( array('fields' => '-project,-owner'), null, '&' ) ); $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); if ($this->fallbackDriver) { return false; } $this->parseCloneUrls($repoData['links']['clone']); $this->hasIssues = !empty($repoData['has_issues']); $this->branchesUrl = $repoData['links']['branches']['href']; $this->tagsUrl = $repoData['links']['tags']['href']; $this->homeUrl = $repoData['links']['html']['href']; $this->website = $repoData['website']; $this->vcsType = $repoData['scm']; return true; } /** * {@inheritDoc} */ public function getComposerInformation($identifier) { if ($this->fallbackDriver) { return $this->fallbackDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { return $this->infoCache[$identifier] = JsonFile::parseJson($res); } $composer = $this->getBaseComposerInformation($identifier); // specials for bitbucket if (!isset($composer['support']['source'])) { $label = array_search( $identifier, $this->getTags() ) ?: array_search( $identifier, $this->getBranches() ) ?: $identifier; if (array_key_exists($label, $tags = $this->getTags())) { $hash = $tags[$label]; } elseif (array_key_exists($label, $branches = $this->getBranches())) { $hash = $branches[$label]; } if (! isset($hash)) { $composer['support']['source'] = sprintf( 'https://%s/%s/%s/src', $this->originUrl, $this->owner, $this->repository ); } else { $composer['support']['source'] = sprintf( 'https://%s/%s/%s/src/%s/?at=%s', $this->originUrl, $this->owner, $this->repository, $hash, $label ); } } if (!isset($composer['support']['issues']) && $this->hasIssues) { $composer['support']['issues'] = sprintf( 'https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository ); } if (!isset($composer['homepage'])) { $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; } $this->infoCache[$identifier] = $composer; if ($this->shouldCache($identifier)) { $this->cache->write($identifier, json_encode($composer)); } } return $this->infoCache[$identifier]; } /** * {@inheritdoc} */ public function getFileContent($file, $identifier) { if ($this->fallbackDriver) { return $this->fallbackDriver->getFileContent($file, $identifier); } $resource = sprintf( 'https://api.bitbucket.org/1.0/repositories/%s/%s/raw/%s/%s', $this->owner, $this->repository, $identifier, $file ); return $this->getContentsWithOAuthCredentials($resource); } /** * {@inheritdoc} */ public function getChangeDate($identifier) { if ($this->fallbackDriver) { return $this->fallbackDriver->getChangeDate($identifier); } $resource = sprintf( 'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', $this->owner, $this->repository, $identifier ); $commit = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); return new \DateTime($commit['date']); } /** * {@inheritDoc} */ public function getSource($identifier) { if ($this->fallbackDriver) { return $this->fallbackDriver->getSource($identifier); } return array('type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier); } /** * {@inheritDoc} */ public function getDist($identifier) { if ($this->fallbackDriver) { return $this->fallbackDriver->getDist($identifier); } $url = sprintf( 'https://bitbucket.org/%s/%s/get/%s.zip', $this->owner, $this->repository, $identifier ); return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); } /** * {@inheritDoc} */ public function getTags() { if ($this->fallbackDriver) { return $this->fallbackDriver->getTags(); } if (null === $this->tags) { $this->tags = array(); $resource = sprintf( '%s?%s', $this->tagsUrl, http_build_query( array( 'pagelen' => 100, 'fields' => 'values.name,values.target.hash,next', 'sort' => '-target.date', ), null, '&' ) ); $hasNext = true; while ($hasNext) { $tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); foreach ($tagsData['values'] as $data) { $this->tags[$data['name']] = $data['target']['hash']; } if (empty($tagsData['next'])) { $hasNext = false; } else { $resource = $tagsData['next']; } } if ($this->vcsType === 'hg') { unset($this->tags['tip']); } } return $this->tags; } /** * {@inheritDoc} */ public function getBranches() { if ($this->fallbackDriver) { return $this->fallbackDriver->getBranches(); } if (null === $this->branches) { $this->branches = array(); $resource = sprintf( '%s?%s', $this->branchesUrl, http_build_query( array( 'pagelen' => 100, 'fields' => 'values.name,values.target.hash,values.heads,next', 'sort' => '-target.date', ), null, '&' ) ); $hasNext = true; while ($hasNext) { $branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); foreach ($branchData['values'] as $data) { // skip headless branches which seem to be deleted branches that bitbucket nevertheless returns in the API if ($this->vcsType === 'hg' && empty($data['heads'])) { continue; } $this->branches[$data['name']] = $data['target']['hash']; } if (empty($branchData['next'])) { $hasNext = false; } else { $resource = $branchData['next']; } } } return $this->branches; } /** * Get the remote content. * * @param string $url The URL of content * @param bool $fetchingRepoData * * @return mixed The result */ protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false) { try { return parent::getContents($url); } catch (TransportException $e) { $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem); if (403 === $e->getCode() || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) { if (!$this->io->hasAuthentication($this->originUrl) && $bitbucketUtil->authorizeOAuth($this->originUrl) ) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { return $this->attemptCloneFallback(); } } throw $e; } } /** * Generate an SSH URL * * @return string */ abstract protected function generateSshUrl(); protected function attemptCloneFallback() { try { $this->setupFallbackDriver($this->generateSshUrl()); } catch (\RuntimeException $e) { $this->fallbackDriver = null; $this->io->writeError( 'Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode' . ' so that you can enter your Bitbucket OAuth consumer credentials' ); throw $e; } } /** * @param string $url * @return void */ abstract protected function setupFallbackDriver($url); /** * @param array $cloneLinks * @return void */ protected function parseCloneUrls(array $cloneLinks) { foreach ($cloneLinks as $cloneLink) { if ($cloneLink['name'] === 'https') { // Format: https://(user@)bitbucket.org/{user}/{repo} // Strip username from URL (only present in clone URL's for private repositories) $this->cloneHttpsUrl = preg_replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']); } } } /** * @return array|null */ protected function getMainBranchData() { $resource = sprintf( 'https://api.bitbucket.org/1.0/repositories/%s/%s/main-branch', $this->owner, $this->repository ); return JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\IO\IOInterface; /** * @author Jordi Boggiano */ interface VcsDriverInterface { /** * Initializes the driver (git clone, svn checkout, fetch info etc) */ public function initialize(); /** * Return the composer.json file information * * @param string $identifier Any identifier to a specific branch/tag/commit * @return array containing all infos from the composer.json file */ public function getComposerInformation($identifier); /** * Return the content of $file or null if the file does not exist. * * @param string $file * @param string $identifier * @return string */ public function getFileContent($file, $identifier); /** * Get the changedate for $identifier. * * @param string $identifier * @return \DateTime */ public function getChangeDate($identifier); /** * Return the root identifier (trunk, master, default/tip ..) * * @return string Identifier */ public function getRootIdentifier(); /** * Return list of branches in the repository * * @return array Branch names as keys, identifiers as values */ public function getBranches(); /** * Return list of tags in the repository * * @return array Tag names as keys, identifiers as values */ public function getTags(); /** * @param string $identifier Any identifier to a specific branch/tag/commit * @return array With type, url reference and shasum keys. */ public function getDist($identifier); /** * @param string $identifier Any identifier to a specific branch/tag/commit * @return array With type, url and reference keys. */ public function getSource($identifier); /** * Return the URL of the repository * * @return string */ public function getUrl(); /** * Return true if the repository has a composer file for a given identifier, * false otherwise. * * @param string $identifier Any identifier to a specific branch/tag/commit * @return bool Whether the repository has a composer file for a given identifier. */ public function hasComposerFile($identifier); /** * Performs any cleanup necessary as the driver is not longer needed */ public function cleanup(); /** * Checks if this driver can handle a given url * * @param IOInterface $io IO instance * @param Config $config current $config * @param string $url URL to validate/check * @param bool $deep unless true, only shallow checks (url matching typically) should be done * @return bool */ public static function supports(IOInterface $io, Config $config, $url, $deep = false); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Cache; use Composer\Config; use Composer\Json\JsonFile; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\Util\Svn as SvnUtil; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; /** * @author Jordi Boggiano * @author Till Klampaeckel */ class SvnDriver extends VcsDriver { /** * @var Cache */ protected $cache; protected $baseUrl; protected $tags; protected $branches; protected $rootIdentifier; protected $infoCache = array(); protected $trunkPath = 'trunk'; protected $branchesPath = 'branches'; protected $tagsPath = 'tags'; protected $packagePath = ''; protected $cacheCredentials = true; /** * @var \Composer\Util\Svn */ private $util; /** * {@inheritDoc} */ public function initialize() { $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/'); SvnUtil::cleanEnv(); if (isset($this->repoConfig['trunk-path'])) { $this->trunkPath = $this->repoConfig['trunk-path']; } if (isset($this->repoConfig['branches-path'])) { $this->branchesPath = $this->repoConfig['branches-path']; } if (isset($this->repoConfig['tags-path'])) { $this->tagsPath = $this->repoConfig['tags-path']; } if (array_key_exists('svn-cache-credentials', $this->repoConfig)) { $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials']; } if (isset($this->repoConfig['package-path'])) { $this->packagePath = '/' . trim($this->repoConfig['package-path'], '/'); } if (false !== ($pos = strrpos($this->url, '/' . $this->trunkPath))) { $this->baseUrl = substr($this->url, 0, $pos); } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->baseUrl)); $this->getBranches(); $this->getTags(); } /** * {@inheritDoc} */ public function getRootIdentifier() { return $this->rootIdentifier ?: $this->trunkPath; } /** * {@inheritDoc} */ public function getUrl() { return $this->url; } /** * {@inheritDoc} */ public function getSource($identifier) { return array('type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier); } /** * {@inheritDoc} */ public function getDist($identifier) { return null; } /** * {@inheritdoc} */ public function getComposerInformation($identifier) { if (!isset($this->infoCache[$identifier])) { if ($res = $this->cache->read($identifier.'.json')) { return $this->infoCache[$identifier] = JsonFile::parseJson($res); } $composer = $this->getBaseComposerInformation($identifier); $this->cache->write($identifier.'.json', json_encode($composer)); $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } /** * @param string $file * @param string $identifier */ public function getFileContent($file, $identifier) { $identifier = '/' . trim($identifier, '/') . '/'; preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match); if (!empty($match[2])) { $path = $match[1]; $rev = $match[2]; } else { $path = $identifier; $rev = ''; } try { $resource = $path.$file; $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev); if (!trim($output)) { return null; } } catch (\RuntimeException $e) { throw new TransportException($e->getMessage()); } return $output; } /** * {@inheritdoc} */ public function getChangeDate($identifier) { $identifier = '/' . trim($identifier, '/') . '/'; preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match); if (!empty($match[2])) { $path = $match[1]; $rev = $match[2]; } else { $path = $identifier; $rev = ''; } $output = $this->execute('svn info', $this->baseUrl . $path . $rev); foreach ($this->process->splitLines($output) as $line) { if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) { return new \DateTime($match[1], new \DateTimeZone('UTC')); } } return null; } /** * {@inheritDoc} */ public function getTags() { if (null === $this->tags) { $this->tags = array(); if ($this->tagsPath !== false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->tagsPath); if ($output) { foreach ($this->process->splitLines($output) as $line) { $line = trim($line); if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { if (isset($match[1]) && isset($match[2]) && $match[2] !== './') { $this->tags[rtrim($match[2], '/')] = $this->buildIdentifier( '/' . $this->tagsPath . '/' . $match[2], $match[1] ); } } } } } } return $this->tags; } /** * {@inheritDoc} */ public function getBranches() { if (null === $this->branches) { $this->branches = array(); if (false === $this->trunkPath) { $trunkParent = $this->baseUrl . '/'; } else { $trunkParent = $this->baseUrl . '/' . $this->trunkPath; } $output = $this->execute('svn ls --verbose', $trunkParent); if ($output) { foreach ($this->process->splitLines($output) as $line) { $line = trim($line); if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { if (isset($match[1]) && isset($match[2]) && $match[2] === './') { $this->branches['trunk'] = $this->buildIdentifier( '/' . $this->trunkPath, $match[1] ); $this->rootIdentifier = $this->branches['trunk']; break; } } } } unset($output); if ($this->branchesPath !== false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->branchesPath); if ($output) { foreach ($this->process->splitLines(trim($output)) as $line) { $line = trim($line); if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { if (isset($match[1]) && isset($match[2]) && $match[2] !== './') { $this->branches[rtrim($match[2], '/')] = $this->buildIdentifier( '/' . $this->branchesPath . '/' . $match[2], $match[1] ); } } } } } } return $this->branches; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { $url = self::normalizeUrl($url); if (preg_match('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) { return true; } // proceed with deep check for local urls since they are fast to process if (!$deep && !Filesystem::isLocalPath($url)) { return false; } $processExecutor = new ProcessExecutor(); $exit = $processExecutor->execute( "svn info --non-interactive {$url}", $ignoredOutput ); if ($exit === 0) { // This is definitely a Subversion repository. return true; } // Subversion client 1.7 and older if (false !== stripos($processExecutor->getErrorOutput(), 'authorization failed:')) { // This is likely a remote Subversion repository that requires // authentication. We will handle actual authentication later. return true; } // Subversion client 1.8 and newer if (false !== stripos($processExecutor->getErrorOutput(), 'Authentication failed')) { // This is likely a remote Subversion or newer repository that requires // authentication. We will handle actual authentication later. return true; } return false; } /** * An absolute path (leading '/') is converted to a file:// url. * * @param string $url * * @return string */ protected static function normalizeUrl($url) { $fs = new Filesystem(); if ($fs->isAbsolutePath($url)) { return 'file://' . strtr($url, '\\', '/'); } return $url; } /** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command The svn command to run. * @param string $url The SVN URL. * @throws \RuntimeException * @return string */ protected function execute($command, $url) { if (null === $this->util) { $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process); $this->util->setCacheCredentials($this->cacheCredentials); } try { return $this->util->execute($command, $url); } catch (\RuntimeException $e) { if (0 !== $this->process->execute('svn --version', $ignoredOutput)) { throw new \RuntimeException('Failed to load '.$this->url.', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } throw new \RuntimeException( 'Repository '.$this->url.' could not be processed, '.$e->getMessage() ); } } /** * Build the identifier respecting "package-path" config option * * @param string $baseDir The path to trunk/branch/tag * @param int $revision The revision mark to add to identifier * * @return string */ protected function buildIdentifier($baseDir, $revision) { return rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Cache; use Composer\Downloader\TransportException; use Composer\Config; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\Util\Filesystem; /** * A driver implementation for driver with authentication interaction. * * @author François Pluchino */ abstract class VcsDriver implements VcsDriverInterface { /** @var string */ protected $url; /** @var string */ protected $originUrl; /** @var array */ protected $repoConfig; /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var RemoteFilesystem */ protected $remoteFilesystem; /** @var array */ protected $infoCache = array(); /** @var Cache */ protected $cache; /** * Constructor. * * @param array $repoConfig The repository configuration * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking */ final public function __construct(array $repoConfig, IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) { if (Filesystem::isLocalPath($repoConfig['url'])) { $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']); } $this->url = $repoConfig['url']; $this->originUrl = $repoConfig['url']; $this->repoConfig = $repoConfig; $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); } /** * Returns whether or not the given $identifier should be cached or not. * * @param string $identifier * @return bool */ protected function shouldCache($identifier) { return $this->cache && preg_match('{[a-f0-9]{40}}i', $identifier); } /** * {@inheritdoc} */ public function getComposerInformation($identifier) { if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { return $this->infoCache[$identifier] = JsonFile::parseJson($res); } $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, json_encode($composer)); } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } protected function getBaseComposerInformation($identifier) { $composerFileContent = $this->getFileContent('composer.json', $identifier); if (!$composerFileContent) { return null; } $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json'); if (empty($composer['time']) && $changeDate = $this->getChangeDate($identifier)) { $composer['time'] = $changeDate->format(DATE_RFC3339); } return $composer; } /** * {@inheritDoc} */ public function hasComposerFile($identifier) { try { return (bool) $this->getComposerInformation($identifier); } catch (TransportException $e) { } return false; } /** * Get the https or http protocol depending on SSL support. * * Call this only if you know that the server supports both. * * @return string The correct type of protocol */ protected function getScheme() { if (extension_loaded('openssl')) { return 'https'; } return 'http'; } /** * Get the remote content. * * @param string $url The URL of content * * @return mixed The result */ protected function getContents($url) { $options = isset($this->repoConfig['options']) ? $this->repoConfig['options'] : array(); return $this->remoteFilesystem->getContents($this->originUrl, $url, false, $options); } /** * {@inheritDoc} */ public function cleanup() { return; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\IO\IOInterface; use Composer\Util\ProcessExecutor; use Composer\Util\Perforce; /** * @author Matt Whittom */ class PerforceDriver extends VcsDriver { protected $depot; protected $branch; /** @var Perforce */ protected $perforce; /** * {@inheritDoc} */ public function initialize() { $this->depot = $this->repoConfig['depot']; $this->branch = ''; if (!empty($this->repoConfig['branch'])) { $this->branch = $this->repoConfig['branch']; } $this->initPerforce($this->repoConfig); $this->perforce->p4Login(); $this->perforce->checkStream(); $this->perforce->writeP4ClientSpec(); $this->perforce->connectClient(); return true; } private function initPerforce($repoConfig) { if (!empty($this->perforce)) { return; } $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot; $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); } /** * {@inheritdoc} */ public function getFileContent($file, $identifier) { return $this->perforce->getFileContent($file, $identifier); } /** * {@inheritdoc} */ public function getChangeDate($identifier) { return null; } /** * {@inheritDoc} */ public function getRootIdentifier() { return $this->branch; } /** * {@inheritDoc} */ public function getBranches() { $branches = $this->perforce->getBranches(); return $branches; } /** * {@inheritDoc} */ public function getTags() { $tags = $this->perforce->getTags(); return $tags; } /** * {@inheritDoc} */ public function getDist($identifier) { return null; } /** * {@inheritDoc} */ public function getSource($identifier) { $source = array( 'type' => 'perforce', 'url' => $this->repoConfig['url'], 'reference' => $identifier, 'p4user' => $this->perforce->getUser(), ); return $source; } /** * {@inheritDoc} */ public function getUrl() { return $this->url; } /** * {@inheritDoc} */ public function hasComposerFile($identifier) { $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier); $composerInfoIdentifier = $identifier; return !empty($composerInfo); } /** * {@inheritDoc} */ public function getContents($url) { return false; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if ($deep || preg_match('#\b(perforce|p4)\b#i', $url)) { return Perforce::checkServerExists($url, new ProcessExecutor($io)); } return false; } /** * {@inheritDoc} */ public function cleanup() { $this->perforce->cleanupClientSpec(); $this->perforce = null; } public function getDepot() { return $this->depot; } public function getBranch() { return $this->branch; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\Cache; use Composer\IO\IOInterface; use Composer\Util\GitHub; /** * @author Jordi Boggiano */ class GitHubDriver extends VcsDriver { protected $cache; protected $owner; protected $repository; protected $tags; protected $branches; protected $rootIdentifier; protected $repoData; protected $hasIssues; protected $infoCache = array(); protected $isPrivate = false; /** * Git Driver * * @var GitDriver */ protected $gitDriver; /** * {@inheritDoc} */ public function initialize() { preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match); $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = !empty($match[1]) ? $match[1] : $match[2]; if ($this->originUrl === 'www.github.com') { $this->originUrl = 'github.com'; } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); if (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api']) { $this->setupGitDriver($this->url); return; } $this->fetchRootIdentifier(); } public function getRepositoryUrl() { return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository; } /** * {@inheritDoc} */ public function getRootIdentifier() { if ($this->gitDriver) { return $this->gitDriver->getRootIdentifier(); } return $this->rootIdentifier; } /** * {@inheritDoc} */ public function getUrl() { if ($this->gitDriver) { return $this->gitDriver->getUrl(); } return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; } /** * {@inheritDoc} */ protected function getApiUrl() { if ('github.com' === $this->originUrl) { $apiUrl = 'api.github.com'; } else { $apiUrl = $this->originUrl . '/api/v3'; } return 'https://' . $apiUrl; } /** * {@inheritDoc} */ public function getSource($identifier) { if ($this->gitDriver) { return $this->gitDriver->getSource($identifier); } if ($this->isPrivate) { // Private GitHub repositories should be accessed using the // SSH version of the URL. $url = $this->generateSshUrl(); } else { $url = $this->getUrl(); } return array('type' => 'git', 'url' => $url, 'reference' => $identifier); } /** * {@inheritDoc} */ public function getDist($identifier) { $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier; return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); } /** * {@inheritDoc} */ public function getComposerInformation($identifier) { if ($this->gitDriver) { return $this->gitDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { return $this->infoCache[$identifier] = JsonFile::parseJson($res); } $composer = $this->getBaseComposerInformation($identifier); if ($composer) { // specials for github if (!isset($composer['support']['source'])) { $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label); } if (!isset($composer['support']['issues']) && $this->hasIssues) { $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); } } if ($this->shouldCache($identifier)) { $this->cache->write($identifier, json_encode($composer)); } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } /** * {@inheritdoc} */ public function getFileContent($file, $identifier) { if ($this->gitDriver) { return $this->gitDriver->getFileContent($file, $identifier); } $notFoundRetries = 2; while ($notFoundRetries) { try { $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); $resource = JsonFile::parseJson($this->getContents($resource)); if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) { throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); } return $content; } catch (TransportException $e) { if (404 !== $e->getCode()) { throw $e; } // TODO should be removed when possible // retry fetching if github returns a 404 since they happen randomly $notFoundRetries--; return null; } } return null; } /** * {@inheritdoc} */ public function getChangeDate($identifier) { if ($this->gitDriver) { return $this->gitDriver->getChangeDate($identifier); } $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier); $commit = JsonFile::parseJson($this->getContents($resource), $resource); return new \DateTime($commit['commit']['committer']['date']); } /** * {@inheritDoc} */ public function getTags() { if ($this->gitDriver) { return $this->gitDriver->getTags(); } if (null === $this->tags) { $this->tags = array(); $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100'; do { $tagsData = JsonFile::parseJson($this->getContents($resource), $resource); foreach ($tagsData as $tag) { $this->tags[$tag['name']] = $tag['commit']['sha']; } $resource = $this->getNextPage(); } while ($resource); } return $this->tags; } /** * {@inheritDoc} */ public function getBranches() { if ($this->gitDriver) { return $this->gitDriver->getBranches(); } if (null === $this->branches) { $this->branches = array(); $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100'; $branchBlacklist = array('gh-pages'); do { $branchData = JsonFile::parseJson($this->getContents($resource), $resource); foreach ($branchData as $branch) { $name = substr($branch['ref'], 11); if (!in_array($name, $branchBlacklist)) { $this->branches[$name] = $branch['object']['sha']; } } $resource = $this->getNextPage(); } while ($resource); } return $this->branches; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) { return false; } $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3]; if (!in_array(preg_replace('{^www\.}i', '', $originUrl), $config->get('github-domains'))) { return false; } if (!extension_loaded('openssl')) { $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } /** * Gives back the loaded /repos// result * * @return array|null */ public function getRepoData() { $this->fetchRootIdentifier(); return $this->repoData; } /** * Generate an SSH URL * * @return string */ protected function generateSshUrl() { return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; } /** * {@inheritDoc} */ protected function getContents($url, $fetchingRepoData = false) { try { return parent::getContents($url); } catch (TransportException $e) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem); switch ($e->getCode()) { case 401: case 404: // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 if (!$fetchingRepoData) { throw $e; } if ($gitHubUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive()) { return $this->attemptCloneFallback(); } $scopesIssued = array(); $scopesNeeded = array(); if ($headers = $e->getHeaders()) { if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-OAuth-Scopes')) { $scopesIssued = explode(' ', $scopes); } if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { $scopesNeeded = explode(' ', $scopes); } } $scopesFailed = array_diff($scopesNeeded, $scopesIssued); if (!$headers || count($scopesFailed)) { $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata ('.$this->url.')'); } return parent::getContents($url); case 403: if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { return $this->attemptCloneFallback(); } $rateLimited = false; foreach ($e->getHeaders() as $header) { if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) { $rateLimited = true; } } if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->isInteractive()) { $this->io->writeError('GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); throw $e; } $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit ('.$this->url.')'); return parent::getContents($url); } if ($rateLimited) { $rateLimit = $this->getRateLimit($e->getHeaders()); $this->io->writeError(sprintf( 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', $rateLimit['limit'], $rateLimit['reset'] )); } throw $e; default: throw $e; } } } /** * Extract ratelimit from response. * * @param array $headers Headers from Composer\Downloader\TransportException. * * @return array Associative array with the keys limit and reset. */ protected function getRateLimit(array $headers) { $rateLimit = array( 'limit' => '?', 'reset' => '?', ); foreach ($headers as $header) { $header = trim($header); if (false === strpos($header, 'X-RateLimit-')) { continue; } list($type, $value) = explode(':', $header, 2); switch ($type) { case 'X-RateLimit-Limit': $rateLimit['limit'] = (int) trim($value); break; case 'X-RateLimit-Reset': $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); break; } } return $rateLimit; } /** * Fetch root identifier from GitHub * * @throws TransportException */ protected function fetchRootIdentifier() { if ($this->repoData) { return; } $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository; $this->repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl); if (null === $this->repoData && null !== $this->gitDriver) { return; } $this->owner = $this->repoData['owner']['login']; $this->repository = $this->repoData['name']; $this->isPrivate = !empty($this->repoData['private']); if (isset($this->repoData['default_branch'])) { $this->rootIdentifier = $this->repoData['default_branch']; } elseif (isset($this->repoData['master_branch'])) { $this->rootIdentifier = $this->repoData['master_branch']; } else { $this->rootIdentifier = 'master'; } $this->hasIssues = !empty($this->repoData['has_issues']); } protected function attemptCloneFallback() { $this->isPrivate = true; try { // If this repository may be private (hard to say for sure, // GitHub returns 404 for private repositories) and we // cannot ask for authentication credentials (because we // are not interactive) then we fallback to GitDriver. $this->setupGitDriver($this->generateSshUrl()); return; } catch (\RuntimeException $e) { $this->gitDriver = null; $this->io->writeError('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials'); throw $e; } } protected function setupGitDriver($url) { $this->gitDriver = new GitDriver( array('url' => $url), $this->io, $this->config, $this->process, $this->remoteFilesystem ); $this->gitDriver->initialize(); } protected function getNextPage() { $headers = $this->remoteFilesystem->getLastHeaders(); foreach ($headers as $header) { if (substr($header, 0, 5) === 'Link:') { $links = explode(',', substr($header, 5)); foreach ($links as $link) { if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) { return $match[1]; } } } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; /** * @author BohwaZ */ class FossilDriver extends VcsDriver { protected $tags; protected $branches; protected $rootIdentifier; protected $repoFile; protected $checkoutDir; protected $infoCache = array(); /** * {@inheritDoc} */ public function initialize() { if (Filesystem::isLocalPath($this->url)) { $this->checkoutDir = $this->url; } else { $this->repoFile = $this->config->get('cache-repo-dir') . '/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '.fossil'; $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/'; $fs = new Filesystem(); $fs->ensureDirectoryExists($this->checkoutDir); if (!is_writable(dirname($this->checkoutDir))) { throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.'); } // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($this->url, $this->io); // update the repo if it is a valid fossil repository if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute('fossil info', $output, $this->checkoutDir)) { if (0 !== $this->process->execute('fossil pull', $output, $this->checkoutDir)) { $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { // clean up directory and do a fresh clone into it $fs->removeDirectory($this->checkoutDir); $fs->remove($this->repoFile); $fs->ensureDirectoryExists($this->checkoutDir); if (0 !== $this->process->execute(sprintf('fossil clone %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoFile)), $output)) { $output = $this->process->getErrorOutput(); if (0 !== $this->process->execute('fossil version', $ignoredOutput)) { throw new \RuntimeException('Failed to clone '.$this->url.', fossil was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output); } if (0 !== $this->process->execute(sprintf('fossil open %s', ProcessExecutor::escape($this->repoFile)), $output, $this->checkoutDir)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output); } } } $this->getTags(); $this->getBranches(); } /** * {@inheritDoc} */ public function getRootIdentifier() { if (null === $this->rootIdentifier) { $this->rootIdentifier = 'trunk'; } return $this->rootIdentifier; } /** * {@inheritDoc} */ public function getUrl() { return $this->url; } /** * {@inheritDoc} */ public function getSource($identifier) { return array('type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier); } /** * {@inheritDoc} */ public function getDist($identifier) { return null; } /** * {@inheritdoc} */ public function getFileContent($file, $identifier) { $command = sprintf('fossil cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute($command, $content, $this->checkoutDir); if (!trim($content)) { return null; } return $content; } /** * {@inheritdoc} */ public function getChangeDate($identifier) { $this->process->execute(sprintf('fossil finfo composer.json | head -n 2 | tail -n 1 | awk \'{print $1}\''), $output, $this->checkoutDir); return new \DateTime(trim($output), new \DateTimeZone('UTC')); } /** * {@inheritDoc} */ public function getTags() { if (null === $this->tags) { $tags = array(); $this->process->execute('fossil tag list', $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $tag) { $tags[$tag] = $tag; } $this->tags = $tags; } return $this->tags; } /** * {@inheritDoc} */ public function getBranches() { if (null === $this->branches) { $branches = array(); $bookmarks = array(); $this->process->execute('fossil branch list', $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $branch) { $branch = trim(preg_replace('/^\*/', '', trim($branch))); $branches[$branch] = $branch; } $this->branches = $branches; } return $this->branches; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) { return true; } if (preg_match('!/fossil/|\.fossil!', $url)) { return true; } // local filesystem if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!is_dir($url)) { return false; } $process = new ProcessExecutor(); // check whether there is a fossil repo in that path if ($process->execute('fossil info', $output, $url) === 0) { return true; } } return false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\IO\IOInterface; /** * @author Per Bernhardt */ class GitBitbucketDriver extends BitbucketDriver { /** * {@inheritDoc} */ public function getRootIdentifier() { if ($this->fallbackDriver) { return $this->fallbackDriver->getRootIdentifier(); } if (null === $this->rootIdentifier) { if (! $this->getRepoData()) { return $this->fallbackDriver->getRootIdentifier(); } if ($this->vcsType !== 'git') { throw new \RuntimeException( $this->url.' does not appear to be a git repository, use '. $this->cloneHttpsUrl.' if this is a mercurial bitbucket repository' ); } $mainBranchData = $this->getMainBranchData(); $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'master'; } return $this->rootIdentifier; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) { return false; } if (!extension_loaded('openssl')) { $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } /** * {@inheritdoc} */ protected function setupFallbackDriver($url) { $this->fallbackDriver = new GitDriver( array('url' => $url), $this->io, $this->config, $this->process, $this->remoteFilesystem ); $this->fallbackDriver->initialize(); } /** * {@inheritdoc} */ protected function generateSshUrl() { return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\IO\IOInterface; /** * @author Per Bernhardt */ class HgBitbucketDriver extends BitbucketDriver { /** * {@inheritDoc} */ public function getRootIdentifier() { if ($this->fallbackDriver) { return $this->fallbackDriver->getRootIdentifier(); } if (null === $this->rootIdentifier) { if (! $this->getRepoData()) { return $this->fallbackDriver->getRootIdentifier(); } if ($this->vcsType !== 'hg') { throw new \RuntimeException( $this->url.' does not appear to be a mercurial repository, use '. $this->cloneHttpsUrl.' if this is a git bitbucket repository' ); } $mainBranchData = $this->getMainBranchData(); $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'default'; } return $this->rootIdentifier; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) { return false; } if (!extension_loaded('openssl')) { $io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } /** * {@inheritdoc} */ protected function setupFallbackDriver($url) { $this->fallbackDriver = new HgDriver( array('url' => $url), $this->io, $this->config, $this->process, $this->remoteFilesystem ); $this->fallbackDriver->initialize(); } /** * {@inheritdoc} */ protected function generateSshUrl() { return 'ssh://hg@' . $this->originUrl . '/' . $this->owner.'/'.$this->repository; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; /** * @author Per Bernhardt */ class HgDriver extends VcsDriver { protected $tags; protected $branches; protected $rootIdentifier; protected $repoDir; protected $infoCache = array(); /** * {@inheritDoc} */ public function initialize() { if (Filesystem::isLocalPath($this->url)) { $this->repoDir = $this->url; } else { $cacheDir = $this->config->get('cache-vcs-dir'); $this->repoDir = $cacheDir . '/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/'; $fs = new Filesystem(); $fs->ensureDirectoryExists($cacheDir); if (!is_writable(dirname($this->repoDir))) { throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.'); } // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($this->url, $this->io); // update the repo if it is a valid hg repository if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) { if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) { $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); if (0 !== $this->process->execute(sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoDir)), $output, $cacheDir)) { $output = $this->process->getErrorOutput(); if (0 !== $this->process->execute('hg --version', $ignoredOutput)) { throw new \RuntimeException('Failed to clone '.$this->url.', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } throw new \RuntimeException('Failed to clone '.$this->url.', could not read packages from it' . "\n\n" .$output); } } } $this->getTags(); $this->getBranches(); } /** * {@inheritDoc} */ public function getRootIdentifier() { if (null === $this->rootIdentifier) { $this->process->execute(sprintf('hg tip --template "{node}"'), $output, $this->repoDir); $output = $this->process->splitLines($output); $this->rootIdentifier = $output[0]; } return $this->rootIdentifier; } /** * {@inheritDoc} */ public function getUrl() { return $this->url; } /** * {@inheritDoc} */ public function getSource($identifier) { return array('type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier); } /** * {@inheritDoc} */ public function getDist($identifier) { return null; } /** * {@inheritdoc} */ public function getFileContent($file, $identifier) { $resource = sprintf('hg cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute($resource, $content, $this->repoDir); if (!trim($content)) { return; } return $content; } /** * {@inheritdoc} */ public function getChangeDate($identifier) { $this->process->execute( sprintf( 'hg log --template "{date|rfc3339date}" -r %s', ProcessExecutor::escape($identifier) ), $output, $this->repoDir ); return new \DateTime(trim($output), new \DateTimeZone('UTC')); } /** * {@inheritDoc} */ public function getTags() { if (null === $this->tags) { $tags = array(); $this->process->execute('hg tags', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $tag) { if ($tag && preg_match('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) { $tags[$match[1]] = $match[2]; } } unset($tags['tip']); $this->tags = $tags; } return $this->tags; } /** * {@inheritDoc} */ public function getBranches() { if (null === $this->branches) { $branches = array(); $bookmarks = array(); $this->process->execute('hg branches', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && preg_match('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match)) { $branches[$match[1]] = $match[2]; } } $this->process->execute('hg bookmarks', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && preg_match('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match)) { $bookmarks[$match[1]] = $match[2]; } } // Branches will have preference over bookmarks $this->branches = array_merge($bookmarks, $branches); } return $this->branches; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if (preg_match('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) { return true; } // local filesystem if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!is_dir($url)) { return false; } $process = new ProcessExecutor(); // check whether there is a hg repo in that path if ($process->execute('hg summary', $output, $url) === 0) { return true; } } if (!$deep) { return false; } $processExecutor = new ProcessExecutor(); $exit = $processExecutor->execute(sprintf('hg identify %s', ProcessExecutor::escape($url)), $ignored); return $exit === 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Cache; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Downloader\TransportException; use Composer\Util\RemoteFilesystem; use Composer\Util\GitLab; /** * Driver for GitLab API, use the Git driver for local checkouts. * * @author Henrik Bjørnskov * @author Jérôme Tamarelle */ class GitLabDriver extends VcsDriver { private $scheme; private $namespace; private $repository; /** * @var array Project data returned by GitLab API */ private $project; /** * @var array Keeps commits returned by GitLab API */ private $commits = array(); /** * @var array List of tag => reference */ private $tags; /** * @var array List of branch => reference */ private $branches; /** * Git Driver * * @var GitDriver */ protected $gitDriver; /** * Defaults to true unless we can make sure it is public * * @var bool defines whether the repo is private or not */ private $isPrivate = true; const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; /** * Extracts information from the repository url. * * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead. * * {@inheritDoc} */ public function initialize() { if (!preg_match(self::URL_REGEX, $this->url, $match)) { throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.'); } $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; $configuredDomains = $this->config->get('gitlab-domains'); $urlParts = explode('/', $match['parts']); $this->scheme = !empty($match['scheme']) ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') ; $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts); $this->namespace = implode('/', $urlParts); $this->repository = preg_replace('#(\.git)$#', '', $match['repo']); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository); $this->fetchProject(); } /** * Updates the RemoteFilesystem instance. * Mainly useful for tests. * * @internal */ public function setRemoteFilesystem(RemoteFilesystem $remoteFilesystem) { $this->remoteFilesystem = $remoteFilesystem; } /** * {@inheritdoc} */ public function getFileContent($file, $identifier) { if ($this->gitDriver) { return $this->gitDriver->getFileContent($file, $identifier); } // Convert the root identifier to a cachable commit id if (!preg_match('{[a-f0-9]{40}}i', $identifier)) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { $identifier = $branches[$identifier]; } } $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier; try { $content = $this->getContents($resource); } catch (TransportException $e) { if ($e->getCode() !== 404) { throw $e; } return null; } return $content; } /** * {@inheritdoc} */ public function getChangeDate($identifier) { if ($this->gitDriver) { return $this->gitDriver->getChangeDate($identifier); } if (isset($this->commits[$identifier])) { return new \DateTime($this->commits[$identifier]['committed_date']); } return new \DateTime(); } /** * {@inheritDoc} */ public function getRepositoryUrl() { return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo']; } /** * {@inheritDoc} */ public function getUrl() { if ($this->gitDriver) { return $this->gitDriver->getUrl(); } return $this->project['web_url']; } /** * {@inheritDoc} */ public function getDist($identifier) { $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier; return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); } /** * {@inheritDoc} */ public function getSource($identifier) { if ($this->gitDriver) { return $this->gitDriver->getSource($identifier); } return array('type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier); } /** * {@inheritDoc} */ public function getRootIdentifier() { if ($this->gitDriver) { return $this->gitDriver->getRootIdentifier(); } return $this->project['default_branch']; } /** * {@inheritDoc} */ public function getBranches() { if ($this->gitDriver) { return $this->gitDriver->getBranches(); } if (!$this->branches) { $this->branches = $this->getReferences('branches'); } return $this->branches; } /** * {@inheritDoc} */ public function getTags() { if ($this->gitDriver) { return $this->gitDriver->getTags(); } if (!$this->tags) { $this->tags = $this->getReferences('tags'); } return $this->tags; } /** * @return string Base URL for GitLab API v3 */ public function getApiUrl() { return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); } /** * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.` * * @param string $string * @return string */ private function urlEncodeAll($string) { $encoded = ''; for ($i = 0; isset($string[$i]); $i++) { $character = $string[$i]; if (!ctype_alnum($character) && !in_array($character, array('-', '_'), true)) { $character = '%' . sprintf('%02X', ord($character)); } $encoded .= $character; } return $encoded; } /** * @param string $type * * @return string[] where keys are named references like tags or branches and the value a sha */ protected function getReferences($type) { $perPage = 100; $resource = $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage; $references = array(); do { $data = JsonFile::parseJson($this->getContents($resource), $resource); foreach ($data as $datum) { $references[$datum['name']] = $datum['commit']['id']; // Keep the last commit date of a reference to avoid // unnecessary API call when retrieving the composer file. $this->commits[$datum['commit']['id']] = $datum['commit']; } if (count($data) >= $perPage) { $resource = $this->getNextPage(); } else { $resource = false; } } while ($resource); return $references; } protected function fetchProject() { // we need to fetch the default branch from the api $resource = $this->getApiUrl(); $this->project = JsonFile::parseJson($this->getContents($resource, true), $resource); if (isset($this->project['visibility'])) { $this->isPrivate = $this->project['visibility'] !== 'public'; } else { // client is not authendicated, therefore repository has to be public $this->isPrivate = false; } } protected function attemptCloneFallback() { try { if ($this->isPrivate === false) { $url = $this->generatePublicUrl(); } else { $url = $this->generateSshUrl(); } // If this repository may be private and we // cannot ask for authentication credentials (because we // are not interactive) then we fallback to GitDriver. $this->setupGitDriver($url); return; } catch (\RuntimeException $e) { $this->gitDriver = null; $this->io->writeError('Failed to clone the '.$url.' repository, try running in interactive mode so that you can enter your credentials'); throw $e; } } /** * Generate an SSH URL * * @return string */ protected function generateSshUrl() { return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; } protected function generatePublicUrl() { return 'https://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git'; } protected function setupGitDriver($url) { $this->gitDriver = new GitDriver( array('url' => $url), $this->io, $this->config, $this->process, $this->remoteFilesystem ); $this->gitDriver->initialize(); } /** * {@inheritDoc} */ protected function getContents($url, $fetchingRepoData = false) { try { $res = parent::getContents($url); if ($fetchingRepoData) { $json = JsonFile::parseJson($res, $url); // force auth as the unauthenticated version of the API is broken if (!isset($json['default_branch'])) { if (!empty($json['id'])) { $this->isPrivate = false; } throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401); } } return $res; } catch (TransportException $e) { $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->remoteFilesystem); switch ($e->getCode()) { case 401: case 404: // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 if (!$fetchingRepoData) { throw $e; } if ($gitLabUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive()) { return $this->attemptCloneFallback(); } $this->io->writeError('Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . ''); $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata ('.$this->url.')'); return parent::getContents($url); case 403: if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { return $this->attemptCloneFallback(); } throw $e; default: throw $e; } } } /** * Uses the config `gitlab-domains` to see if the driver supports the url for the * repository given. * * {@inheritDoc} */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { if (!preg_match(self::URL_REGEX, $url, $match)) { return false; } $scheme = !empty($match['scheme']) ? $match['scheme'] : null; $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; $urlParts = explode('/', $match['parts']); if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts)) { return false; } if ('https' === $scheme && !extension_loaded('openssl')) { $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } private function getNextPage() { $headers = $this->remoteFilesystem->getLastHeaders(); foreach ($headers as $header) { if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) { $links = explode(',', $match[1]); foreach ($links as $link) { if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) { return $match[1]; } } } } } /** * @param array $configuredDomains * @param string $guessedDomain * @param array $urlParts * @return bool|string */ private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts) { if (in_array($guessedDomain, $configuredDomains)) { return $guessedDomain; } while (null !== ($part = array_shift($urlParts))) { $guessedDomain .= '/' . $part; if (in_array($guessedDomain, $configuredDomains)) { return $guessedDomain; } } return false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\PackageInterface; /** * Composite repository. * * @author Beau Simensen */ class CompositeRepository extends BaseRepository { /** * List of repositories * @var array */ private $repositories; /** * Constructor * @param array $repositories */ public function __construct(array $repositories) { $this->repositories = array(); foreach ($repositories as $repo) { $this->addRepository($repo); } } /** * Returns all the wrapped repositories * * @return array */ public function getRepositories() { return $this->repositories; } /** * {@inheritdoc} */ public function hasPackage(PackageInterface $package) { foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ if ($repository->hasPackage($package)) { return true; } } return false; } /** * {@inheritdoc} */ public function findPackage($name, $constraint) { foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $package = $repository->findPackage($name, $constraint); if (null !== $package) { return $package; } } return null; } /** * {@inheritdoc} */ public function findPackages($name, $constraint = null) { $packages = array(); foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $packages[] = $repository->findPackages($name, $constraint); } return $packages ? call_user_func_array('array_merge', $packages) : array(); } /** * {@inheritdoc} */ public function search($query, $mode = 0, $type = null) { $matches = array(); foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $matches[] = $repository->search($query, $mode, $type); } return $matches ? call_user_func_array('array_merge', $matches) : array(); } /** * {@inheritdoc} */ public function getPackages() { $packages = array(); foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $packages[] = $repository->getPackages(); } return $packages ? call_user_func_array('array_merge', $packages) : array(); } /** * {@inheritdoc} */ public function removePackage(PackageInterface $package) { foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $repository->removePackage($package); } } /** * {@inheritdoc} */ public function count() { $total = 0; foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $total += $repository->count(); } return $total; } /** * Add a repository. * @param RepositoryInterface $repository */ public function addRepository(RepositoryInterface $repository) { if ($repository instanceof self) { foreach ($repository->getRepositories() as $repo) { $this->addRepository($repo); } } else { $this->repositories[] = $repository; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; /** * A repository implementation that simply stores packages in an array * * @author Nils Adermann */ class ArrayRepository extends BaseRepository { /** @var PackageInterface[] */ protected $packages; public function __construct(array $packages = array()) { foreach ($packages as $package) { $this->addPackage($package); } } /** * {@inheritDoc} */ public function findPackage($name, $constraint) { $name = strtolower($name); if (!$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { return $package; } } } return null; } /** * {@inheritDoc} */ public function findPackages($name, $constraint = null) { // normalize name $name = strtolower($name); $packages = array(); if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); if (null === $constraint || $constraint->matches($pkgConstraint)) { $packages[] = $package; } } } return $packages; } /** * {@inheritDoc} */ public function search($query, $mode = 0, $type = null) { $regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i'; $matches = array(); foreach ($this->getPackages() as $package) { $name = $package->getName(); if (isset($matches[$name])) { continue; } if (preg_match($regex, $name) || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && preg_match($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription())) ) { if (null !== $type && $package->getType() !== $type) { continue; } $matches[$name] = array( 'name' => $package->getPrettyName(), 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null, ); } } return array_values($matches); } /** * {@inheritDoc} */ public function hasPackage(PackageInterface $package) { $packageId = $package->getUniqueName(); foreach ($this->getPackages() as $repoPackage) { if ($packageId === $repoPackage->getUniqueName()) { return true; } } return false; } /** * Adds a new package to the repository * * @param PackageInterface $package */ public function addPackage(PackageInterface $package) { if (null === $this->packages) { $this->initialize(); } $package->setRepository($this); $this->packages[] = $package; if ($package instanceof AliasPackage) { $aliasedPackage = $package->getAliasOf(); if (null === $aliasedPackage->getRepository()) { $this->addPackage($aliasedPackage); } } } protected function createAliasPackage(PackageInterface $package, $alias, $prettyAlias) { return new AliasPackage($package instanceof AliasPackage ? $package->getAliasOf() : $package, $alias, $prettyAlias); } /** * Removes package from repository. * * @param PackageInterface $package package instance */ public function removePackage(PackageInterface $package) { $packageId = $package->getUniqueName(); foreach ($this->getPackages() as $key => $repoPackage) { if ($packageId === $repoPackage->getUniqueName()) { array_splice($this->packages, $key, 1); return; } } } /** * {@inheritDoc} */ public function getPackages() { if (null === $this->packages) { $this->initialize(); } return $this->packages; } /** * Returns the number of packages in this repository * * @return int Number of packages */ public function count() { return count($this->packages); } /** * Initializes the packages array. Mostly meant as an extension point. */ protected function initialize() { $this->packages = array(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Configurable repository interface. * * @author Lukas Homza */ interface ConfigurableRepositoryInterface { public function getRepoConfig(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Downloader\TransportException; use Composer\Repository\Vcs\VcsDriverInterface; use Composer\Package\Version\VersionParser; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Package\Loader\InvalidPackageException; use Composer\Package\Loader\LoaderInterface; use Composer\EventDispatcher\EventDispatcher; use Composer\IO\IOInterface; use Composer\Config; /** * @author Jordi Boggiano */ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInterface { protected $url; protected $packageName; protected $verbose; protected $io; protected $config; protected $versionParser; protected $type; protected $loader; protected $repoConfig; protected $branchErrorOccurred = false; private $drivers; /** @var VcsDriverInterface */ private $driver; public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null) { parent::__construct(); $this->drivers = $drivers ?: array( 'github' => 'Composer\Repository\Vcs\GitHubDriver', 'gitlab' => 'Composer\Repository\Vcs\GitLabDriver', 'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', 'git' => 'Composer\Repository\Vcs\GitDriver', 'hg-bitbucket' => 'Composer\Repository\Vcs\HgBitbucketDriver', 'hg' => 'Composer\Repository\Vcs\HgDriver', 'perforce' => 'Composer\Repository\Vcs\PerforceDriver', 'fossil' => 'Composer\Repository\Vcs\FossilDriver', // svn must be last because identifying a subversion server for sure is practically impossible 'svn' => 'Composer\Repository\Vcs\SvnDriver', ); $this->url = $repoConfig['url']; $this->io = $io; $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs'; $this->verbose = $io->isVeryVerbose(); $this->config = $config; $this->repoConfig = $repoConfig; } public function getRepoConfig() { return $this->repoConfig; } public function setLoader(LoaderInterface $loader) { $this->loader = $loader; } public function getDriver() { if ($this->driver) { return $this->driver; } if (isset($this->drivers[$this->type])) { $class = $this->drivers[$this->type]; $this->driver = new $class($this->repoConfig, $this->io, $this->config); $this->driver->initialize(); return $this->driver; } foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url)) { $this->driver = new $driver($this->repoConfig, $this->io, $this->config); $this->driver->initialize(); return $this->driver; } } foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url, true)) { $this->driver = new $driver($this->repoConfig, $this->io, $this->config); $this->driver->initialize(); return $this->driver; } } } public function hadInvalidBranches() { return $this->branchErrorOccurred; } protected function initialize() { parent::initialize(); $verbose = $this->verbose; $driver = $this->getDriver(); if (!$driver) { throw new \InvalidArgumentException('No driver found to handle VCS repository '.$this->url); } $this->versionParser = new VersionParser; if (!$this->loader) { $this->loader = new ArrayLoader($this->versionParser); } try { if ($driver->hasComposerFile($driver->getRootIdentifier())) { $data = $driver->getComposerInformation($driver->getRootIdentifier()); $this->packageName = !empty($data['name']) ? $data['name'] : null; } } catch (\Exception $e) { if ($verbose) { $this->io->writeError('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().''); } } foreach ($driver->getTags() as $tag => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $tag . ')'; if ($verbose) { $this->io->writeError($msg); } else { $this->io->overwriteError($msg, false); } // strip the release- prefix from tags if present $tag = str_replace('release-', '', $tag); if (!$parsedTag = $this->validateTag($tag)) { if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); } continue; } try { if (!$data = $driver->getComposerInformation($identifier)) { if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', no composer file'); } continue; } // manually versioned package if (isset($data['version'])) { $data['version_normalized'] = $this->versionParser->normalize($data['version']); } else { // auto-versioned package, read value from tag $data['version'] = $tag; $data['version_normalized'] = $parsedTag; } // make sure tag packages have no -dev flag $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']); $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']); // broken package, version doesn't match tag if ($data['version_normalized'] !== $parsedTag) { if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); } continue; } if ($verbose) { $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); } $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); } catch (\Exception $e) { if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).''); } continue; } } if (!$verbose) { $this->io->overwriteError('', false); } foreach ($driver->getBranches() as $branch => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; if ($verbose) { $this->io->writeError($msg); } else { $this->io->overwriteError($msg, false); } if (!$parsedBranch = $this->validateBranch($branch)) { if ($verbose) { $this->io->writeError('Skipped branch '.$branch.', invalid name'); } continue; } try { if (!$data = $driver->getComposerInformation($identifier)) { if ($verbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file'); } continue; } // branches are always auto-versioned, read value from branch name $data['version'] = $branch; $data['version_normalized'] = $parsedBranch; // make sure branch packages have a dev flag if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) { $data['version'] = 'dev-' . $data['version']; } else { $prefix = substr($branch, 0, 1) === 'v' ? 'v' : ''; $data['version'] = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch); } if ($verbose) { $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); } $packageData = $this->preProcess($driver, $data, $identifier); $package = $this->loader->load($packageData); if ($this->loader instanceof ValidatingArrayLoader && $this->loader->getWarnings()) { throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData); } $this->addPackage($package); } catch (TransportException $e) { if ($verbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file was found'); } continue; } catch (\Exception $e) { if (!$verbose) { $this->io->writeError(''); } $this->branchErrorOccurred = true; $this->io->writeError('Skipped branch '.$branch.', '.$e->getMessage().''); $this->io->writeError(''); continue; } } $driver->cleanup(); if (!$verbose) { $this->io->overwriteError('', false); } if (!$this->getPackages()) { throw new InvalidRepositoryException('No valid composer.json was found in any branch or tag of '.$this->url.', could not load a package from it.'); } } protected function preProcess(VcsDriverInterface $driver, array $data, $identifier) { // keep the name of the main identifier for all packages $data['name'] = $this->packageName ?: $data['name']; if (!isset($data['dist'])) { $data['dist'] = $driver->getDist($identifier); } if (!isset($data['source'])) { $data['source'] = $driver->getSource($identifier); } return $data; } private function validateBranch($branch) { try { return $this->versionParser->normalizeBranch($branch); } catch (\Exception $e) { } return false; } private function validateTag($version) { try { return $this->versionParser->normalize($version); } catch (\Exception $e) { } return false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ValidatingArrayLoader; /** * Package repository. * * @author Jordi Boggiano */ class PackageRepository extends ArrayRepository { private $config; /** * Initializes filesystem repository. * * @param array $config package definition */ public function __construct(array $config) { parent::__construct(); $this->config = $config['package']; // make sure we have an array of package definitions if (!is_numeric(key($this->config))) { $this->config = array($this->config); } } /** * Initializes repository (reads file, or remote address). */ protected function initialize() { parent::initialize(); $loader = new ValidatingArrayLoader(new ArrayLoader(null, true), false); foreach ($this->config as $package) { try { $package = $loader->load($package); } catch (\Exception $e) { throw new InvalidRepositoryException('A repository of type "package" contains an invalid package definition: '.$e->getMessage()."\n\nInvalid package definition:\n".json_encode($package)); } $this->addPackage($package); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\RemoteFilesystem; use Composer\Json\JsonFile; /** * @author Jordi Boggiano */ class RepositoryFactory { /** * @param IOInterface $io * @param Config $config * @param string $repository * @param bool $allowFilesystem * @return array|mixed */ public static function configFromString(IOInterface $io, Config $config, $repository, $allowFilesystem = false) { if (0 === strpos($repository, 'http')) { $repoConfig = array('type' => 'composer', 'url' => $repository); } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) { $json = new JsonFile($repository, Factory::createRemoteFilesystem($io, $config)); $data = $json->read(); if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) { $repoConfig = array('type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/')); } elseif ($allowFilesystem) { $repoConfig = array('type' => 'filesystem', 'json' => $json); } else { throw new \InvalidArgumentException("Invalid repository URL ($repository) given. This file does not contain a valid composer repository."); } } elseif ('{' === substr($repository, 0, 1)) { // assume it is a json object that makes a repo config $repoConfig = JsonFile::parseJson($repository); } else { throw new \InvalidArgumentException("Invalid repository url ($repository) given. Has to be a .json file, an http url or a JSON object."); } return $repoConfig; } /** * @param IOInterface $io * @param Config $config * @param string $repository * @param bool $allowFilesystem * @return RepositoryInterface */ public static function fromString(IOInterface $io, Config $config, $repository, $allowFilesystem = false) { $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem); return static::createRepo($io, $config, $repoConfig); } /** * @param IOInterface $io * @param Config $config * @param array $repoConfig * @return RepositoryInterface */ public static function createRepo(IOInterface $io, Config $config, array $repoConfig) { $rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config)); $repos = static::createRepos($rm, array($repoConfig)); return reset($repos); } /** * @param IOInterface|null $io * @param Config|null $config * @param RepositoryManager|null $rm * @return RepositoryInterface[] */ public static function defaultRepos(IOInterface $io = null, Config $config = null, RepositoryManager $rm = null) { if (!$config) { $config = Factory::createConfig($io); } if (!$rm) { if (!$io) { throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); } $rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config)); } return static::createRepos($rm, $config->getRepositories()); } /** * @param IOInterface $io * @param Config $config * @param EventDispatcher $eventDispatcher * @param RemoteFilesystem $rfs * @return RepositoryManager */ public static function manager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) { $rm = new RepositoryManager($io, $config, $eventDispatcher, $rfs); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository'); return $rm; } /** * @return RepositoryInterface[] */ private static function createRepos(RepositoryManager $rm, array $repoConfigs) { $repos = array(); foreach ($repoConfigs as $index => $repo) { if (is_string($repo)) { throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given'); } if (!is_array($repo)) { throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given'); } if (!isset($repo['type'])) { throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined'); } $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index; while (isset($repos[$name])) { $name .= '2'; } if ($repo['type'] === 'filesystem') { $repos[$name] = new FilesystemRepository($repo['json']); } else { $repos[$name] = $rm->createRepository($repo['type'], $repo, $index); } } return $repos; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Installed filesystem repository. * * @author Jordi Boggiano */ class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\AliasPackage; /** * Writable array repository. * * @author Jordi Boggiano */ class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface { /** * {@inheritDoc} */ public function write() { } /** * {@inheritDoc} */ public function reload() { } /** * {@inheritDoc} */ public function getCanonicalPackages() { $packages = $this->getPackages(); // get at most one package of each name, preferring non-aliased ones $packagesByName = array(); foreach ($packages as $package) { if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) { $packagesByName[$package->getName()] = $package; } } $canonicalPackages = array(); // unfold aliased packages foreach ($packagesByName as $package) { while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $canonicalPackages[] = $package; } return $canonicalPackages; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; /** * Filesystem repository. * * @author Konstantin Kudryashov * @author Jordi Boggiano */ class FilesystemRepository extends WritableArrayRepository { private $file; /** * Initializes filesystem repository. * * @param JsonFile $repositoryFile repository json file */ public function __construct(JsonFile $repositoryFile) { parent::__construct(); $this->file = $repositoryFile; } /** * Initializes repository (reads file, or remote address). */ protected function initialize() { parent::initialize(); if (!$this->file->exists()) { return; } try { $packages = $this->file->read(); if (!is_array($packages)) { throw new \UnexpectedValueException('Could not parse package list from the repository'); } } catch (\Exception $e) { throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage()); } $loader = new ArrayLoader(null, true); foreach ($packages as $packageData) { $package = $loader->load($packageData); $this->addPackage($package); } } public function reload() { $this->packages = null; $this->initialize(); } /** * Writes writable repository. */ public function write() { $data = array(); $dumper = new ArrayDumper(); foreach ($this->getCanonicalPackages() as $package) { $data[] = $dumper->dump($package); } $this->file->write($data); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\LoaderInterface; /** * @author Serge Smertin */ class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface { /** @var LoaderInterface */ protected $loader; protected $lookup; protected $repoConfig; private $io; public function __construct(array $repoConfig, IOInterface $io) { parent::__construct(); if (!extension_loaded('zip')) { throw new \RuntimeException('The artifact repository requires PHP\'s zip extension'); } $this->loader = new ArrayLoader(); $this->lookup = $repoConfig['url']; $this->io = $io; $this->repoConfig = $repoConfig; } public function getRepoConfig() { return $this->repoConfig; } protected function initialize() { parent::initialize(); $this->scanDirectory($this->lookup); } private function scanDirectory($path) { $io = $this->io; $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); $iterator = new \RecursiveIteratorIterator($directory); $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i'); foreach ($regex as $file) { /* @var $file \SplFileInfo */ if (!$file->isFile()) { continue; } $package = $this->getComposerInformation($file); if (!$package) { $io->writeError("File {$file->getBasename()} doesn't seem to hold a package", true, IOInterface::VERBOSE); continue; } $template = 'Found package %s (%s) in file %s'; $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE); $this->addPackage($package); } } /** * Find a file by name, returning the one that has the shortest path. * * @param \ZipArchive $zip * @param $filename * @return bool|int */ private function locateFile(\ZipArchive $zip, $filename) { $indexOfShortestMatch = false; $lengthOfShortestMatch = -1; for ($i = 0; $i < $zip->numFiles; $i++) { $stat = $zip->statIndex($i); if (strcmp(basename($stat['name']), $filename) === 0) { $directoryName = dirname($stat['name']); if ($directoryName == '.') { //if composer.json is in root directory //it has to be the one to use. return $i; } if (strpos($directoryName, '\\') !== false || strpos($directoryName, '/') !== false) { //composer.json files below first directory are rejected continue; } $length = strlen($stat['name']); if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) { //Check it's not a directory. $contents = $zip->getFromIndex($i); if ($contents !== false) { $indexOfShortestMatch = $i; $lengthOfShortestMatch = $length; } } } } return $indexOfShortestMatch; } private function getComposerInformation(\SplFileInfo $file) { $zip = new \ZipArchive(); $zip->open($file->getPathname()); if (0 == $zip->numFiles) { return false; } $foundFileIndex = $this->locateFile($zip, 'composer.json'); if (false === $foundFileIndex) { return false; } $configurationFileName = $zip->getNameIndex($foundFileIndex); $composerFile = "zip://{$file->getPathname()}#$configurationFileName"; $json = file_get_contents($composerFile); $package = JsonFile::parseJson($json, $composerFile); $package['dist'] = array( 'type' => 'zip', 'url' => strtr($file->getPathname(), '\\', '/'), 'shasum' => sha1_file($file->getRealPath()), ); try { $package = $this->loader->load($package); } catch (\UnexpectedValueException $e) { throw new \UnexpectedValueException('Failed loading package in '.$file.': '.$e->getMessage(), 0, $e); } return $package; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Config; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; /** * This repository allows installing local packages that are not necessarily under their own VCS. * * The local packages will be symlinked when possible, else they will be copied. * * @code * "require": { * "/": "*" * }, * "repositories": [ * { * "type": "path", * "url": "../../relative/path/to/package/" * }, * { * "type": "path", * "url": "/absolute/path/to/package/" * }, * { * "type": "path", * "url": "/absolute/path/to/several/packages/*" * }, * { * "type": "path", * "url": "../../relative/path/to/package/", * "options": { * "symlink": false * } * }, * ] * @endcode * * @author Samuel Roze * @author Johann Reinke */ class PathRepository extends ArrayRepository implements ConfigurableRepositoryInterface { /** * @var ArrayLoader */ private $loader; /** * @var VersionGuesser */ private $versionGuesser; /** * @var string */ private $url; /** * @var array */ private $repoConfig; /** * @var ProcessExecutor */ private $process; /** * @var array */ private $options; /** * Initializes path repository. * * @param array $repoConfig * @param IOInterface $io * @param Config $config */ public function __construct(array $repoConfig, IOInterface $io, Config $config) { if (!isset($repoConfig['url'])) { throw new \RuntimeException('You must specify the `url` configuration for the path repository'); } $this->loader = new ArrayLoader(null, true); $this->url = Platform::expandPath($repoConfig['url']); $this->process = new ProcessExecutor($io); $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser()); $this->repoConfig = $repoConfig; $this->options = isset($repoConfig['options']) ? $repoConfig['options'] : array(); parent::__construct(); } public function getRepoConfig() { return $this->repoConfig; } /** * Initializes path repository. * * This method will basically read the folder and add the found package. */ protected function initialize() { parent::initialize(); foreach ($this->getUrlMatches() as $url) { $path = realpath($url) . DIRECTORY_SEPARATOR; $composerFilePath = $path.'composer.json'; if (!file_exists($composerFilePath)) { continue; } $json = file_get_contents($composerFilePath); $package = JsonFile::parseJson($json, $composerFilePath); $package['dist'] = array( 'type' => 'path', 'url' => $url, 'reference' => sha1($json . serialize($this->options)), ); $package['transport-options'] = $this->options; // carry over the root package version if this path repo is in the same git repository as root package if (!isset($package['version']) && ($rootVersion = getenv('COMPOSER_ROOT_VERSION'))) { if ( 0 === $this->process->execute('git rev-parse HEAD', $ref1, $path) && 0 === $this->process->execute('git rev-parse HEAD', $ref2) && $ref1 === $ref2 ) { $package['version'] = $rootVersion; } } if (!isset($package['version'])) { $versionData = $this->versionGuesser->guessVersion($package, $path); $package['version'] = $versionData['version'] ?: 'dev-master'; } $output = ''; if (is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H', $output, $path)) { $package['dist']['reference'] = trim($output); } $package = $this->loader->load($package); $this->addPackage($package); } } /** * Get a list of all (possibly relative) path names matching given url (supports globbing). * * @return string[] */ private function getUrlMatches() { // Ensure environment-specific path separators are normalized to URL separators return array_map(function ($val) { return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/'); }, glob($this->url, GLOB_MARK | GLOB_ONLYDIR)); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Package\Version\VersionParser; use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Cache; use Composer\Config; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Util\RemoteFilesystem; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Downloader\TransportException; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; /** * @author Jordi Boggiano */ class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface { protected $config; protected $repoConfig; protected $options; protected $url; protected $baseUrl; protected $io; protected $rfs; protected $cache; protected $notifyUrl; protected $searchUrl; protected $hasProviders = false; protected $providersUrl; protected $lazyProvidersUrl; protected $providerListing; protected $providers = array(); protected $providersByUid = array(); protected $loader; protected $rootAliases; protected $allowSslDowngrade = false; protected $eventDispatcher; protected $sourceMirrors; protected $distMirrors; private $degradedMode = false; private $rootData; private $hasPartialPackages; private $partialPackagesByName; public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) { parent::__construct(); if (!preg_match('{^[\w.]+\??://}', $repoConfig['url'])) { // assume http as the default protocol $repoConfig['url'] = 'http://'.$repoConfig['url']; } $repoConfig['url'] = rtrim($repoConfig['url'], '/'); if ('https?' === substr($repoConfig['url'], 0, 6)) { $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6); } $urlBits = parse_url($repoConfig['url']); if ($urlBits === false || empty($urlBits['scheme'])) { throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']); } if (!isset($repoConfig['options'])) { $repoConfig['options'] = array(); } if (isset($repoConfig['allow_ssl_downgrade']) && true === $repoConfig['allow_ssl_downgrade']) { $this->allowSslDowngrade = true; } $this->config = $config; $this->options = $repoConfig['options']; $this->url = $repoConfig['url']; $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); $this->io = $io; $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$'); $this->loader = new ArrayLoader(); if ($rfs && $this->options) { $rfs = clone $rfs; $rfs->setOptions($this->options); } $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $this->config, $this->options); $this->eventDispatcher = $eventDispatcher; $this->repoConfig = $repoConfig; } public function getRepoConfig() { return $this->repoConfig; } public function setRootAliases(array $rootAliases) { $this->rootAliases = $rootAliases; } /** * {@inheritDoc} */ public function findPackage($name, $constraint) { if (!$this->hasProviders()) { return parent::findPackage($name, $constraint); } $name = strtolower($name); if (!$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { $packages = $this->whatProvides(new Pool('dev'), $providerName); foreach ($packages as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { return $package; } } } break; } } } /** * {@inheritDoc} */ public function findPackages($name, $constraint = null) { if (!$this->hasProviders()) { return parent::findPackages($name, $constraint); } // normalize name $name = strtolower($name); if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } $packages = array(); foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { $candidates = $this->whatProvides(new Pool('dev'), $providerName); foreach ($candidates as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); if (null === $constraint || $constraint->matches($pkgConstraint)) { $packages[] = $package; } } } break; } } return $packages; } public function getPackages() { if ($this->hasProviders()) { throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getProviderNames instead.'); } return parent::getPackages(); } /** * {@inheritDoc} */ public function search($query, $mode = 0, $type = null) { $this->loadRootServerFile(); if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { $url = str_replace(array('%query%', '%type%'), array($query, $type), $this->searchUrl); $hostname = parse_url($url, PHP_URL_HOST) ?: $url; $json = $this->rfs->getContents($hostname, $url, false); $results = JsonFile::parseJson($json, $url); return $results['results']; } if ($this->hasProviders()) { $results = array(); $regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i'; foreach ($this->getProviderNames() as $name) { if (preg_match($regex, $name)) { $results[] = array('name' => $name); } } return $results; } return parent::search($query, $mode); } public function getProviderNames() { $this->loadRootServerFile(); if (null === $this->providerListing) { $this->loadProviderListings($this->loadRootServerFile()); } if ($this->lazyProvidersUrl) { // Can not determine list of provided packages for lazy repositories return array(); } if ($this->providersUrl) { return array_keys($this->providerListing); } return array(); } protected function configurePackageTransportOptions(PackageInterface $package) { foreach ($package->getDistUrls() as $url) { if (strpos($url, $this->baseUrl) === 0) { $package->setTransportOptions($this->options); return; } } } public function hasProviders() { $this->loadRootServerFile(); return $this->hasProviders; } public function resetPackageIds() { foreach ($this->providersByUid as $package) { if ($package instanceof AliasPackage) { $package->getAliasOf()->setId(-1); } $package->setId(-1); } } /** * @param Pool $pool * @param string $name package name * @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache * @return array|mixed */ public function whatProvides(Pool $pool, $name, $bypassFilters = false) { if (isset($this->providers[$name]) && !$bypassFilters) { return $this->providers[$name]; } if ($this->hasPartialPackages && null === $this->partialPackagesByName) { $this->initializePartialPackages(); } if (!$this->hasPartialPackages || !isset($this->partialPackagesByName[$name])) { // skip platform packages, root package and composer-plugin-api if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { return array(); } if (null === $this->providerListing) { $this->loadProviderListings($this->loadRootServerFile()); } $useLastModifiedCheck = false; if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) { $hash = null; $url = str_replace('%package%', $name, $this->lazyProvidersUrl); $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; $useLastModifiedCheck = true; } elseif ($this->providersUrl) { // package does not exist in this repo if (!isset($this->providerListing[$name])) { return array(); } $hash = $this->providerListing[$name]['sha256']; $url = str_replace(array('%package%', '%hash%'), array($name, $hash), $this->providersUrl); $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; } else { return array(); } $packages = null; if ($cacheKey) { if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) { $packages = json_decode($this->cache->read($cacheKey), true); } elseif ($useLastModifiedCheck) { if ($contents = $this->cache->read($cacheKey)) { $contents = json_decode($contents, true); if (isset($contents['last-modified'])) { $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']); if (true === $response) { $packages = $contents; } elseif ($response) { $packages = $response; } } } } } if (!$packages) { try { $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck); } catch (TransportException $e) { // 404s are acceptable for lazy provider repos if ($e->getStatusCode() === 404 && $this->lazyProvidersUrl) { $packages = array('packages' => array()); } else { throw $e; } } } $loadingPartialPackage = false; } else { $packages = array('packages' => array('versions' => $this->partialPackagesByName[$name])); $loadingPartialPackage = true; } $this->providers[$name] = array(); foreach ($packages['packages'] as $versions) { foreach ($versions as $version) { if (!$loadingPartialPackage && $this->hasPartialPackages && isset($this->partialPackagesByName[$version['name']])) { continue; } // avoid loading the same objects twice if (isset($this->providersByUid[$version['uid']])) { // skip if already assigned if (!isset($this->providers[$name][$version['uid']])) { // expand alias in two packages if ($this->providersByUid[$version['uid']] instanceof AliasPackage) { $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]->getAliasOf(); $this->providers[$name][$version['uid'].'-alias'] = $this->providersByUid[$version['uid']]; } else { $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]; } // check for root aliases if (isset($this->providersByUid[$version['uid'].'-root'])) { $this->providers[$name][$version['uid'].'-root'] = $this->providersByUid[$version['uid'].'-root']; } } } else { if (!$bypassFilters && !$pool->isPackageAcceptable(strtolower($version['name']), VersionParser::parseStability($version['version']))) { continue; } // load acceptable packages in the providers $package = $this->createPackage($version, 'Composer\Package\CompletePackage'); $package->setRepository($this); if ($package instanceof AliasPackage) { $aliased = $package->getAliasOf(); $aliased->setRepository($this); $this->providers[$name][$version['uid']] = $aliased; $this->providers[$name][$version['uid'].'-alias'] = $package; // override provider with its alias so it can be expanded in the if block above $this->providersByUid[$version['uid']] = $package; } else { $this->providers[$name][$version['uid']] = $package; $this->providersByUid[$version['uid']] = $package; } // handle root package aliases unset($rootAliasData); if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { $rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()]; } elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) { $rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()]; } if (isset($rootAliasData)) { $alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']); $alias->setRepository($this); $this->providers[$name][$version['uid'].'-root'] = $alias; $this->providersByUid[$version['uid'].'-root'] = $alias; } } } } $result = $this->providers[$name]; // clean up the cache because otherwise using this puts the repo in an inconsistent state with a polluted unfiltered cache // which is likely not an issue but might cause hard to track behaviors depending on how the repo is used if ($bypassFilters) { foreach ($this->providers[$name] as $uid => $provider) { unset($this->providersByUid[$uid]); } unset($this->providers[$name]); } return $result; } /** * {@inheritDoc} */ protected function initialize() { parent::initialize(); $repoData = $this->loadDataFromServer(); foreach ($repoData as $package) { $this->addPackage($this->createPackage($package, 'Composer\Package\CompletePackage')); } } /** * Adds a new package to the repository * * @param PackageInterface $package */ public function addPackage(PackageInterface $package) { parent::addPackage($package); $this->configurePackageTransportOptions($package); } protected function loadRootServerFile() { if (null !== $this->rootData) { return $this->rootData; } if (!extension_loaded('openssl') && 'https' === substr($this->url, 0, 5)) { throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url); } $jsonUrlParts = parse_url($this->url); if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) { $jsonUrl = $this->url; } else { $jsonUrl = $this->url . '/packages.json'; } $data = $this->fetchFile($jsonUrl, 'packages.json'); if (!empty($data['notify-batch'])) { $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']); } elseif (!empty($data['notify'])) { $this->notifyUrl = $this->canonicalizeUrl($data['notify']); } if (!empty($data['search'])) { $this->searchUrl = $this->canonicalizeUrl($data['search']); } if (!empty($data['mirrors'])) { foreach ($data['mirrors'] as $mirror) { if (!empty($mirror['git-url'])) { $this->sourceMirrors['git'][] = array('url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred'])); } if (!empty($mirror['hg-url'])) { $this->sourceMirrors['hg'][] = array('url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred'])); } if (!empty($mirror['dist-url'])) { $this->distMirrors[] = array( 'url' => $this->canonicalizeUrl($mirror['dist-url']), 'preferred' => !empty($mirror['preferred']), ); } } } if (!empty($data['providers-lazy-url'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']); $this->hasProviders = true; $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); } if ($this->allowSslDowngrade) { $this->url = str_replace('https://', 'http://', $this->url); $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl); } if (!empty($data['providers-url'])) { $this->providersUrl = $this->canonicalizeUrl($data['providers-url']); $this->hasProviders = true; } if (!empty($data['providers']) || !empty($data['providers-includes'])) { $this->hasProviders = true; } // force values for packagist if (preg_match('{^https?://packagist.org/?$}i', $this->url) && !empty($this->repoConfig['force-lazy-providers'])) { $this->url = 'https://packagist.org'; $this->baseUrl = 'https://packagist.org'; $this->lazyProvidersUrl = $this->canonicalizeUrl('https://packagist.org/p/%package%.json'); $this->providersUrl = null; } elseif (!empty($this->repoConfig['force-lazy-providers'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl('/p/%package%.json'); $this->providersUrl = null; } return $this->rootData = $data; } protected function canonicalizeUrl($url) { if ('/' === $url[0]) { return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url); } return $url; } protected function loadDataFromServer() { $data = $this->loadRootServerFile(); return $this->loadIncludes($data); } protected function loadProviderListings($data) { if (isset($data['providers'])) { if (!is_array($this->providerListing)) { $this->providerListing = array(); } $this->providerListing = array_merge($this->providerListing, $data['providers']); } if ($this->providersUrl && isset($data['provider-includes'])) { $includes = $data['provider-includes']; foreach ($includes as $include => $metadata) { $url = $this->baseUrl . '/' . str_replace('%hash%', $metadata['sha256'], $include); $cacheKey = str_replace(array('%hash%','$'), '', $include); if ($this->cache->sha256($cacheKey) === $metadata['sha256']) { $includedData = json_decode($this->cache->read($cacheKey), true); } else { $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']); } $this->loadProviderListings($includedData); } } } protected function loadIncludes($data) { $packages = array(); // legacy repo handling if (!isset($data['packages']) && !isset($data['includes'])) { foreach ($data as $pkg) { foreach ($pkg['versions'] as $metadata) { $packages[] = $metadata; } } return $packages; } if (isset($data['packages'])) { foreach ($data['packages'] as $package => $versions) { foreach ($versions as $version => $metadata) { $packages[] = $metadata; } } } if (isset($data['includes'])) { foreach ($data['includes'] as $include => $metadata) { if ($this->cache->sha1($include) === $metadata['sha1']) { $includedData = json_decode($this->cache->read($include), true); } else { $includedData = $this->fetchFile($include); } $packages = array_merge($packages, $this->loadIncludes($includedData)); } } return $packages; } protected function createPackage(array $data, $class = 'Composer\Package\CompletePackage') { try { if (!isset($data['notification-url'])) { $data['notification-url'] = $this->notifyUrl; } $package = $this->loader->load($data, $class); if (isset($this->sourceMirrors[$package->getSourceType()])) { $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); } $package->setDistMirrors($this->distMirrors); $this->configurePackageTransportOptions($package); return $package; } catch (\Exception $e) { throw new \RuntimeException('Could not load package '.(isset($data['name']) ? $data['name'] : json_encode($data)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e); } } protected function fetchFile($filename, $cacheKey = null, $sha256 = null, $storeLastModifiedTime = false) { if (null === $cacheKey) { $cacheKey = $filename; $filename = $this->baseUrl.'/'.$filename; } // url-encode $ signs in URLs as bad proxies choke on them if (($pos = strpos($filename, '$')) && preg_match('{^https?://.*}i', $filename)) { $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1); } $retries = 3; while ($retries--) { try { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename); if ($this->eventDispatcher) { $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; $rfs = $preFileDownloadEvent->getRemoteFilesystem(); $json = $rfs->getContents($hostname, $filename, false); if ($sha256 && $sha256 !== hash('sha256', $json)) { // undo downgrade before trying again if http seems to be hijacked or modifying content somehow if ($this->allowSslDowngrade) { $this->url = str_replace('http://', 'https://', $this->url); $this->baseUrl = str_replace('http://', 'https://', $this->baseUrl); $filename = str_replace('http://', 'https://', $filename); } if ($retries) { usleep(100000); continue; } // TODO use scarier wording once we know for sure it doesn't do false positives anymore throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This should indicate a man-in-the-middle attack. Try running composer again and report this if you think it is a mistake.'); } $data = JsonFile::parseJson($json, $filename); if (!empty($data['warning'])) { $this->io->writeError('Warning from '.$this->url.': '.$data['warning'].''); } if (!empty($data['info'])) { $this->io->writeError('Info from '.$this->url.': '.$data['info'].''); } if ($cacheKey) { if ($storeLastModifiedTime) { $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified'); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = json_encode($data); } } $this->cache->write($cacheKey, $json); } break; } catch (\Exception $e) { if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } if ($retries) { usleep(100000); continue; } if ($e instanceof RepositorySecurityException) { throw $e; } if ($cacheKey && ($contents = $this->cache->read($cacheKey))) { if (!$this->degradedMode) { $this->io->writeError(''.$e->getMessage().''); $this->io->writeError(''.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date'); } $this->degradedMode = true; $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey); break; } throw $e; } } return $data; } protected function fetchFileIfLastModified($filename, $cacheKey, $lastModifiedTime) { $retries = 3; while ($retries--) { try { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename); if ($this->eventDispatcher) { $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; $rfs = $preFileDownloadEvent->getRemoteFilesystem(); $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))); $json = $rfs->getContents($hostname, $filename, false, $options); if ($json === '' && $rfs->findStatusCode($rfs->getLastHeaders()) === 304) { return true; } $data = JsonFile::parseJson($json, $filename); if (!empty($data['warning'])) { $this->io->writeError('Warning from '.$this->url.': '.$data['warning'].''); } if (!empty($data['info'])) { $this->io->writeError('Info from '.$this->url.': '.$data['info'].''); } $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified'); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = json_encode($data); } $this->cache->write($cacheKey, $json); return $data; } catch (\Exception $e) { if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } if ($retries) { usleep(100000); continue; } if (!$this->degradedMode) { $this->io->writeError(''.$e->getMessage().''); $this->io->writeError(''.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date'); } $this->degradedMode = true; return true; } } } /** * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url * * This should only be called once */ private function initializePartialPackages() { $rootData = $this->loadRootServerFile(); $this->partialPackagesByName = array(); foreach ($rootData['packages'] as $package => $versions) { $package = strtolower($package); foreach ($versions as $version) { $this->partialPackagesByName[$package][] = $version; if (!empty($version['provide']) && is_array($version['provide'])) { foreach ($version['provide'] as $provided => $providedVersion) { $this->partialPackagesByName[strtolower($provided)][] = $version; } } if (!empty($version['replace']) && is_array($version['replace'])) { foreach ($version['replace'] as $provided => $providedVersion) { $this->partialPackagesByName[strtolower($provided)][] = $version; } } } } // wipe rootData as it is fully consumed at this point and this saves some memory $this->rootData = true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Exception thrown when a package repository is utterly broken * * @author Jordi Boggiano */ class InvalidRepositoryException extends \Exception { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Installed array repository. * * This is used for serving the RootPackage inside an in-memory InstalledRepository * * @author Jordi Boggiano */ class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\RemoteFilesystem; /** * Repositories manager. * * @author Jordi Boggiano * @author Konstantin Kudryashov * @author François Pluchino */ class RepositoryManager { private $localRepository; private $repositories = array(); private $repositoryClasses = array(); private $io; private $config; private $eventDispatcher; private $rfs; public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) { $this->io = $io; $this->config = $config; $this->eventDispatcher = $eventDispatcher; $this->rfs = $rfs; } /** * Searches for a package by it's name and version in managed repositories. * * @param string $name package name * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface|null */ public function findPackage($name, $constraint) { foreach ($this->repositories as $repository) { if ($package = $repository->findPackage($name, $constraint)) { return $package; } } return null; } /** * Searches for all packages matching a name and optionally a version in managed repositories. * * @param string $name package name * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return array */ public function findPackages($name, $constraint) { $packages = array(); foreach ($this->repositories as $repository) { $packages = array_merge($packages, $repository->findPackages($name, $constraint)); } return $packages; } /** * Adds repository * * @param RepositoryInterface $repository repository instance */ public function addRepository(RepositoryInterface $repository) { $this->repositories[] = $repository; } /** * Adds a repository to the beginning of the chain * * This is useful when injecting additional repositories that should trump Packagist, e.g. from a plugin. * * @param RepositoryInterface $repository repository instance */ public function prependRepository(RepositoryInterface $repository) { array_unshift($this->repositories, $repository); } /** * Returns a new repository for a specific installation type. * * @param string $type repository type * @param array $config repository configuration * @param string $name repository name * @throws \InvalidArgumentException if repository for provided type is not registered * @return RepositoryInterface */ public function createRepository($type, $config, $name = null) { if (!isset($this->repositoryClasses[$type])) { throw new \InvalidArgumentException('Repository type is not registered: '.$type); } if (isset($config['packagist']) && false === $config['packagist']) { $this->io->writeError('Repository "'.$name.'" ('.json_encode($config).') has a packagist key which should be in its own repository definition'); } $class = $this->repositoryClasses[$type]; $reflMethod = new \ReflectionMethod($class, '__construct'); $params = $reflMethod->getParameters(); if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\RemoteFilesystem') { return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->rfs); } return new $class($config, $this->io, $this->config, $this->eventDispatcher); } /** * Stores repository class for a specific installation type. * * @param string $type installation type * @param string $class class name of the repo implementation */ public function setRepositoryClass($type, $class) { $this->repositoryClasses[$type] = $class; } /** * Returns all repositories, except local one. * * @return array */ public function getRepositories() { return $this->repositories; } /** * Sets local repository for the project. * * @param WritableRepositoryInterface $repository repository instance */ public function setLocalRepository(WritableRepositoryInterface $repository) { $this->localRepository = $repository; } /** * Returns local repository for the project. * * @return WritableRepositoryInterface */ public function getLocalRepository() { return $this->localRepository; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\XdebugHandler; use Composer\Package\CompletePackage; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginInterface; use Composer\Util\Silencer; /** * @author Jordi Boggiano */ class PlatformRepository extends ArrayRepository { const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[^/]+)$}i'; private $versionParser; /** * Defines overrides so that the platform can be mocked * * Should be an array of package name => version number mappings * * @var array */ private $overrides = array(); public function __construct(array $packages = array(), array $overrides = array()) { foreach ($overrides as $name => $version) { $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version); } parent::__construct($packages); } protected function initialize() { parent::initialize(); $this->versionParser = new VersionParser(); // Add each of the override versions as options. // Later we might even replace the extensions instead. foreach ($this->overrides as $override) { // Check that it's a platform package. if (!preg_match(self::PLATFORM_PACKAGE_REGEX, $override['name'])) { throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']); } $this->addOverriddenPackage($override); } $prettyVersion = PluginInterface::PLUGIN_API_VERSION; $version = $this->versionParser->normalize($prettyVersion); $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); $composerPluginApi->setDescription('The Composer Plugin API'); $this->addPackage($composerPluginApi); try { $prettyVersion = PHP_VERSION; $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', PHP_VERSION); $version = $this->versionParser->normalize($prettyVersion); } $php = new CompletePackage('php', $version, $prettyVersion); $php->setDescription('The PHP interpreter'); $this->addPackage($php); if (PHP_DEBUG) { $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion); $phpdebug->setDescription('The PHP interpreter, with debugging symbols'); $this->addPackage($phpdebug); } if (defined('PHP_ZTS') && PHP_ZTS) { $phpzts = new CompletePackage('php-zts', $version, $prettyVersion); $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety'); $this->addPackage($phpzts); } if (PHP_INT_SIZE === 8) { $php64 = new CompletePackage('php-64bit', $version, $prettyVersion); $php64->setDescription('The PHP interpreter, 64bit'); $this->addPackage($php64); } // The AF_INET6 constant is only defined if ext-sockets is available but // IPv6 support might still be available. if (defined('AF_INET6') || Silencer::call('inet_pton', '::') !== false) { $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); $this->addPackage($phpIpv6); } $loadedExtensions = get_loaded_extensions(); // Extensions scanning foreach ($loadedExtensions as $name) { if (in_array($name, array('standard', 'Core'))) { continue; } $reflExt = new \ReflectionExtension($name); $prettyVersion = $reflExt->getVersion(); $this->addExtension($name, $prettyVersion); } // Check for xdebug in a restarted process if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = strval(getenv(XdebugHandler::ENV_VERSION)))) { $this->addExtension('xdebug', $prettyVersion); } // Another quick loop, just for possible libraries // Doing it this way to know that functions or constants exist before // relying on them. foreach ($loadedExtensions as $name) { $prettyVersion = null; $description = 'The '.$name.' PHP library'; switch ($name) { case 'curl': $curlVersion = curl_version(); $prettyVersion = $curlVersion['version']; break; case 'iconv': $prettyVersion = ICONV_VERSION; break; case 'intl': $name = 'ICU'; if (defined('INTL_ICU_VERSION')) { $prettyVersion = INTL_ICU_VERSION; } else { $reflector = new \ReflectionExtension('intl'); ob_start(); $reflector->info(); $output = ob_get_clean(); preg_match('/^ICU version => (.*)$/m', $output, $matches); $prettyVersion = $matches[1]; } break; case 'libxml': $prettyVersion = LIBXML_DOTTED_VERSION; break; case 'openssl': $prettyVersion = preg_replace_callback('{^(?:OpenSSL|LibreSSL)?\s*([0-9.]+)([a-z]*).*}i', function ($match) { if (empty($match[2])) { return $match[1]; } // OpenSSL versions add another letter when they reach Z. // e.g. OpenSSL 0.9.8zh 3 Dec 2015 if (!preg_match('{^z*[a-z]$}', $match[2])) { // 0.9.8abc is garbage return 0; } $len = strlen($match[2]); $patchVersion = ($len - 1) * 26; // All Z $patchVersion += ord($match[2][$len - 1]) - 96; return $match[1].'.'.$patchVersion; }, OPENSSL_VERSION_TEXT); $description = OPENSSL_VERSION_TEXT; break; case 'pcre': $prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION); break; case 'uuid': $prettyVersion = phpversion('uuid'); break; case 'xsl': $prettyVersion = LIBXSLT_DOTTED_VERSION; break; default: // None handled extensions have no special cases, skip continue 2; } try { $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { continue; } $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); $lib->setDescription($description); $this->addPackage($lib); } if (defined('HHVM_VERSION')) { try { $prettyVersion = HHVM_VERSION; $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', HHVM_VERSION); $version = $this->versionParser->normalize($prettyVersion); } $hhvm = new CompletePackage('hhvm', $version, $prettyVersion); $hhvm->setDescription('The HHVM Runtime (64bit)'); $this->addPackage($hhvm); } } /** * {@inheritDoc} */ public function addPackage(PackageInterface $package) { // Skip if overridden if (isset($this->overrides[$package->getName()])) { $overrider = $this->findPackage($package->getName(), '*'); $overrider->setDescription($overrider->getDescription().' (actual: '.$package->getPrettyVersion().')'); return; } // Skip if PHP is overridden and we are adding a php-* package if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) { $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName()); $overrider->setDescription($overrider->getDescription().' (actual: '.$package->getPrettyVersion().')'); return; } parent::addPackage($package); } private function addOverriddenPackage(array $override, $name = null) { $version = $this->versionParser->normalize($override['version']); $package = new CompletePackage($name ?: $override['name'], $version, $override['version']); $package->setDescription('Package overridden via config.platform'); $package->setExtra(array('config.platform' => true)); parent::addPackage($package); return $package; } /** * Parses the version and adds a new package to the repository * * @param string $name * @param null|string $prettyVersion */ private function addExtension($name, $prettyVersion) { $extraDescription = null; try { $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $extraDescription = ' (actual version: '.$prettyVersion.')'; if (preg_match('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) { $prettyVersion = $match[1]; } else { $prettyVersion = '0'; } $version = $this->versionParser->normalize($prettyVersion); } $packageName = $this->buildPackageName($name); $ext = new CompletePackage($packageName, $version, $prettyVersion); $ext->setDescription('The '.$name.' PHP extension'.$extraDescription); $this->addPackage($ext); } private function buildPackageName($name) { return 'ext-' . str_replace(' ', '-', $name); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Thrown when a security problem, like a broken or missing signature * * @author Eric Daspet */ class RepositorySecurityException extends \Exception { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\PackageInterface; /** * Writable repository interface. * * @author Konstantin Kudryashov */ interface WritableRepositoryInterface extends RepositoryInterface { /** * Writes repository (f.e. to the disc). */ public function write(); /** * Adds package to the repository. * * @param PackageInterface $package package instance */ public function addPackage(PackageInterface $package); /** * Removes package from the repository. * * @param PackageInterface $package package instance */ public function removePackage(PackageInterface $package); /** * Get unique packages (at most one package of each name), with aliases resolved and removed. * * @return PackageInterface[] */ public function getCanonicalPackages(); /** * Forces a reload of all packages. */ public function reload(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\PackageInterface; /** * Repository interface. * * @author Nils Adermann * @author Konstantin Kudryashov * @author Jordi Boggiano */ interface RepositoryInterface extends \Countable { const SEARCH_FULLTEXT = 0; const SEARCH_NAME = 1; /** * Checks if specified package registered (installed). * * @param PackageInterface $package package instance * * @return bool */ public function hasPackage(PackageInterface $package); /** * Searches for the first match of a package by name and version. * * @param string $name package name * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface|null */ public function findPackage($name, $constraint); /** * Searches for all packages matching a name and optionally a version. * * @param string $name package name * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface[] */ public function findPackages($name, $constraint = null); /** * Returns list of registered packages. * * @return PackageInterface[] */ public function getPackages(); /** * Searches the repository for packages containing the query * * @param string $query search query * @param int $mode a set of SEARCH_* constants to search on, implementations should do a best effort only * * @return \array[] an array of array('name' => '...', 'description' => '...') */ public function search($query, $mode = 0); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\IO\IOInterface; use Composer\Semver\VersionParser as SemverVersionParser; use Composer\Package\Version\VersionParser; use Composer\Repository\Pear\ChannelReader; use Composer\Package\CompletePackage; use Composer\Repository\Pear\ChannelInfo; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\Link; use Composer\Semver\Constraint\Constraint; use Composer\Util\RemoteFilesystem; use Composer\Config; use Composer\Factory; /** * Builds list of package from PEAR channel. * * Packages read from channel are named as 'pear-{channelName}/{packageName}' * and has aliased as 'pear-{channelAlias}/{packageName}' * * @author Benjamin Eberlei * @author Jordi Boggiano */ class PearRepository extends ArrayRepository implements ConfigurableRepositoryInterface { private $url; private $io; private $rfs; private $versionParser; private $repoConfig; /** @var string vendor makes additional alias for each channel as {prefix}/{packagename}. It allows smoother * package transition to composer-like repositories. */ private $vendorAlias; public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, RemoteFilesystem $rfs = null) { parent::__construct(); if (!preg_match('{^https?://}', $repoConfig['url'])) { $repoConfig['url'] = 'http://'.$repoConfig['url']; } $urlBits = parse_url($repoConfig['url']); if (empty($urlBits['scheme']) || empty($urlBits['host'])) { throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$repoConfig['url']); } $this->url = rtrim($repoConfig['url'], '/'); $this->io = $io; $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config); $this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null; $this->versionParser = new VersionParser(); $this->repoConfig = $repoConfig; } public function getRepoConfig() { return $this->repoConfig; } protected function initialize() { parent::initialize(); $this->io->writeError('Initializing PEAR repository '.$this->url); $reader = new ChannelReader($this->rfs); try { $channelInfo = $reader->read($this->url); } catch (\Exception $e) { $this->io->writeError('PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().''); return; } $packages = $this->buildComposerPackages($channelInfo, $this->versionParser); foreach ($packages as $package) { $this->addPackage($package); } } /** * Builds CompletePackages from PEAR package definition data. * * @param ChannelInfo $channelInfo * @param SemverVersionParser $versionParser * @return CompletePackage */ private function buildComposerPackages(ChannelInfo $channelInfo, SemverVersionParser $versionParser) { $result = array(); foreach ($channelInfo->getPackages() as $packageDefinition) { foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { try { $normalizedVersion = $versionParser->normalize($version); } catch (\UnexpectedValueException $e) { $this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage(), true, IOInterface::VERBOSE); continue; } $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName()); // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text() // but this location is 'de-facto' standard $urlBits = parse_url($this->url); $scheme = (isset($urlBits['scheme']) && 'https' === $urlBits['scheme'] && extension_loaded('openssl')) ? 'https' : 'http'; $distUrl = "{$scheme}://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz"; $requires = array(); $suggests = array(); $conflicts = array(); $replaces = array(); // alias package only when its channel matches repository channel, // cause we've know only repository channel alias if ($channelInfo->getName() == $packageDefinition->getChannelName()) { $composerPackageAlias = $this->buildComposerPackageName($channelInfo->getAlias(), $packageDefinition->getPackageName()); $aliasConstraint = new Constraint('==', $normalizedVersion); $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); } // alias package with user-specified prefix. it makes private pear channels looks like composer's. if (!empty($this->vendorAlias) && ($this->vendorAlias != 'pear-'.$channelInfo->getAlias() || $channelInfo->getName() != $packageDefinition->getChannelName()) ) { $composerPackageAlias = "{$this->vendorAlias}/{$packageDefinition->getPackageName()}"; $aliasConstraint = new Constraint('==', $normalizedVersion); $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); } foreach ($releaseInfo->getDependencyInfo()->getRequires() as $dependencyConstraint) { $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint()); $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint()); switch ($dependencyConstraint->getType()) { case 'required': $requires[] = $link; break; case 'conflicts': $conflicts[] = $link; break; case 'replaces': $replaces[] = $link; break; } } foreach ($releaseInfo->getDependencyInfo()->getOptionals() as $group => $dependencyConstraints) { foreach ($dependencyConstraints as $dependencyConstraint) { $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); $suggests[$group.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint(); } } $package = new CompletePackage($composerPackageName, $normalizedVersion, $version); $package->setType('pear-library'); $package->setDescription($packageDefinition->getDescription()); $package->setLicense(array($packageDefinition->getLicense())); $package->setDistType('file'); $package->setDistUrl($distUrl); $package->setAutoload(array('classmap' => array(''))); $package->setIncludePaths(array('/')); $package->setRequires($requires); $package->setConflicts($conflicts); $package->setSuggests($suggests); $package->setReplaces($replaces); $result[] = $package; } } return $result; } private function buildComposerPackageName($channelName, $packageName) { if ('php' === $channelName) { return "php"; } if ('ext' === $channelName) { return "ext-{$packageName}"; } return "pear-{$channelName}/{$packageName}"; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\RootPackageInterface; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; use Composer\Package\Link; /** * Common ancestor class for generic repository functionality. * * @author Niels Keurentjes */ abstract class BaseRepository implements RepositoryInterface { /** * Returns a list of links causing the requested needle packages to be installed, as an associative array with the * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship * as values. If recursive lookup was requested a third value is returned containing an identically formed array up * to the root package. That third value will be false in case a circular recursion was detected. * * @param string|string[] $needle The package name(s) to inspect. * @param ConstraintInterface|null $constraint Optional constraint to filter by. * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed. * @param bool $recurse Whether to recursively expand the requirement tree up to the root package. * @param string[] $packagesFound Used internally when recurring * @return array An associative array of arrays as described above. */ public function getDependents($needle, $constraint = null, $invert = false, $recurse = true, $packagesFound = null) { $needles = (array) $needle; $results = array(); // initialize the array with the needles before any recursion occurs if (null === $packagesFound) { $packagesFound = $needles; } // locate root package for use below $rootPackage = null; foreach ($this->getPackages() as $package) { if ($package instanceof RootPackageInterface) { $rootPackage = $package; break; } } // Loop over all currently installed packages. foreach ($this->getPackages() as $package) { $links = $package->getRequires(); // each loop needs its own "tree" as we want to show the complete dependent set of every needle // without warning all the time about finding circular deps $packagesInTree = $packagesFound; // Replacements are considered valid reasons for a package to be installed during forward resolution if (!$invert) { $links += $package->getReplaces(); } // Require-dev is only relevant for the root package if ($package instanceof RootPackageInterface) { $links += $package->getDevRequires(); } // Cross-reference all discovered links to the needles foreach ($links as $link) { foreach ($needles as $needle) { if ($link->getTarget() === $needle) { if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) { // already displayed this node's dependencies, cutting short if (in_array($link->getSource(), $packagesInTree)) { $results[$link->getSource()] = array($package, $link, false); continue; } $packagesInTree[] = $link->getSource(); $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array(); $results[$link->getSource()] = array($package, $link, $dependents); } } } } // When inverting, we need to check for conflicts of the needles against installed packages if ($invert && in_array($package->getName(), $needles)) { foreach ($package->getConflicts() as $link) { foreach ($this->findPackages($link->getTarget()) as $pkg) { $version = new Constraint('=', $pkg->getVersion()); if ($link->getConstraint()->matches($version) === $invert) { $results[] = array($package, $link, false); } } } } // When inverting, we need to check for conflicts of the needles' requirements against installed packages if ($invert && $constraint && in_array($package->getName(), $needles) && $constraint->matches(new Constraint('=', $package->getVersion()))) { foreach ($package->getRequires() as $link) { if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { if ($this->findPackage($link->getTarget(), $link->getConstraint())) { continue; } $platformPkg = $this->findPackage($link->getTarget(), '*'); $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing'; $results[] = array($package, new Link($package->getName(), $link->getTarget(), null, 'requires', $link->getPrettyConstraint().' '.$description), false); continue; } foreach ($this->getPackages() as $pkg) { if (!in_array($link->getTarget(), $pkg->getNames())) { continue; } $version = new Constraint('=', $pkg->getVersion()); if (!$link->getConstraint()->matches($version)) { // if we have a root package (we should but can not guarantee..) we show // the root requires as well to perhaps allow to find an issue there if ($rootPackage) { foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) { if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) { $results[] = array($package, $link, false); $results[] = array($rootPackage, $rootReq, false); continue 3; } } $results[] = array($package, $link, false); $results[] = array($rootPackage, new Link($rootPackage->getName(), $link->getTarget(), null, 'does not require', 'but ' . $pkg->getPrettyVersion() . ' is installed'), false); } else { // no root so let's just print whatever we found $results[] = array($package, $link, false); } } continue 2; } } } } ksort($results); return $results; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; use Composer\Util\RemoteFilesystem; /** * PEAR Channel package reader. * * Reads channel packages info from and builds Package's * * @author Alexey Prilipko */ class ChannelReader extends BaseChannelReader { /** @var array of ('xpath test' => 'rest implementation') */ private $readerMap; public function __construct(RemoteFilesystem $rfs) { parent::__construct($rfs); $rest10reader = new ChannelRest10Reader($rfs); $rest11reader = new ChannelRest11Reader($rfs); $this->readerMap = array( 'REST1.3' => $rest11reader, 'REST1.2' => $rest11reader, 'REST1.1' => $rest11reader, 'REST1.0' => $rest10reader, ); } /** * Reads PEAR channel through REST interface and builds list of packages * * @param $url string PEAR Channel url * @throws \UnexpectedValueException * @return ChannelInfo */ public function read($url) { $xml = $this->requestXml($url, "/channel.xml"); $channelName = (string) $xml->name; $channelAlias = (string) $xml->suggestedalias; $supportedVersions = array_keys($this->readerMap); $selectedRestVersion = $this->selectRestVersion($xml, $supportedVersions); if (!$selectedRestVersion) { throw new \UnexpectedValueException(sprintf('PEAR repository %s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions))); } $reader = $this->readerMap[$selectedRestVersion['version']]; $packageDefinitions = $reader->read($selectedRestVersion['baseUrl']); return new ChannelInfo($channelName, $channelAlias, $packageDefinitions); } /** * Reads channel supported REST interfaces and selects one of them * * @param $channelXml \SimpleXMLElement * @param $supportedVersions string[] supported PEAR REST protocols * @return array|null hash with selected version and baseUrl */ private function selectRestVersion($channelXml, $supportedVersions) { $channelXml->registerXPathNamespace('ns', self::CHANNEL_NS); foreach ($supportedVersions as $version) { $xpathTest = "ns:servers/ns:*/ns:rest/ns:baseurl[@type='{$version}']"; $testResult = $channelXml->xpath($xpathTest); foreach ($testResult as $result) { // Choose first https:// option. $result = (string) $result; if (preg_match('{^https://}i', $result)) { return array('version' => $version, 'baseUrl' => $result); } } // Fallback to non-https if it does not exist. if (count($testResult) > 0) { return array('version' => $version, 'baseUrl' => (string) $testResult[0]); } } return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; /** * PEAR package release dependency info * * @author Alexey Prilipko */ class DependencyConstraint { private $type; private $constraint; private $channelName; private $packageName; /** * @param string $type * @param string $constraint * @param string $channelName * @param string $packageName */ public function __construct($type, $constraint, $channelName, $packageName) { $this->type = $type; $this->constraint = $constraint; $this->channelName = $channelName; $this->packageName = $packageName; } public function getChannelName() { return $this->channelName; } public function getConstraint() { return $this->constraint; } public function getPackageName() { return $this->packageName; } public function getType() { return $this->type; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; use Composer\Util\RemoteFilesystem; /** * Base PEAR Channel reader. * * Provides xml namespaces and red * * @author Alexey Prilipko */ abstract class BaseChannelReader { /** * PEAR REST Interface namespaces */ const CHANNEL_NS = 'http://pear.php.net/channel-1.0'; const ALL_CATEGORIES_NS = 'http://pear.php.net/dtd/rest.allcategories'; const CATEGORY_PACKAGES_INFO_NS = 'http://pear.php.net/dtd/rest.categorypackageinfo'; const ALL_PACKAGES_NS = 'http://pear.php.net/dtd/rest.allpackages'; const ALL_RELEASES_NS = 'http://pear.php.net/dtd/rest.allreleases'; const PACKAGE_INFO_NS = 'http://pear.php.net/dtd/rest.package'; /** @var RemoteFilesystem */ private $rfs; protected function __construct(RemoteFilesystem $rfs) { $this->rfs = $rfs; } /** * Read content from remote filesystem. * * @param $origin string server * @param $path string relative path to content * @throws \UnexpectedValueException * @return \SimpleXMLElement */ protected function requestContent($origin, $path) { $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); $content = $this->rfs->getContents($origin, $url, false); if (!$content) { throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.'); } return str_replace('http://pear.php.net/rest/', 'https://pear.php.net/rest/', $content); } /** * Read xml content from remote filesystem * * @param $origin string server * @param $path string relative path to content * @throws \UnexpectedValueException * @return \SimpleXMLElement */ protected function requestXml($origin, $path) { // http://components.ez.no/p/packages.xml is malformed. to read it we must ignore parsing errors. $xml = simplexml_load_string($this->requestContent($origin, $path), "SimpleXMLElement", LIBXML_NOERROR); if (false === $xml) { throw new \UnexpectedValueException(sprintf('The PEAR channel at ' . $origin . ' is broken. (Invalid XML at file `%s`)', $path)); } return $xml; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; /** * Read PEAR packages using REST 1.1 interface * * At version 1.1 package descriptions read from: * {baseUrl}/c/categories.xml * {baseUrl}/c/{category}/packagesinfo.xml * * @author Alexey Prilipko */ class ChannelRest11Reader extends BaseChannelReader { private $dependencyReader; public function __construct($rfs) { parent::__construct($rfs); $this->dependencyReader = new PackageDependencyParser(); } /** * Reads package descriptions using PEAR Rest 1.1 interface * * @param $baseUrl string base Url interface * * @return PackageInfo[] */ public function read($baseUrl) { return $this->readChannelPackages($baseUrl); } /** * Read list of channel categories from * {baseUrl}/c/categories.xml * * @param $baseUrl string * @return PackageInfo[] */ private function readChannelPackages($baseUrl) { $result = array(); $xml = $this->requestXml($baseUrl, "/c/categories.xml"); $xml->registerXPathNamespace('ns', self::ALL_CATEGORIES_NS); foreach ($xml->xpath('ns:c') as $node) { $categoryName = (string) $node; $categoryPackages = $this->readCategoryPackages($baseUrl, $categoryName); $result = array_merge($result, $categoryPackages); } return $result; } /** * Read packages from * {baseUrl}/c/{category}/packagesinfo.xml * * @param $baseUrl string * @param $categoryName string * @return PackageInfo[] */ private function readCategoryPackages($baseUrl, $categoryName) { $result = array(); $categoryPath = '/c/'.urlencode($categoryName).'/packagesinfo.xml'; $xml = $this->requestXml($baseUrl, $categoryPath); $xml->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS); foreach ($xml->xpath('ns:pi') as $node) { $packageInfo = $this->parsePackage($node); $result[] = $packageInfo; } return $result; } /** * Parses package node. * * @param $packageInfo \SimpleXMLElement xml element describing package * @return PackageInfo */ private function parsePackage($packageInfo) { $packageInfo->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS); $channelName = (string) $packageInfo->p->c; $packageName = (string) $packageInfo->p->n; $license = (string) $packageInfo->p->l; $shortDescription = (string) $packageInfo->p->s; $description = (string) $packageInfo->p->d; $dependencies = array(); foreach ($packageInfo->xpath('ns:deps') as $node) { $dependencyVersion = (string) $node->v; $dependencyArray = unserialize((string) $node->d); $dependencyInfo = $this->dependencyReader->buildDependencyInfo($dependencyArray); $dependencies[$dependencyVersion] = $dependencyInfo; } $releases = array(); $releasesInfo = $packageInfo->xpath('ns:a/ns:r'); if ($releasesInfo) { foreach ($releasesInfo as $node) { $releaseVersion = (string) $node->v; $releaseStability = (string) $node->s; $releases[$releaseVersion] = new ReleaseInfo( $releaseStability, isset($dependencies[$releaseVersion]) ? $dependencies[$releaseVersion] : new DependencyInfo(array(), array()) ); } } return new PackageInfo( $channelName, $packageName, $license, $shortDescription, $description, $releases ); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; /** * PEAR package release dependency info * * @author Alexey Prilipko */ class DependencyInfo { private $requires; private $optionals; /** * @param DependencyConstraint[] $requires list of requires/conflicts/replaces * @param array $optionals [groupName => DependencyConstraint[]] list of optional groups */ public function __construct($requires, $optionals) { $this->requires = $requires; $this->optionals = $optionals; } /** * @return DependencyConstraint[] list of requires/conflicts/replaces */ public function getRequires() { return $this->requires; } /** * @return array [groupName => DependencyConstraint[]] list of optional groups */ public function getOptionals() { return $this->optionals; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; /** * PEAR Package info * * @author Alexey Prilipko */ class PackageInfo { private $channelName; private $packageName; private $license; private $shortDescription; private $description; private $releases; /** * @param string $channelName * @param string $packageName * @param string $license * @param string $shortDescription * @param string $description * @param ReleaseInfo[] $releases associative array maps release version to release info */ public function __construct($channelName, $packageName, $license, $shortDescription, $description, $releases) { $this->channelName = $channelName; $this->packageName = $packageName; $this->license = $license; $this->shortDescription = $shortDescription; $this->description = $description; $this->releases = $releases; } /** * @return string the package channel name */ public function getChannelName() { return $this->channelName; } /** * @return string the package name */ public function getPackageName() { return $this->packageName; } /** * @return string the package description */ public function getDescription() { return $this->description; } /** * @return string the package short description */ public function getShortDescription() { return $this->shortDescription; } /** * @return string the package license */ public function getLicense() { return $this->license; } /** * @return ReleaseInfo[] */ public function getReleases() { return $this->releases; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; use Composer\Downloader\TransportException; /** * Read PEAR packages using REST 1.0 interface * * At version 1.0 package descriptions read from: * {baseUrl}/p/packages.xml * {baseUrl}/p/{package}/info.xml * {baseUrl}/p/{package}/allreleases.xml * {baseUrl}/p/{package}/deps.{version}.txt * * @author Alexey Prilipko */ class ChannelRest10Reader extends BaseChannelReader { private $dependencyReader; public function __construct($rfs) { parent::__construct($rfs); $this->dependencyReader = new PackageDependencyParser(); } /** * Reads package descriptions using PEAR Rest 1.0 interface * * @param $baseUrl string base Url interface * * @return PackageInfo[] */ public function read($baseUrl) { return $this->readPackages($baseUrl); } /** * Read list of packages from * {baseUrl}/p/packages.xml * * @param $baseUrl string * @return PackageInfo[] */ private function readPackages($baseUrl) { $result = array(); $xmlPath = '/p/packages.xml'; $xml = $this->requestXml($baseUrl, $xmlPath); $xml->registerXPathNamespace('ns', self::ALL_PACKAGES_NS); foreach ($xml->xpath('ns:p') as $node) { $packageName = (string) $node; $packageInfo = $this->readPackage($baseUrl, $packageName); $result[] = $packageInfo; } return $result; } /** * Read package info from * {baseUrl}/p/{package}/info.xml * * @param $baseUrl string * @param $packageName string * @return PackageInfo */ private function readPackage($baseUrl, $packageName) { $xmlPath = '/p/' . strtolower($packageName) . '/info.xml'; $xml = $this->requestXml($baseUrl, $xmlPath); $xml->registerXPathNamespace('ns', self::PACKAGE_INFO_NS); $channelName = (string) $xml->c; $packageName = (string) $xml->n; $license = (string) $xml->l; $shortDescription = (string) $xml->s; $description = (string) $xml->d; return new PackageInfo( $channelName, $packageName, $license, $shortDescription, $description, $this->readPackageReleases($baseUrl, $packageName) ); } /** * Read package releases from * {baseUrl}/p/{package}/allreleases.xml * * @param $baseUrl string * @param $packageName string * @throws \Composer\Downloader\TransportException|\Exception * @return ReleaseInfo[] hash array with keys as version numbers */ private function readPackageReleases($baseUrl, $packageName) { $result = array(); try { $xmlPath = '/r/' . strtolower($packageName) . '/allreleases.xml'; $xml = $this->requestXml($baseUrl, $xmlPath); $xml->registerXPathNamespace('ns', self::ALL_RELEASES_NS); foreach ($xml->xpath('ns:r') as $node) { $releaseVersion = (string) $node->v; $releaseStability = (string) $node->s; try { $result[$releaseVersion] = new ReleaseInfo( $releaseStability, $this->readPackageReleaseDependencies($baseUrl, $packageName, $releaseVersion) ); } catch (TransportException $exception) { if ($exception->getCode() != 404) { throw $exception; } } } } catch (TransportException $exception) { if ($exception->getCode() != 404) { throw $exception; } } return $result; } /** * Read package dependencies from * {baseUrl}/p/{package}/deps.{version}.txt * * @param $baseUrl string * @param $packageName string * @param $version string * @return DependencyInfo[] */ private function readPackageReleaseDependencies($baseUrl, $packageName, $version) { $dependencyReader = new PackageDependencyParser(); $depthPath = '/r/' . strtolower($packageName) . '/deps.' . $version . '.txt'; $content = $this->requestContent($baseUrl, $depthPath); $dependencyArray = unserialize($content); $result = $dependencyReader->buildDependencyInfo($dependencyArray); return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; /** * Read PEAR packages using REST 1.0 interface * * @author Alexey Prilipko */ class PackageDependencyParser { /** * Builds dependency information. It detects used package.xml format. * * @param $depArray array * @return DependencyInfo */ public function buildDependencyInfo($depArray) { if (!is_array($depArray)) { return new DependencyInfo(array(), array()); } if (!$this->isHash($depArray)) { return new DependencyInfo($this->buildDependency10Info($depArray), array()); } return $this->buildDependency20Info($depArray); } /** * Builds dependency information from package.xml 1.0 format * * https://pear.php.net/manual/en/guide.developers.package2.dependencies.php * * package.xml 1.0 format consists of array of * { type="php|os|sapi|ext|pkg" rel="has|not|eq|ge|gt|le|lt" optional="yes" * channel="channelName" name="extName|packageName" } * * @param $depArray array Dependency data in package.xml 1.0 format * @return DependencyConstraint[] */ private function buildDependency10Info($depArray) { static $dep10toOperatorMap = array('has' => '==', 'eq' => '==', 'ge' => '>=', 'gt' => '>', 'le' => '<=', 'lt' => '<', 'not' => '!='); $result = array(); foreach ($depArray as $depItem) { if (empty($depItem['rel']) || !array_key_exists($depItem['rel'], $dep10toOperatorMap)) { // 'unknown rel type:' . $depItem['rel']; continue; } $depType = !empty($depItem['optional']) && 'yes' == $depItem['optional'] ? 'optional' : 'required'; $depType = 'not' == $depItem['rel'] ? 'conflicts' : $depType; $depVersion = !empty($depItem['version']) ? $this->parseVersion($depItem['version']) : '*'; // has & not are special operators that does not requires version $depVersionConstraint = ('has' == $depItem['rel'] || 'not' == $depItem['rel']) && '*' == $depVersion ? '*' : $dep10toOperatorMap[$depItem['rel']] . $depVersion; switch ($depItem['type']) { case 'php': $depChannelName = 'php'; $depPackageName = ''; break; case 'pkg': $depChannelName = !empty($depItem['channel']) ? $depItem['channel'] : 'pear.php.net'; $depPackageName = $depItem['name']; break; case 'ext': $depChannelName = 'ext'; $depPackageName = $depItem['name']; break; case 'os': case 'sapi': $depChannelName = ''; $depPackageName = ''; break; default: $depChannelName = ''; $depPackageName = ''; break; } if ('' != $depChannelName) { $result[] = new DependencyConstraint( $depType, $depVersionConstraint, $depChannelName, $depPackageName ); } } return $result; } /** * Builds dependency information from package.xml 2.0 format * * @param $depArray array Dependency data in package.xml 1.0 format * @return DependencyInfo */ private function buildDependency20Info($depArray) { $result = array(); $optionals = array(); $defaultOptionals = array(); foreach ($depArray as $depType => $depTypeGroup) { if (!is_array($depTypeGroup)) { continue; } if ('required' == $depType || 'optional' == $depType) { foreach ($depTypeGroup as $depItemType => $depItem) { switch ($depItemType) { case 'php': $result[] = new DependencyConstraint( $depType, $this->parse20VersionConstraint($depItem), 'php', '' ); break; case 'package': $deps = $this->buildDepPackageConstraints($depItem, $depType); $result = array_merge($result, $deps); break; case 'extension': $deps = $this->buildDepExtensionConstraints($depItem, $depType); $result = array_merge($result, $deps); break; case 'subpackage': $deps = $this->buildDepPackageConstraints($depItem, 'replaces'); $defaultOptionals += $deps; break; case 'os': case 'pearinstaller': break; default: break; } } } elseif ('group' == $depType) { if ($this->isHash($depTypeGroup)) { $depTypeGroup = array($depTypeGroup); } foreach ($depTypeGroup as $depItem) { $groupName = $depItem['attribs']['name']; if (!isset($optionals[$groupName])) { $optionals[$groupName] = array(); } if (isset($depItem['subpackage'])) { $optionals[$groupName] += $this->buildDepPackageConstraints($depItem['subpackage'], 'replaces'); } else { $result += $this->buildDepPackageConstraints($depItem['package'], 'optional'); } } } } if (count($defaultOptionals) > 0) { $optionals['*'] = $defaultOptionals; } return new DependencyInfo($result, $optionals); } /** * Builds dependency constraint of 'extension' type * * @param $depItem array dependency constraint or array of dependency constraints * @param $depType string target type of building constraint. * @return DependencyConstraint[] */ private function buildDepExtensionConstraints($depItem, $depType) { if ($this->isHash($depItem)) { $depItem = array($depItem); } $result = array(); foreach ($depItem as $subDepItem) { $depChannelName = 'ext'; $depPackageName = $subDepItem['name']; $depVersionConstraint = $this->parse20VersionConstraint($subDepItem); $result[] = new DependencyConstraint( $depType, $depVersionConstraint, $depChannelName, $depPackageName ); } return $result; } /** * Builds dependency constraint of 'package' type * * @param $depItem array dependency constraint or array of dependency constraints * @param $depType string target type of building constraint. * @return DependencyConstraint[] */ private function buildDepPackageConstraints($depItem, $depType) { if ($this->isHash($depItem)) { $depItem = array($depItem); } $result = array(); foreach ($depItem as $subDepItem) { if (!array_key_exists('channel', $subDepItem)) { $subDepItem['channel'] = $subDepItem['uri']; } $depChannelName = $subDepItem['channel']; $depPackageName = $subDepItem['name']; $depVersionConstraint = $this->parse20VersionConstraint($subDepItem); if (isset($subDepItem['conflicts'])) { $depType = 'conflicts'; } $result[] = new DependencyConstraint( $depType, $depVersionConstraint, $depChannelName, $depPackageName ); } return $result; } /** * Parses version constraint * * @param array $data array containing several 'min', 'max', 'has', 'exclude' and other keys. * @return string */ private function parse20VersionConstraint(array $data) { static $dep20toOperatorMap = array('has' => '==', 'min' => '>=', 'max' => '<=', 'exclude' => '!='); $versions = array(); $values = array_intersect_key($data, $dep20toOperatorMap); if (0 == count($values)) { return '*'; } if (isset($values['min']) && isset($values['exclude']) && $data['min'] == $data['exclude']) { $versions[] = '>' . $this->parseVersion($values['min']); } elseif (isset($values['max']) && isset($values['exclude']) && $data['max'] == $data['exclude']) { $versions[] = '<' . $this->parseVersion($values['max']); } else { foreach ($values as $op => $version) { if ('exclude' == $op && is_array($version)) { foreach ($version as $versionPart) { $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($versionPart); } } else { $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($version); } } } return implode(',', $versions); } /** * Softened version parser * * @param $version * @return null|string */ private function parseVersion($version) { if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) { $version = $matches[1] .(!empty($matches[2]) ? $matches[2] : '.0') .(!empty($matches[3]) ? $matches[3] : '.0') .(!empty($matches[4]) ? $matches[4] : '.0'); return $version; } return null; } /** * Test if array is associative or hash type * * @param array $array * @return bool */ private function isHash(array $array) { return !array_key_exists(1, $array) && !array_key_exists(0, $array); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; /** * PEAR package release info * * @author Alexey Prilipko */ class ReleaseInfo { private $stability; private $dependencyInfo; /** * @param string $stability * @param DependencyInfo $dependencyInfo */ public function __construct($stability, $dependencyInfo) { $this->stability = $stability; $this->dependencyInfo = $dependencyInfo; } /** * @return DependencyInfo release dependencies */ public function getDependencyInfo() { return $this->dependencyInfo; } /** * @return string release stability */ public function getStability() { return $this->stability; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Pear; /** * PEAR channel info * * @author Alexey Prilipko */ class ChannelInfo { private $name; private $alias; private $packages; /** * @param string $name * @param string $alias * @param PackageInfo[] $packages */ public function __construct($name, $alias, array $packages) { $this->name = $name; $this->alias = $alias; $this->packages = $packages; } /** * Name of the channel * * @return string */ public function getName() { return $this->name; } /** * Alias of the channel * * @return string */ public function getAlias() { return $this->alias; } /** * List of channel packages * * @return PackageInfo[] */ public function getPackages() { return $this->packages; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\AutoloadGenerator; use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\Downloader\DownloadManager; use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; use Composer\Installer\InstallerEvents; use Composer\Installer\NoopInstaller; use Composer\Installer\SuggestedPackagesReporter; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; use Composer\Package\CompletePackage; use Composer\Package\Link; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; use Composer\Semver\Constraint\Constraint; use Composer\Package\Locker; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; use Composer\Repository\CompositeRepository; use Composer\Repository\InstalledArrayRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryManager; use Composer\Repository\WritableRepositoryInterface; use Composer\Script\ScriptEvents; /** * @author Jordi Boggiano * @author Beau Simensen * @author Konstantin Kudryashov * @author Nils Adermann */ class Installer { /** * @var IOInterface */ protected $io; /** * @var Config */ protected $config; /** * @var RootPackageInterface */ protected $package; /** * @var DownloadManager */ protected $downloadManager; /** * @var RepositoryManager */ protected $repositoryManager; /** * @var Locker */ protected $locker; /** * @var InstallationManager */ protected $installationManager; /** * @var EventDispatcher */ protected $eventDispatcher; /** * @var AutoloadGenerator */ protected $autoloadGenerator; protected $preferSource = false; protected $preferDist = false; protected $optimizeAutoloader = false; protected $classMapAuthoritative = false; protected $apcuAutoloader = false; protected $devMode = false; protected $dryRun = false; protected $verbose = false; protected $update = false; protected $dumpAutoloader = true; protected $runScripts = true; protected $ignorePlatformReqs = false; protected $preferStable = false; protected $preferLowest = false; protected $skipSuggest = false; protected $writeLock = true; protected $executeOperations = true; /** * Array of package names/globs flagged for update * * @var array|null */ protected $updateWhitelist = null; protected $whitelistDependencies = false; /** * @var SuggestedPackagesReporter */ protected $suggestedPackagesReporter; /** * @var RepositoryInterface */ protected $additionalInstalledRepository; /** * Constructor * * @param IOInterface $io * @param Config $config * @param RootPackageInterface $package * @param DownloadManager $downloadManager * @param RepositoryManager $repositoryManager * @param Locker $locker * @param InstallationManager $installationManager * @param EventDispatcher $eventDispatcher * @param AutoloadGenerator $autoloadGenerator */ public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator) { $this->io = $io; $this->config = $config; $this->package = $package; $this->downloadManager = $downloadManager; $this->repositoryManager = $repositoryManager; $this->locker = $locker; $this->installationManager = $installationManager; $this->eventDispatcher = $eventDispatcher; $this->autoloadGenerator = $autoloadGenerator; } /** * Run installation (or update) * * @throws \Exception * @return int 0 on success or a positive error code on failure */ public function run() { // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands // of PHP objects, the GC can spend quite some time walking the tree of references looking // for stuff to collect while there is nothing to collect. This slows things down dramatically // and turning it off results in much better performance. Do not try this at home however. gc_collect_cycles(); gc_disable(); // Force update if there is no lock file present if (!$this->update && !$this->locker->isLocked()) { $this->update = true; } if ($this->dryRun) { $this->verbose = true; $this->runScripts = false; $this->executeOperations = false; $this->writeLock = false; $this->dumpAutoloader = false; $this->installationManager->addInstaller(new NoopInstaller); $this->mockLocalRepositories($this->repositoryManager); } if ($this->runScripts) { // dispatch pre event $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } $this->downloadManager->setPreferSource($this->preferSource); $this->downloadManager->setPreferDist($this->preferDist); // create installed repo, this contains all local packages + platform packages (php & extensions) $localRepo = $this->repositoryManager->getLocalRepository(); if ($this->update) { $platformOverrides = $this->config->get('platform') ?: array(); } else { $platformOverrides = $this->locker->getPlatformOverrides(); } $platformRepo = new PlatformRepository(array(), $platformOverrides); $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo); $aliases = $this->getRootAliases(); $this->aliasPlatformPackages($platformRepo, $aliases); if (!$this->suggestedPackagesReporter) { $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io); } try { list($res, $devPackages) = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases); if ($res !== 0) { return $res; } } catch (\Exception $e) { if ($this->executeOperations) { $this->installationManager->notifyInstalls($this->io); } throw $e; } if ($this->executeOperations) { $this->installationManager->notifyInstalls($this->io); } // output suggestions if we're in dev mode if ($this->devMode && !$this->skipSuggest) { $this->suggestedPackagesReporter->output($installedRepo); } # Find abandoned packages and warn user foreach ($localRepo->getPackages() as $package) { if (!$package instanceof CompletePackage || !$package->isAbandoned()) { continue; } $replacement = (is_string($package->getReplacementPackage())) ? 'Use ' . $package->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $this->io->writeError( sprintf( "Package %s is abandoned, you should avoid using it. %s.", $package->getPrettyName(), $replacement ) ); } // write lock if ($this->update && $this->writeLock) { $localRepo->reload(); $platformReqs = $this->extractPlatformRequirements($this->package->getRequires()); $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires()); $updatedLock = $this->locker->setLockData( array_diff($localRepo->getCanonicalPackages(), $devPackages), $devPackages, $platformReqs, $platformDevReqs, $aliases, $this->package->getMinimumStability(), $this->package->getStabilityFlags(), $this->preferStable || $this->package->getPreferStable(), $this->preferLowest, $this->config->get('platform') ?: array() ); if ($updatedLock) { $this->io->writeError('Writing lock file'); } } if ($this->dumpAutoloader) { // write autoloader if ($this->optimizeAutoloader) { $this->io->writeError('Generating optimized autoload files'); } else { $this->io->writeError('Generating autoload files'); } $this->autoloadGenerator->setDevMode($this->devMode); $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative); $this->autoloadGenerator->setApcu($this->apcuAutoloader); $this->autoloadGenerator->setRunScripts($this->runScripts); $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); } if ($this->runScripts) { $devMode = (int) $this->devMode; putenv("COMPOSER_DEV_MODE=$devMode"); // dispatch post event $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } if ($this->executeOperations) { // force binaries re-generation in case they are missing foreach ($localRepo->getPackages() as $package) { $this->installationManager->ensureBinariesPresence($package); } $vendorDir = $this->config->get('vendor-dir'); if (is_dir($vendorDir)) { // suppress errors as this fails sometimes on OSX for no apparent reason // see https://github.com/composer/composer/issues/4070#issuecomment-129792748 @touch($vendorDir); } } // re-enable GC except on HHVM which triggers a warning here if (!defined('HHVM_VERSION')) { gc_enable(); } return 0; } /** * @param RepositoryInterface $localRepo * @param RepositoryInterface $installedRepo * @param PlatformRepository $platformRepo * @param array $aliases * @return array [int, PackageInterfaces[]|null] with the exit code and an array of dev packages on update, or null on install */ protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases) { // init vars $lockedRepository = null; $repositories = null; // initialize locked repo if we are installing from lock or in a partial update // and a lock file is present as we need to force install non-whitelisted lock file // packages in that case if (!$this->update || (!empty($this->updateWhitelist) && $this->locker->isLocked())) { try { $lockedRepository = $this->locker->getLockedRepository($this->devMode); } catch (\RuntimeException $e) { // if there are dev requires, then we really can not install if ($this->package->getDevRequires()) { throw $e; } // no require-dev in composer.json and the lock file was created with no dev info, so skip them $lockedRepository = $this->locker->getLockedRepository(); } } $this->whitelistUpdateDependencies( $lockedRepository ?: $localRepo, $this->package->getRequires(), $this->package->getDevRequires() ); $this->io->writeError('Loading composer repositories with package information'); // creating repository pool $policy = $this->createPolicy(); $pool = $this->createPool($this->update ? null : $lockedRepository); $pool->addRepository($installedRepo, $aliases); if ($this->update) { $repositories = $this->repositoryManager->getRepositories(); foreach ($repositories as $repository) { $pool->addRepository($repository, $aliases); } } // Add the locked repository after the others in case we are doing a // partial update so missing packages can be found there still. // For installs from lock it's the only one added so it is first if ($lockedRepository) { $pool->addRepository($lockedRepository, $aliases); } // creating requirements request $request = $this->createRequest($this->package, $platformRepo); if ($this->update) { // remove unstable packages from the localRepo if they don't match the current stability settings $removedUnstablePackages = array(); foreach ($localRepo->getPackages() as $package) { if ( !$pool->isPackageAcceptable($package->getNames(), $package->getStability()) && $this->installationManager->isPackageInstalled($localRepo, $package) ) { $removedUnstablePackages[$package->getName()] = true; $request->remove($package->getName(), new Constraint('=', $package->getVersion())); } } $this->io->writeError('Updating dependencies'.($this->devMode ? ' (including require-dev)' : '').''); $request->updateAll(); $links = array_merge($this->package->getRequires(), $this->package->getDevRequires()); foreach ($links as $link) { $request->install($link->getTarget(), $link->getConstraint()); } // if the updateWhitelist is enabled, packages not in it are also fixed // to the version specified in the lock, or their currently installed version if ($this->updateWhitelist) { $currentPackages = $this->getCurrentPackages($installedRepo); // collect packages to fixate from root requirements as well as installed packages $candidates = array(); foreach ($links as $link) { $candidates[$link->getTarget()] = true; $rootRequires[$link->getTarget()] = $link; } foreach ($currentPackages as $package) { $candidates[$package->getName()] = true; } // fix them to the version in lock (or currently installed) if they are not updateable foreach ($candidates as $candidate => $dummy) { foreach ($currentPackages as $curPackage) { if ($curPackage->getName() === $candidate) { if (!$this->isUpdateable($curPackage) && !isset($removedUnstablePackages[$curPackage->getName()])) { $constraint = new Constraint('=', $curPackage->getVersion()); $description = $this->locker->isLocked() ? '(locked at' : '(installed at'; $requiredAt = isset($rootRequires[$candidate]) ? ', required as ' . $rootRequires[$candidate]->getPrettyConstraint() : ''; $constraint->setPrettyString($description . ' ' . $curPackage->getPrettyVersion() . $requiredAt . ')'); $request->install($curPackage->getName(), $constraint); } break; } } } } } else { $this->io->writeError('Installing dependencies'.($this->devMode ? ' (including require-dev)' : '').' from lock file'); if (!$this->locker->isFresh()) { $this->io->writeError('Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.', true, IOInterface::QUIET); } foreach ($lockedRepository->getPackages() as $package) { $version = $package->getVersion(); if (isset($aliases[$package->getName()][$version])) { $version = $aliases[$package->getName()][$version]['alias_normalized']; } $constraint = new Constraint('=', $version); $constraint->setPrettyString($package->getPrettyVersion()); $request->install($package->getName(), $constraint); } foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) { $request->install($link->getTarget(), $link->getConstraint()); } } // force dev packages to have the latest links if we update or install from a (potentially new) lock $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links'); // solve dependencies $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request); $solver = new Solver($policy, $pool, $installedRepo, $this->io); try { $operations = $solver->solve($request, $this->ignorePlatformReqs); } catch (SolverProblemsException $e) { $this->io->writeError('Your requirements could not be resolved to an installable set of packages.', true, IOInterface::QUIET); $this->io->writeError($e->getMessage()); if ($this->update && !$this->devMode) { $this->io->writeError('Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.', true, IOInterface::QUIET); } return array(max(1, $e->getCode()), array()); } // force dev packages to be updated if we update or install from a (potentially new) lock $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations); $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request, $operations); $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE); // execute operations if (!$operations) { $this->io->writeError('Nothing to install or update'); } $operations = $this->movePluginsToFront($operations); $operations = $this->moveUninstallsToFront($operations); // extract dev packages and mark them to be skipped if it's a --no-dev install or update // we also force them to be uninstalled if they are present in the local repo if ($this->update) { $devPackages = $this->extractDevPackages($operations, $localRepo, $platformRepo, $aliases); if (!$this->devMode) { $operations = $this->filterDevPackageOperations($devPackages, $operations, $localRepo); } } else { $devPackages = null; } if ($operations) { $installs = $updates = $uninstalls = array(); foreach ($operations as $operation) { if ($operation instanceof InstallOperation) { $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UpdateOperation) { $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UninstallOperation) { $uninstalls[] = $operation->getPackage()->getPrettyName(); } } $this->io->writeError( sprintf("Package operations: %d install%s, %d update%s, %d removal%s", count($installs), 1 === count($installs) ? '' : 's', count($updates), 1 === count($updates) ? '' : 's', count($uninstalls), 1 === count($uninstalls) ? '' : 's') ); if ($installs) { $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE); } if ($updates) { $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE); } if ($uninstalls) { $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE); } } foreach ($operations as $operation) { // collect suggestions if ('install' === $operation->getJobType()) { $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage()); } // updating, force dev packages' references if they're in root package refs if ($this->update) { $package = null; if ('update' === $operation->getJobType()) { $package = $operation->getTargetPackage(); } elseif ('install' === $operation->getJobType()) { $package = $operation->getPackage(); } if ($package && $package->isDev()) { $references = $this->package->getReferences(); if (isset($references[$package->getName()])) { $this->updateInstallReferences($package, $references[$package->getName()]); } } if ('update' === $operation->getJobType() && $operation->getTargetPackage()->isDev() && $operation->getTargetPackage()->getVersion() === $operation->getInitialPackage()->getVersion() && (!$operation->getTargetPackage()->getSourceReference() || $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference()) && (!$operation->getTargetPackage()->getDistReference() || $operation->getTargetPackage()->getDistReference() === $operation->getInitialPackage()->getDistReference()) ) { $this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version', true, IOInterface::DEBUG); $this->io->writeError('', true, IOInterface::DEBUG); continue; } } $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType()); if (defined($event) && $this->runScripts) { $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); } // output non-alias ops when not executing operations (i.e. dry run), output alias ops in debug verbosity if (!$this->executeOperations && false === strpos($operation->getJobType(), 'Alias')) { $this->io->writeError(' - ' . $operation); } elseif ($this->io->isDebug() && false !== strpos($operation->getJobType(), 'Alias')) { $this->io->writeError(' - ' . $operation); } $this->installationManager->execute($localRepo, $operation); // output reasons why the operation was ran, only for install/update operations if ($this->verbose && $this->io->isVeryVerbose() && in_array($operation->getJobType(), array('install', 'update'))) { $reason = $operation->getReason(); if ($reason instanceof Rule) { switch ($reason->getReason()) { case Rule::RULE_JOB_INSTALL: $this->io->writeError(' REASON: Required by the root package: '.$reason->getPrettyString($pool)); $this->io->writeError(''); break; case Rule::RULE_PACKAGE_REQUIRES: $this->io->writeError(' REASON: '.$reason->getPrettyString($pool)); $this->io->writeError(''); break; } } } $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($operation->getJobType()); if (defined($event) && $this->runScripts) { $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); } if ($this->executeOperations || $this->writeLock) { $localRepo->write(); } } if ($this->executeOperations) { // force source/dist urls to be updated for all packages $this->processPackageUrls($pool, $policy, $localRepo, $repositories); $localRepo->write(); } return array(0, $devPackages); } /** * Extracts the dev packages out of the localRepo * * This works by faking the operations so we can see what the dev packages * would be at the end of the operation execution. This lets us then remove * the dev packages from the list of operations accordingly if we are in a * --no-dev install or update. * * @return array */ private function extractDevPackages(array $operations, RepositoryInterface $localRepo, PlatformRepository $platformRepo, array $aliases) { if (!$this->package->getDevRequires()) { return array(); } // fake-apply all operations to this clone of the local repo so we see the complete set of package we would end up with $tempLocalRepo = clone $localRepo; foreach ($operations as $operation) { switch ($operation->getJobType()) { case 'install': case 'markAliasInstalled': if (!$tempLocalRepo->hasPackage($operation->getPackage())) { $tempLocalRepo->addPackage(clone $operation->getPackage()); } break; case 'uninstall': case 'markAliasUninstalled': $tempLocalRepo->removePackage($operation->getPackage()); break; case 'update': $tempLocalRepo->removePackage($operation->getInitialPackage()); if (!$tempLocalRepo->hasPackage($operation->getTargetPackage())) { $tempLocalRepo->addPackage(clone $operation->getTargetPackage()); } break; default: throw new \LogicException('Unknown type: '.$operation->getJobType()); } } // we have to reload the local repo to handle aliases properly // but as it is not persisted on disk we use a loader/dumper // to reload it in memory $localRepo = new InstalledArrayRepository(array()); $loader = new ArrayLoader(null, true); $dumper = new ArrayDumper(); foreach ($tempLocalRepo->getCanonicalPackages() as $pkg) { $localRepo->addPackage($loader->load($dumper->dump($pkg))); } unset($tempLocalRepo, $loader, $dumper); $policy = $this->createPolicy(); $pool = $this->createPool(); $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo); $pool->addRepository($installedRepo, $aliases); // creating requirements request without dev requirements $request = $this->createRequest($this->package, $platformRepo); $request->updateAll(); foreach ($this->package->getRequires() as $link) { $request->install($link->getTarget(), $link->getConstraint()); } // solve deps to see which get removed $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request); $solver = new Solver($policy, $pool, $installedRepo, $this->io); $ops = $solver->solve($request, $this->ignorePlatformReqs); $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops); $devPackages = array(); foreach ($ops as $op) { if ($op->getJobType() === 'uninstall') { $devPackages[] = $op->getPackage(); } } return $devPackages; } /** * @return OperationInterface[] filtered operations, dev packages are uninstalled and all operations on them ignored */ private function filterDevPackageOperations(array $devPackages, array $operations, RepositoryInterface $localRepo) { $finalOps = array(); $packagesToSkip = array(); foreach ($devPackages as $pkg) { $packagesToSkip[$pkg->getName()] = true; if ($installedDevPkg = $localRepo->findPackage($pkg->getName(), '*')) { $finalOps[] = new UninstallOperation($installedDevPkg, 'non-dev install removing it'); } } // skip operations applied on dev packages foreach ($operations as $op) { $package = $op->getJobType() === 'update' ? $op->getTargetPackage() : $op->getPackage(); if (isset($packagesToSkip[$package->getName()])) { continue; } $finalOps[] = $op; } return $finalOps; } /** * Workaround: if your packages depend on plugins, we must be sure * that those are installed / updated first; else it would lead to packages * being installed multiple times in different folders, when running Composer * twice. * * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147, * it at least fixes the symptoms and makes usage of composer possible (again) * in such scenarios. * * @param OperationInterface[] $operations * @return OperationInterface[] reordered operation list */ private function movePluginsToFront(array $operations) { $pluginsNoDeps = array(); $pluginsWithDeps = array(); $pluginRequires = array(); foreach (array_reverse($operations, true) as $idx => $op) { if ($op instanceof InstallOperation) { $package = $op->getPackage(); } elseif ($op instanceof UpdateOperation) { $package = $op->getTargetPackage(); } else { continue; } // is this package a plugin? $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; // is this a plugin or a dependency of a plugin? if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) { // get the package's requires, but filter out any platform requirements or 'composer-plugin-api' $requires = array_filter(array_keys($package->getRequires()), function($req) { return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req); }); // is this a plugin with no meaningful dependencies? if ($isPlugin && !count($requires)) { // plugins with no dependencies go to the very front array_unshift($pluginsNoDeps, $op); } else { // capture the requirements for this package so those packages will be moved up as well $pluginRequires = array_merge($pluginRequires, $requires); // move the operation to the front array_unshift($pluginsWithDeps, $op); } unset($operations[$idx]); } } return array_merge($pluginsNoDeps, $pluginsWithDeps, $operations); } /** * Removals of packages should be executed before installations in * case two packages resolve to the same path (due to custom installers) * * @param OperationInterface[] $operations * @return OperationInterface[] reordered operation list */ private function moveUninstallsToFront(array $operations) { $uninstOps = array(); foreach ($operations as $idx => $op) { if ($op instanceof UninstallOperation) { $uninstOps[] = $op; unset($operations[$idx]); } } return array_merge($uninstOps, $operations); } /** * @return RepositoryInterface */ private function createInstalledRepo(RepositoryInterface $localRepo, PlatformRepository $platformRepo) { // clone root package to have one in the installed repo that does not require anything // we don't want it to be uninstallable, but its requirements should not conflict // with the lock file for example $installedRootPackage = clone $this->package; $installedRootPackage->setRequires(array()); $installedRootPackage->setDevRequires(array()); $repos = array( $localRepo, new InstalledArrayRepository(array($installedRootPackage)), $platformRepo, ); $installedRepo = new CompositeRepository($repos); if ($this->additionalInstalledRepository) { $installedRepo->addRepository($this->additionalInstalledRepository); } return $installedRepo; } /** * @param RepositoryInterface|null $lockedRepository * @return Pool */ private function createPool(RepositoryInterface $lockedRepository = null) { if ($this->update) { $minimumStability = $this->package->getMinimumStability(); $stabilityFlags = $this->package->getStabilityFlags(); $requires = array_merge($this->package->getRequires(), $this->package->getDevRequires()); } else { $minimumStability = $this->locker->getMinimumStability(); $stabilityFlags = $this->locker->getStabilityFlags(); $requires = array(); foreach ($lockedRepository->getPackages() as $package) { $constraint = new Constraint('=', $package->getVersion()); $constraint->setPrettyString($package->getPrettyVersion()); $requires[$package->getName()] = $constraint; } } $rootConstraints = array(); foreach ($requires as $req => $constraint) { // skip platform requirements from the root package to avoid filtering out existing platform packages if ($this->ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req)) { continue; } if ($constraint instanceof Link) { $rootConstraints[$req] = $constraint->getConstraint(); } else { $rootConstraints[$req] = $constraint; } } return new Pool($minimumStability, $stabilityFlags, $rootConstraints); } /** * @return DefaultPolicy */ private function createPolicy() { $preferStable = null; $preferLowest = null; if (!$this->update) { $preferStable = $this->locker->getPreferStable(); $preferLowest = $this->locker->getPreferLowest(); } // old lock file without prefer stable/lowest will return null // so in this case we use the composer.json info if (null === $preferStable) { $preferStable = $this->preferStable || $this->package->getPreferStable(); } if (null === $preferLowest) { $preferLowest = $this->preferLowest; } return new DefaultPolicy($preferStable, $preferLowest); } /** * @param RootPackageInterface $rootPackage * @param PlatformRepository $platformRepo * @return Request */ private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo) { $request = new Request(); $constraint = new Constraint('=', $rootPackage->getVersion()); $constraint->setPrettyString($rootPackage->getPrettyVersion()); $request->install($rootPackage->getName(), $constraint); $fixedPackages = $platformRepo->getPackages(); if ($this->additionalInstalledRepository) { $additionalFixedPackages = $this->additionalInstalledRepository->getPackages(); $fixedPackages = array_merge($fixedPackages, $additionalFixedPackages); } // fix the version of all platform packages + additionally installed packages // to prevent the solver trying to remove or update those $provided = $rootPackage->getProvides(); foreach ($fixedPackages as $package) { $constraint = new Constraint('=', $package->getVersion()); $constraint->setPrettyString($package->getPrettyVersion()); // skip platform packages that are provided by the root package if ($package->getRepository() !== $platformRepo || !isset($provided[$package->getName()]) || !$provided[$package->getName()]->getConstraint()->matches($constraint) ) { $request->fix($package->getName(), $constraint); } } return $request; } /** * @param WritableRepositoryInterface $localRepo * @param Pool $pool * @param PolicyInterface $policy * @param array $repositories * @param RepositoryInterface $installedRepo * @param RepositoryInterface $lockedRepository * @param string $task * @param array|null $operations * @return array */ private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null) { if ($task === 'force-updates' && null === $operations) { throw new \InvalidArgumentException('Missing operations argument'); } if ($task === 'force-links') { $operations = array(); } if ($this->update && $this->updateWhitelist) { $currentPackages = $this->getCurrentPackages($installedRepo); } foreach ($localRepo->getCanonicalPackages() as $package) { // skip non-dev packages if (!$package->isDev()) { continue; } // skip packages that will be updated/uninstalled foreach ($operations as $operation) { if (('update' === $operation->getJobType() && $operation->getInitialPackage()->equals($package)) || ('uninstall' === $operation->getJobType() && $operation->getPackage()->equals($package)) ) { continue 2; } } if ($this->update) { // skip package if the whitelist is enabled and it is not in it if ($this->updateWhitelist && !$this->isUpdateable($package)) { // check if non-updateable packages are out of date compared to the lock file to ensure we don't corrupt it foreach ($currentPackages as $curPackage) { if ($curPackage->isDev() && $curPackage->getName() === $package->getName() && $curPackage->getVersion() === $package->getVersion()) { if ($task === 'force-links') { $package->setRequires($curPackage->getRequires()); $package->setConflicts($curPackage->getConflicts()); $package->setProvides($curPackage->getProvides()); $package->setReplaces($curPackage->getReplaces()); } elseif ($task === 'force-updates') { if (($curPackage->getSourceReference() && $curPackage->getSourceReference() !== $package->getSourceReference()) || ($curPackage->getDistReference() && $curPackage->getDistReference() !== $package->getDistReference()) ) { $operations[] = new UpdateOperation($package, $curPackage); } } break; } } continue; } // find similar packages (name/version) in all repositories $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion())); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { unset($matches[$index]); continue; } // skip providers/replacers if ($match->getName() !== $package->getName()) { unset($matches[$index]); continue; } $matches[$index] = $match->getId(); } // select preferred package according to policy rules if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) { $newPackage = $pool->literalToPackage($matches[0]); if ($task === 'force-links' && $newPackage) { $package->setRequires($newPackage->getRequires()); $package->setConflicts($newPackage->getConflicts()); $package->setProvides($newPackage->getProvides()); $package->setReplaces($newPackage->getReplaces()); } if ($task === 'force-updates' && $newPackage && ( (($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference()) || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference()) ) )) { $operations[] = new UpdateOperation($package, $newPackage); continue; } } if ($task === 'force-updates') { // force installed package to update to referenced version in root package if it does not match the installed version $references = $this->package->getReferences(); if (isset($references[$package->getName()]) && $references[$package->getName()] !== $package->getSourceReference()) { // changing the source ref to update to will be handled in the operations loop $operations[] = new UpdateOperation($package, clone $package); } } } else { // force update to locked version if it does not match the installed version foreach ($lockedRepository->findPackages($package->getName()) as $lockedPackage) { if ($lockedPackage->isDev() && $lockedPackage->getVersion() === $package->getVersion()) { if ($task === 'force-links') { $package->setRequires($lockedPackage->getRequires()); $package->setConflicts($lockedPackage->getConflicts()); $package->setProvides($lockedPackage->getProvides()); $package->setReplaces($lockedPackage->getReplaces()); } elseif ($task === 'force-updates') { if (($lockedPackage->getSourceReference() && $lockedPackage->getSourceReference() !== $package->getSourceReference()) || ($lockedPackage->getDistReference() && $lockedPackage->getDistReference() !== $package->getDistReference()) ) { $operations[] = new UpdateOperation($package, $lockedPackage); } } break; } } } } return $operations; } /** * Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback * @param RepositoryInterface $installedRepo * @return array */ private function getCurrentPackages($installedRepo) { if ($this->locker->isLocked()) { try { return $this->locker->getLockedRepository(true)->getPackages(); } catch (\RuntimeException $e) { // fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file return $this->locker->getLockedRepository()->getPackages(); } } return $installedRepo->getPackages(); } /** * @return array */ private function getRootAliases() { if ($this->update) { $aliases = $this->package->getAliases(); } else { $aliases = $this->locker->getAliases(); } $normalizedAliases = array(); foreach ($aliases as $alias) { $normalizedAliases[$alias['package']][$alias['version']] = array( 'alias' => $alias['alias'], 'alias_normalized' => $alias['alias_normalized'], ); } return $normalizedAliases; } /** * @param Pool $pool * @param PolicyInterface $policy * @param WritableRepositoryInterface $localRepo * @param array $repositories */ private function processPackageUrls($pool, $policy, $localRepo, $repositories) { if (!$this->update) { return; } $rootRefs = $this->package->getReferences(); foreach ($localRepo->getCanonicalPackages() as $package) { // find similar packages (name/version) in all repositories $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion())); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { unset($matches[$index]); continue; } // skip providers/replacers if ($match->getName() !== $package->getName()) { unset($matches[$index]); continue; } $matches[$index] = $match->getId(); } // select preferred package according to policy rules if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) { $newPackage = $pool->literalToPackage($matches[0]); // update the dist and source URLs $sourceUrl = $package->getSourceUrl(); $newSourceUrl = $newPackage->getSourceUrl(); $newReference = $newPackage->getSourceReference(); if ($package->isDev() && isset($rootRefs[$package->getName()]) && $package->getSourceReference() === $rootRefs[$package->getName()]) { $newReference = $rootRefs[$package->getName()]; } $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl()); if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) { $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned()); } $package->setDistMirrors($newPackage->getDistMirrors()); $package->setSourceMirrors($newPackage->getSourceMirrors()); } } } private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl) { $oldSourceRef = $package->getSourceReference(); if ($package->getSourceUrl() !== $sourceUrl) { $package->setSourceType($sourceType); $package->setSourceUrl($sourceUrl); $package->setSourceReference($sourceReference); } // only update dist url for github/bitbucket dists as they use a combination of dist url + dist reference to install // but for other urls this is ambiguous and could result in bad outcomes if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com)/}i', $distUrl)) { $package->setDistUrl($distUrl); $this->updateInstallReferences($package, $sourceReference); } if ($this->updateWhitelist && !$this->isUpdateable($package)) { $this->updateInstallReferences($package, $oldSourceRef); } } private function updateInstallReferences(PackageInterface $package, $reference) { if (!$reference) { return; } $package->setSourceReference($reference); if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com)/}i', $package->getDistUrl())) { $package->setDistReference($reference); $package->setDistUrl(preg_replace('{(?<=/)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl())); } else if ($package->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it $package->setDistReference($reference); } } /** * @param PlatformRepository $platformRepo * @param array $aliases */ private function aliasPlatformPackages(PlatformRepository $platformRepo, $aliases) { foreach ($aliases as $package => $versions) { foreach ($versions as $version => $alias) { $packages = $platformRepo->findPackages($package, $version); foreach ($packages as $package) { $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); $aliasPackage->setRootPackageAlias(true); $platformRepo->addPackage($aliasPackage); } } } } /** * @param PackageInterface $package * @return bool */ private function isUpdateable(PackageInterface $package) { if (!$this->updateWhitelist) { throw new \LogicException('isUpdateable should only be called when a whitelist is present'); } foreach ($this->updateWhitelist as $whiteListedPattern => $void) { $patternRegexp = $this->packageNameToRegexp($whiteListedPattern); if (preg_match($patternRegexp, $package->getName())) { return true; } } return false; } /** * Build a regexp from a package name, expanding * globs as required * * @param string $whiteListedPattern * @return string */ private function packageNameToRegexp($whiteListedPattern) { $cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern)); return "{^" . $cleanedWhiteListedPattern . "$}i"; } /** * @param array $links * @return array */ private function extractPlatformRequirements($links) { $platformReqs = array(); foreach ($links as $link) { if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { $platformReqs[$link->getTarget()] = $link->getPrettyConstraint(); } } return $platformReqs; } /** * Adds all dependencies of the update whitelist to the whitelist, too. * * Packages which are listed as requirements in the root package will be * skipped including their dependencies, unless they are listed in the * update whitelist themselves. * * @param RepositoryInterface $localOrLockRepo Use the locked repo if available, otherwise installed repo will do * As we want the most accurate package list to work with, and installed * repo might be empty but locked repo will always be current. * @param array $rootRequires An array of links to packages in require of the root package * @param array $rootDevRequires An array of links to packages in require-dev of the root package */ private function whitelistUpdateDependencies($localOrLockRepo, array $rootRequires, array $rootDevRequires) { if (!$this->updateWhitelist) { return; } $rootRequires = array_merge($rootRequires, $rootDevRequires); $requiredPackageNames = array(); foreach ($rootRequires as $require) { $requiredPackageNames[] = $require->getTarget(); } $skipPackages = array(); foreach ($rootRequires as $require) { $skipPackages[$require->getTarget()] = true; } $pool = new Pool('dev'); $pool->addRepository($localOrLockRepo); $seen = array(); $rootRequiredPackageNames = array_keys($rootRequires); foreach ($this->updateWhitelist as $packageName => $void) { $packageQueue = new \SplQueue; $depPackages = $pool->whatProvides($packageName); $nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true); // check if the name is a glob pattern that did not match directly if (!$nameMatchesRequiredPackage) { $whitelistPatternRegexp = $this->packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { $nameMatchesRequiredPackage = true; break; } } } if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) { $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); } foreach ($depPackages as $depPackage) { $packageQueue->enqueue($depPackage); } while (!$packageQueue->isEmpty()) { $package = $packageQueue->dequeue(); if (isset($seen[$package->getId()])) { continue; } $seen[$package->getId()] = true; $this->updateWhitelist[$package->getName()] = true; if (!$this->whitelistDependencies) { continue; } $requires = $package->getRequires(); foreach ($requires as $require) { $requirePackages = $pool->whatProvides($require->getTarget()); foreach ($requirePackages as $requirePackage) { if (isset($this->updateWhitelist[$requirePackage->getName()])) { continue; } if (isset($skipPackages[$requirePackage->getName()])) { $this->io->writeError('Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.'); continue; } $packageQueue->enqueue($requirePackage); } } } } } /** * Replace local repositories with InstalledArrayRepository instances * * This is to prevent any accidental modification of the existing repos on disk * * @param RepositoryManager $rm */ private function mockLocalRepositories(RepositoryManager $rm) { $packages = array(); foreach ($rm->getLocalRepository()->getPackages() as $package) { $packages[(string) $package] = clone $package; } foreach ($packages as $key => $package) { if ($package instanceof AliasPackage) { $alias = (string) $package->getAliasOf(); $packages[$key] = new AliasPackage($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); } } $rm->setLocalRepository( new InstalledArrayRepository($packages) ); } /** * Create Installer * * @param IOInterface $io * @param Composer $composer * @return Installer */ public static function create(IOInterface $io, Composer $composer) { return new static( $io, $composer->getConfig(), $composer->getPackage(), $composer->getDownloadManager(), $composer->getRepositoryManager(), $composer->getLocker(), $composer->getInstallationManager(), $composer->getEventDispatcher(), $composer->getAutoloadGenerator() ); } /** * @param RepositoryInterface $additionalInstalledRepository * @return $this */ public function setAdditionalInstalledRepository(RepositoryInterface $additionalInstalledRepository) { $this->additionalInstalledRepository = $additionalInstalledRepository; return $this; } /** * Whether to run in drymode or not * * @param bool $dryRun * @return Installer */ public function setDryRun($dryRun = true) { $this->dryRun = (bool) $dryRun; return $this; } /** * Checks, if this is a dry run (simulation mode). * * @return bool */ public function isDryRun() { return $this->dryRun; } /** * prefer source installation * * @param bool $preferSource * @return Installer */ public function setPreferSource($preferSource = true) { $this->preferSource = (bool) $preferSource; return $this; } /** * prefer dist installation * * @param bool $preferDist * @return Installer */ public function setPreferDist($preferDist = true) { $this->preferDist = (bool) $preferDist; return $this; } /** * Whether or not generated autoloader are optimized * * @param bool $optimizeAutoloader * @return Installer */ public function setOptimizeAutoloader($optimizeAutoloader = false) { $this->optimizeAutoloader = (bool) $optimizeAutoloader; if (!$this->optimizeAutoloader) { // Force classMapAuthoritative off when not optimizing the // autoloader $this->setClassMapAuthoritative(false); } return $this; } /** * Whether or not generated autoloader considers the class map * authoritative. * * @param bool $classMapAuthoritative * @return Installer */ public function setClassMapAuthoritative($classMapAuthoritative = false) { $this->classMapAuthoritative = (bool) $classMapAuthoritative; if ($this->classMapAuthoritative) { // Force optimizeAutoloader when classmap is authoritative $this->setOptimizeAutoloader(true); } return $this; } /** * Whether or not generated autoloader considers APCu caching. * * @param bool $apcuAutoloader * @return Installer */ public function setApcuAutoloader($apcuAutoloader = false) { $this->apcuAutoloader = (bool) $apcuAutoloader; return $this; } /** * update packages * * @param bool $update * @return Installer */ public function setUpdate($update = true) { $this->update = (bool) $update; return $this; } /** * enables dev packages * * @param bool $devMode * @return Installer */ public function setDevMode($devMode = true) { $this->devMode = (bool) $devMode; return $this; } /** * set whether to run autoloader or not * * This is disabled implicitly when enabling dryRun * * @param bool $dumpAutoloader * @return Installer */ public function setDumpAutoloader($dumpAutoloader = true) { $this->dumpAutoloader = (bool) $dumpAutoloader; return $this; } /** * set whether to run scripts or not * * This is disabled implicitly when enabling dryRun * * @param bool $runScripts * @return Installer */ public function setRunScripts($runScripts = true) { $this->runScripts = (bool) $runScripts; return $this; } /** * set the config instance * * @param Config $config * @return Installer */ public function setConfig(Config $config) { $this->config = $config; return $this; } /** * run in verbose mode * * @param bool $verbose * @return Installer */ public function setVerbose($verbose = true) { $this->verbose = (bool) $verbose; return $this; } /** * Checks, if running in verbose mode. * * @return bool */ public function isVerbose() { return $this->verbose; } /** * set ignore Platform Package requirements * * @param bool $ignorePlatformReqs * @return Installer */ public function setIgnorePlatformRequirements($ignorePlatformReqs = false) { $this->ignorePlatformReqs = (bool) $ignorePlatformReqs; return $this; } /** * restrict the update operation to a few packages, all other packages * that are already installed will be kept at their current version * * @param array $packages * @return Installer */ public function setUpdateWhitelist(array $packages) { $this->updateWhitelist = array_flip(array_map('strtolower', $packages)); return $this; } /** * Should dependencies of whitelisted packages be updated recursively? * * @param bool $updateDependencies * @return Installer */ public function setWhitelistDependencies($updateDependencies = true) { $this->whitelistDependencies = (bool) $updateDependencies; return $this; } /** * Should packages be preferred in a stable version when updating? * * @param bool $preferStable * @return Installer */ public function setPreferStable($preferStable = true) { $this->preferStable = (bool) $preferStable; return $this; } /** * Should packages be preferred in a lowest version when updating? * * @param bool $preferLowest * @return Installer */ public function setPreferLowest($preferLowest = true) { $this->preferLowest = (bool) $preferLowest; return $this; } /** * Should the lock file be updated when updating? * * This is disabled implicitly when enabling dryRun * * @param bool $writeLock * @return Installer */ public function setWriteLock($writeLock = true) { $this->writeLock = (bool) $writeLock; return $this; } /** * Should the operations (packge install, update and removal) be executed on disk? * * This is disabled implicitly when enabling dryRun * * @param bool $executeOperations * @return Installer */ public function setExecuteOperations($executeOperations = true) { $this->executeOperations = (bool) $executeOperations; return $this; } /** * Should suggestions be skipped? * * @param bool $skipSuggest * @return Installer */ public function setSkipSuggest($skipSuggest = true) { $this->skipSuggest = (bool) $skipSuggest; return $this; } /** * Disables plugins. * * Call this if you want to ensure that third-party code never gets * executed. The default is to automatically install, and execute * custom third-party installers. * * @return Installer */ public function disablePlugins() { $this->installationManager->disablePlugins(); return $this; } /** * @param SuggestedPackagesReporter $suggestedPackagesReporter * @return Installer */ public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter) { $this->suggestedPackagesReporter = $suggestedPackagesReporter; return $this; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; use Composer\Package\Link; /** * @author Nils Adermann */ class GenericRule extends Rule { protected $literals; /** * @param array $literals * @param int $reason A RULE_* constant describing the reason for generating this rule * @param Link|PackageInterface $reasonData * @param array $job The job this rule was created from */ public function __construct(array $literals, $reason, $reasonData, $job = null) { parent::__construct($reason, $reasonData, $job); // sort all packages ascending by id sort($literals); $this->literals = $literals; } public function getLiterals() { return $this->literals; } public function getHash() { $data = unpack('ihash', md5(implode(',', $this->literals), true)); return $data['hash']; } /** * Checks if this rule is equal to another one * * Ignores whether either of the rules is disabled. * * @param Rule $rule The rule to check against * @return bool Whether the rules are equal */ public function equals(Rule $rule) { return $this->literals === $rule->getLiterals(); } public function isAssertion() { return 1 === count($this->literals); } /** * Formats a rule as a string of the format (Literal1|Literal2|...) * * @return string */ public function __toString() { $result = ($this->isDisabled()) ? 'disabled(' : '('; foreach ($this->literals as $i => $literal) { if ($i != 0) { $result .= '|'; } $result .= $literal; } $result .= ')'; return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * The RuleWatchGraph efficiently propagates decisions to other rules * * All rules generated for solving a SAT problem should be inserted into the * graph. When a decision on a literal is made, the graph can be used to * propagate the decision to all other rules involving the literal, leading to * other trivial decisions resulting from unit clauses. * * @author Nils Adermann */ class RuleWatchGraph { protected $watchChains = array(); /** * Inserts a rule node into the appropriate chains within the graph * * The node is prepended to the watch chains for each of the two literals it * watches. * * Assertions are skipped because they only depend on a single package and * have no alternative literal that could be true, so there is no need to * watch changes in any literals. * * @param RuleWatchNode $node The rule node to be inserted into the graph */ public function insert(RuleWatchNode $node) { if ($node->getRule()->isAssertion()) { return; } foreach (array($node->watch1, $node->watch2) as $literal) { if (!isset($this->watchChains[$literal])) { $this->watchChains[$literal] = new RuleWatchChain; } $this->watchChains[$literal]->unshift($node); } } /** * Propagates a decision on a literal to all rules watching the literal * * If a decision, e.g. +A has been made, then all rules containing -A, e.g. * (-A|+B|+C) now need to satisfy at least one of the other literals, so * that the rule as a whole becomes true, since with +A applied the rule * is now (false|+B|+C) so essentially (+B|+C). * * This means that all rules watching the literal -A need to be updated to * watch 2 other literals which can still be satisfied instead. So literals * that conflict with previously made decisions are not an option. * * Alternatively it can occur that a unit clause results: e.g. if in the * above example the rule was (-A|+B), then A turning true means that * B must now be decided true as well. * * @param int $decidedLiteral The literal which was decided (A in our example) * @param int $level The level at which the decision took place and at which * all resulting decisions should be made. * @param Decisions $decisions Used to check previous decisions and to * register decisions resulting from propagation * @return Rule|null If a conflict is found the conflicting rule is returned */ public function propagateLiteral($decidedLiteral, $level, $decisions) { // we invert the decided literal here, example: // A was decided => (-A|B) now requires B to be true, so we look for // rules which are fulfilled by -A, rather than A. $literal = -$decidedLiteral; if (!isset($this->watchChains[$literal])) { return null; } $chain = $this->watchChains[$literal]; $chain->rewind(); while ($chain->valid()) { $node = $chain->current(); $otherWatch = $node->getOtherWatch($literal); if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) { $ruleLiterals = $node->getRule()->getLiterals(); $alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) { return $literal !== $ruleLiteral && $otherWatch !== $ruleLiteral && !$decisions->conflict($ruleLiteral); }); if ($alternativeLiterals) { reset($alternativeLiterals); $this->moveWatch($literal, current($alternativeLiterals), $node); continue; } if ($decisions->conflict($otherWatch)) { return $node->getRule(); } $decisions->decide($otherWatch, $level, $node->getRule()); } $chain->next(); } return null; } /** * Moves a rule node from one watch chain to another * * The rule node's watched literals are updated accordingly. * * @param $fromLiteral mixed A literal the node used to watch * @param $toLiteral mixed A literal the node should watch now * @param $node mixed The rule node to be moved */ protected function moveWatch($fromLiteral, $toLiteral, $node) { if (!isset($this->watchChains[$toLiteral])) { $this->watchChains[$toLiteral] = new RuleWatchChain; } $node->moveWatch($fromLiteral, $toLiteral); $this->watchChains[$fromLiteral]->remove(); $this->watchChains[$toLiteral]->unshift($node); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Semver\Constraint\ConstraintInterface; /** * @author Nils Adermann */ class Request { protected $jobs; public function __construct() { $this->jobs = array(); } public function install($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'install', $constraint); } public function update($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'update', $constraint); } public function remove($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'remove', $constraint); } /** * Mark an existing package as being installed and having to remain installed * * These jobs will not be tempered with by the solver * * @param string $packageName * @param ConstraintInterface|null $constraint */ public function fix($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'install', $constraint, true); } protected function addJob($packageName, $cmd, ConstraintInterface $constraint = null, $fixed = false) { $packageName = strtolower($packageName); $this->jobs[] = array( 'cmd' => $cmd, 'packageName' => $packageName, 'constraint' => $constraint, 'fixed' => $fixed, ); } public function updateAll() { $this->jobs[] = array('cmd' => 'update-all'); } public function getJobs() { return $this->jobs; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\IO\IOInterface; use Composer\Repository\RepositoryInterface; use Composer\Repository\PlatformRepository; /** * @author Nils Adermann */ class Solver { const BRANCH_LITERALS = 0; const BRANCH_LEVEL = 1; /** @var PolicyInterface */ protected $policy; /** @var Pool */ protected $pool; /** @var RepositoryInterface */ protected $installed; /** @var Ruleset */ protected $rules; /** @var RuleSetGenerator */ protected $ruleSetGenerator; /** @var array */ protected $jobs; /** @var int[] */ protected $updateMap = array(); /** @var RuleWatchGraph */ protected $watchGraph; /** @var Decisions */ protected $decisions; /** @var int[] */ protected $installedMap; /** @var int */ protected $propagateIndex; /** @var array[] */ protected $branches = array(); /** @var Problem[] */ protected $problems = array(); /** @var array */ protected $learnedPool = array(); /** @var array */ protected $learnedWhy = array(); /** @var IOInterface */ protected $io; /** * @param PolicyInterface $policy * @param Pool $pool * @param RepositoryInterface $installed * @param IOInterface $io */ public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io) { $this->io = $io; $this->policy = $policy; $this->pool = $pool; $this->installed = $installed; $this->ruleSetGenerator = new RuleSetGenerator($policy, $pool); } /** * @return int */ public function getRuleSetSize() { return count($this->rules); } // aka solver_makeruledecisions private function makeAssertionRuleDecisions() { $decisionStart = count($this->decisions) - 1; $rulesCount = count($this->rules); for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) { $rule = $this->rules->ruleById[$ruleIndex]; if (!$rule->isAssertion() || $rule->isDisabled()) { continue; } $literals = $rule->getLiterals(); $literal = $literals[0]; if (!$this->decisions->decided(abs($literal))) { $this->decisions->decide($literal, 1, $rule); continue; } if ($this->decisions->satisfy($literal)) { continue; } // found a conflict if (RuleSet::TYPE_LEARNED === $rule->getType()) { $rule->disable(); continue; } $conflict = $this->decisions->decisionRule($literal); if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) { $problem = new Problem($this->pool); $problem->addRule($rule); $problem->addRule($conflict); $this->disableProblem($rule); $this->problems[] = $problem; continue; } // conflict with another job $problem = new Problem($this->pool); $problem->addRule($rule); $problem->addRule($conflict); // push all of our rules (can only be job rules) // asserting this literal on the problem stack foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) { if ($assertRule->isDisabled() || !$assertRule->isAssertion()) { continue; } $assertRuleLiterals = $assertRule->getLiterals(); $assertRuleLiteral = $assertRuleLiterals[0]; if (abs($literal) !== abs($assertRuleLiteral)) { continue; } $problem->addRule($assertRule); $this->disableProblem($assertRule); } $this->problems[] = $problem; $this->decisions->resetToOffset($decisionStart); $ruleIndex = -1; } } protected function setupInstalledMap() { $this->installedMap = array(); foreach ($this->installed->getPackages() as $package) { $this->installedMap[$package->id] = $package; } } /** * @param bool $ignorePlatformReqs */ protected function checkForRootRequireProblems($ignorePlatformReqs) { foreach ($this->jobs as $job) { switch ($job['cmd']) { case 'update': $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); foreach ($packages as $package) { if (isset($this->installedMap[$package->id])) { $this->updateMap[$package->id] = true; } } break; case 'update-all': foreach ($this->installedMap as $package) { $this->updateMap[$package->id] = true; } break; case 'install': if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) { break; } if (!$this->pool->whatProvides($job['packageName'], $job['constraint'])) { $problem = new Problem($this->pool); $problem->addRule(new GenericRule(array(), null, null, $job)); $this->problems[] = $problem; } break; } } } /** * @param Request $request * @param bool $ignorePlatformReqs * @return array */ public function solve(Request $request, $ignorePlatformReqs = false) { $this->jobs = $request->getJobs(); $this->setupInstalledMap(); $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs); $this->checkForRootRequireProblems($ignorePlatformReqs); $this->decisions = new Decisions($this->pool); $this->watchGraph = new RuleWatchGraph; foreach ($this->rules as $rule) { $this->watchGraph->insert(new RuleWatchNode($rule)); } /* make decisions based on job/update assertions */ $this->makeAssertionRuleDecisions(); $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); $before = microtime(true); $this->runSat(true); $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); // decide to remove everything that's installed and undecided foreach ($this->installedMap as $packageId => $void) { if ($this->decisions->undecided($packageId)) { $this->decisions->decide(-$packageId, 1, null); } } if ($this->problems) { throw new SolverProblemsException($this->problems, $this->installedMap); } $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions); return $transaction->getOperations(); } /** * Makes a decision and propagates it to all rules. * * Evaluates each term affected by the decision (linked through watches) * If we find unit rules we make new decisions based on them * * @param int $level * @return Rule|null A rule on conflict, otherwise null. */ protected function propagate($level) { while ($this->decisions->validOffset($this->propagateIndex)) { $decision = $this->decisions->atOffset($this->propagateIndex); $conflict = $this->watchGraph->propagateLiteral( $decision[Decisions::DECISION_LITERAL], $level, $this->decisions ); $this->propagateIndex++; if ($conflict) { return $conflict; } } return null; } /** * Reverts a decision at the given level. * * @param int $level */ private function revert($level) { while (!$this->decisions->isEmpty()) { $literal = $this->decisions->lastLiteral(); if ($this->decisions->undecided($literal)) { break; } $decisionLevel = $this->decisions->decisionLevel($literal); if ($decisionLevel <= $level) { break; } $this->decisions->revertLast(); $this->propagateIndex = count($this->decisions); } while (!empty($this->branches) && $this->branches[count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) { array_pop($this->branches); } } /** * setpropagatelearn * * add free decision (a positive literal) to decision queue * increase level and propagate decision * return if no conflict. * * in conflict case, analyze conflict rule, add resulting * rule to learnt rule set, make decision from learnt * rule (always unit) and re-propagate. * * returns the new solver level or 0 if unsolvable * * @param int $level * @param string|int $literal * @param bool $disableRules * @param Rule $rule * @return int */ private function setPropagateLearn($level, $literal, $disableRules, Rule $rule) { $level++; $this->decisions->decide($literal, $level, $rule); while (true) { $rule = $this->propagate($level); if (!$rule) { break; } if ($level == 1) { return $this->analyzeUnsolvable($rule, $disableRules); } // conflict list($learnLiteral, $newLevel, $newRule, $why) = $this->analyze($level, $rule); if ($newLevel <= 0 || $newLevel >= $level) { throw new SolverBugException( "Trying to revert to invalid level ".(int) $newLevel." from level ".(int) $level."." ); } elseif (!$newRule) { throw new SolverBugException( "No rule was learned from analyzing $rule at level $level." ); } $level = $newLevel; $this->revert($level); $this->rules->add($newRule, RuleSet::TYPE_LEARNED); $this->learnedWhy[spl_object_hash($newRule)] = $why; $ruleNode = new RuleWatchNode($newRule); $ruleNode->watch2OnHighest($this->decisions); $this->watchGraph->insert($ruleNode); $this->decisions->decide($learnLiteral, $level, $newRule); } return $level; } /** * @param int $level * @param array $decisionQueue * @param bool $disableRules * @param Rule $rule * @return int */ private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule) { // choose best package to install from decisionQueue $literals = $this->policy->selectPreferredPackages($this->pool, $this->installedMap, $decisionQueue, $rule->getRequiredPackage()); $selectedLiteral = array_shift($literals); // if there are multiple candidates, then branch if (count($literals)) { $this->branches[] = array($literals, $level); } return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule); } /** * @param int $level * @param Rule $rule * @return array */ protected function analyze($level, Rule $rule) { $analyzedRule = $rule; $ruleLevel = 1; $num = 0; $l1num = 0; $seen = array(); $learnedLiterals = array(null); $decisionId = count($this->decisions); $this->learnedPool[] = array(); while (true) { $this->learnedPool[count($this->learnedPool) - 1][] = $rule; foreach ($rule->getLiterals() as $literal) { // skip the one true literal if ($this->decisions->satisfy($literal)) { continue; } if (isset($seen[abs($literal)])) { continue; } $seen[abs($literal)] = true; $l = $this->decisions->decisionLevel($literal); if (1 === $l) { $l1num++; } elseif ($level === $l) { $num++; } else { // not level1 or conflict level, add to new rule $learnedLiterals[] = $literal; if ($l > $ruleLevel) { $ruleLevel = $l; } } } $l1retry = true; while ($l1retry) { $l1retry = false; if (!$num && !--$l1num) { // all level 1 literals done break 2; } while (true) { if ($decisionId <= 0) { throw new SolverBugException( "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule." ); } $decisionId--; $decision = $this->decisions->atOffset($decisionId); $literal = $decision[Decisions::DECISION_LITERAL]; if (isset($seen[abs($literal)])) { break; } } unset($seen[abs($literal)]); if ($num && 0 === --$num) { $learnedLiterals[0] = -abs($literal); if (!$l1num) { break 2; } foreach ($learnedLiterals as $i => $learnedLiteral) { if ($i !== 0) { unset($seen[abs($learnedLiteral)]); } } // only level 1 marks left $l1num++; $l1retry = true; } } $decision = $this->decisions->atOffset($decisionId); $rule = $decision[Decisions::DECISION_REASON]; } $why = count($this->learnedPool) - 1; if (!$learnedLiterals[0]) { throw new SolverBugException( "Did not find a learnable literal in analyzed rule $analyzedRule." ); } $newRule = new GenericRule($learnedLiterals, Rule::RULE_LEARNED, $why); return array($learnedLiterals[0], $ruleLevel, $newRule, $why); } /** * @param Problem $problem * @param Rule $conflictRule */ private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule) { $why = spl_object_hash($conflictRule); if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) { $learnedWhy = $this->learnedWhy[$why]; $problemRules = $this->learnedPool[$learnedWhy]; foreach ($problemRules as $problemRule) { $this->analyzeUnsolvableRule($problem, $problemRule); } return; } if ($conflictRule->getType() == RuleSet::TYPE_PACKAGE) { // package rules cannot be part of a problem return; } $problem->nextSection(); $problem->addRule($conflictRule); } /** * @param Rule $conflictRule * @param bool $disableRules * @return int */ private function analyzeUnsolvable(Rule $conflictRule, $disableRules) { $problem = new Problem($this->pool); $problem->addRule($conflictRule); $this->analyzeUnsolvableRule($problem, $conflictRule); $this->problems[] = $problem; $seen = array(); $literals = $conflictRule->getLiterals(); foreach ($literals as $literal) { // skip the one true literal if ($this->decisions->satisfy($literal)) { continue; } $seen[abs($literal)] = true; } foreach ($this->decisions as $decision) { $literal = $decision[Decisions::DECISION_LITERAL]; // skip literals that are not in this rule if (!isset($seen[abs($literal)])) { continue; } $why = $decision[Decisions::DECISION_REASON]; $problem->addRule($why); $this->analyzeUnsolvableRule($problem, $why); $literals = $why->getLiterals(); foreach ($literals as $literal) { // skip the one true literal if ($this->decisions->satisfy($literal)) { continue; } $seen[abs($literal)] = true; } } if ($disableRules) { foreach ($this->problems[count($this->problems) - 1] as $reason) { $this->disableProblem($reason['rule']); } $this->resetSolver(); return 1; } return 0; } /** * @param Rule $why */ private function disableProblem(Rule $why) { $job = $why->getJob(); if (!$job) { $why->disable(); return; } // disable all rules of this job foreach ($this->rules as $rule) { /** @var Rule $rule */ if ($job === $rule->getJob()) { $rule->disable(); } } } private function resetSolver() { $this->decisions->reset(); $this->propagateIndex = 0; $this->branches = array(); $this->enableDisableLearnedRules(); $this->makeAssertionRuleDecisions(); } /** * enable/disable learnt rules * * we have enabled or disabled some of our rules. We now re-enable all * of our learnt rules except the ones that were learnt from rules that * are now disabled. */ private function enableDisableLearnedRules() { foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) { $why = $this->learnedWhy[spl_object_hash($rule)]; $problemRules = $this->learnedPool[$why]; $foundDisabled = false; foreach ($problemRules as $problemRule) { if ($problemRule->isDisabled()) { $foundDisabled = true; break; } } if ($foundDisabled && $rule->isEnabled()) { $rule->disable(); } elseif (!$foundDisabled && $rule->isDisabled()) { $rule->enable(); } } } /** * @param bool $disableRules */ private function runSat($disableRules = true) { $this->propagateIndex = 0; /* * here's the main loop: * 1) propagate new decisions (only needed once) * 2) fulfill jobs * 3) fulfill all unresolved rules * 4) minimalize solution if we had choices * if we encounter a problem, we rewind to a safe level and restart * with step 1 */ $decisionQueue = array(); $decisionSupplementQueue = array(); /** * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code? */ $disableRules = array(); $level = 1; $systemLevel = $level + 1; $installedPos = 0; while (true) { if (1 === $level) { $conflictRule = $this->propagate($level); if (null !== $conflictRule) { if ($this->analyzeUnsolvable($conflictRule, $disableRules)) { continue; } return; } } // handle job rules if ($level < $systemLevel) { $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB); foreach ($iterator as $rule) { if ($rule->isEnabled()) { $decisionQueue = array(); $noneSatisfied = true; foreach ($rule->getLiterals() as $literal) { if ($this->decisions->satisfy($literal)) { $noneSatisfied = false; break; } if ($literal > 0 && $this->decisions->undecided($literal)) { $decisionQueue[] = $literal; } } if ($noneSatisfied && count($decisionQueue)) { // prune all update packages until installed version // except for requested updates if (count($this->installed) != count($this->updateMap)) { $prunedQueue = array(); foreach ($decisionQueue as $literal) { if (isset($this->installedMap[abs($literal)])) { $prunedQueue[] = $literal; if (isset($this->updateMap[abs($literal)])) { $prunedQueue = $decisionQueue; break; } } } $decisionQueue = $prunedQueue; } } if ($noneSatisfied && count($decisionQueue)) { $oLevel = $level; $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule); if (0 === $level) { return; } if ($level <= $oLevel) { break; } } } } $systemLevel = $level + 1; // jobs left $iterator->next(); if ($iterator->valid()) { continue; } } if ($level < $systemLevel) { $systemLevel = $level; } $rulesCount = count($this->rules); for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { if ($i == $rulesCount) { $i = 0; } $rule = $this->rules->ruleById[$i]; $literals = $rule->getLiterals(); if ($rule->isDisabled()) { continue; } $decisionQueue = array(); // make sure that // * all negative literals are installed // * no positive literal is installed // i.e. the rule is not fulfilled and we // just need to decide on the positive literals // foreach ($literals as $literal) { if ($literal <= 0) { if (!$this->decisions->decidedInstall(abs($literal))) { continue 2; // next rule } } else { if ($this->decisions->decidedInstall(abs($literal))) { continue 2; // next rule } if ($this->decisions->undecided(abs($literal))) { $decisionQueue[] = $literal; } } } // need to have at least 2 item to pick from if (count($decisionQueue) < 2) { continue; } $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule); if (0 === $level) { return; } // something changed, so look at all rules again $rulesCount = count($this->rules); $n = -1; } if ($level < $systemLevel) { continue; } // minimization step if (count($this->branches)) { $lastLiteral = null; $lastLevel = null; $lastBranchIndex = 0; $lastBranchOffset = 0; for ($i = count($this->branches) - 1; $i >= 0; $i--) { list($literals, $l) = $this->branches[$i]; foreach ($literals as $offset => $literal) { if ($literal && $literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) { $lastLiteral = $literal; $lastBranchIndex = $i; $lastBranchOffset = $offset; $lastLevel = $l; } } } if ($lastLiteral) { unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]); $level = $lastLevel; $this->revert($level); $why = $this->decisions->lastReason(); $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why); if ($level == 0) { return; } continue; } } break; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * Wrapper around a Rule which keeps track of the two literals it watches * * Used by RuleWatchGraph to store rules in two RuleWatchChains. * * @author Nils Adermann */ class RuleWatchNode { public $watch1; public $watch2; protected $rule; /** * Creates a new node watching the first and second literals of the rule. * * @param Rule $rule The rule to wrap */ public function __construct($rule) { $this->rule = $rule; $literals = $rule->getLiterals(); $this->watch1 = count($literals) > 0 ? $literals[0] : 0; $this->watch2 = count($literals) > 1 ? $literals[1] : 0; } /** * Places the second watch on the rule's literal, decided at the highest level * * Useful for learned rules where the literal for the highest rule is most * likely to quickly lead to further decisions. * * @param Decisions $decisions The decisions made so far by the solver */ public function watch2OnHighest(Decisions $decisions) { $literals = $this->rule->getLiterals(); // if there are only 2 elements, both are being watched anyway if (count($literals) < 3) { return; } $watchLevel = 0; foreach ($literals as $literal) { $level = $decisions->decisionLevel($literal); if ($level > $watchLevel) { $this->watch2 = $literal; $watchLevel = $level; } } } /** * Returns the rule this node wraps * * @return Rule */ public function getRule() { return $this->rule; } /** * Given one watched literal, this method returns the other watched literal * * @param int $literal The watched literal that should not be returned * @return int A literal */ public function getOtherWatch($literal) { if ($this->watch1 == $literal) { return $this->watch2; } else { return $this->watch1; } } /** * Moves a watch from one literal to another * * @param int $from The previously watched literal * @param int $to The literal to be watched now */ public function moveWatch($from, $to) { if ($this->watch1 == $from) { $this->watch1 = $to; } else { $this->watch2 = $to; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\EmptyConstraint; use Composer\Repository\RepositoryInterface; use Composer\Repository\CompositeRepository; use Composer\Repository\ComposerRepository; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\PlatformRepository; use Composer\Package\PackageInterface; /** * A package pool contains repositories that provide packages. * * @author Nils Adermann * @author Jordi Boggiano */ class Pool implements \Countable { const MATCH_NAME = -1; const MATCH_NONE = 0; const MATCH = 1; const MATCH_PROVIDE = 2; const MATCH_REPLACE = 3; const MATCH_FILTERED = 4; protected $repositories = array(); protected $providerRepos = array(); protected $packages = array(); protected $packageByName = array(); protected $packageByExactName = array(); protected $acceptableStabilities; protected $stabilityFlags; protected $versionParser; protected $providerCache = array(); protected $filterRequires; protected $whitelist = null; protected $id = 1; public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array()) { $this->versionParser = new VersionParser; $this->acceptableStabilities = array(); foreach (BasePackage::$stabilities as $stability => $value) { if ($value <= BasePackage::$stabilities[$minimumStability]) { $this->acceptableStabilities[$stability] = $value; } } $this->stabilityFlags = $stabilityFlags; $this->filterRequires = $filterRequires; foreach ($filterRequires as $name => $constraint) { if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) { unset($this->filterRequires[$name]); } } } public function setWhitelist($whitelist) { $this->whitelist = $whitelist; $this->providerCache = array(); } /** * Adds a repository and its packages to this package pool * * @param RepositoryInterface $repo A package repository * @param array $rootAliases */ public function addRepository(RepositoryInterface $repo, $rootAliases = array()) { if ($repo instanceof CompositeRepository) { $repos = $repo->getRepositories(); } else { $repos = array($repo); } foreach ($repos as $repo) { $this->repositories[] = $repo; $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface; if ($repo instanceof ComposerRepository && $repo->hasProviders()) { $this->providerRepos[] = $repo; $repo->setRootAliases($rootAliases); $repo->resetPackageIds(); } else { foreach ($repo->getPackages() as $package) { $names = $package->getNames(); $stability = $package->getStability(); if ($exempt || $this->isPackageAcceptable($names, $stability)) { $package->setId($this->id++); $this->packages[] = $package; $this->packageByExactName[$package->getName()][$package->id] = $package; foreach ($names as $provided) { $this->packageByName[$provided][] = $package; } // handle root package aliases $name = $package->getName(); if (isset($rootAliases[$name][$package->getVersion()])) { $alias = $rootAliases[$name][$package->getVersion()]; if ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); $aliasPackage->setRootPackageAlias(true); $aliasPackage->setId($this->id++); $package->getRepository()->addPackage($aliasPackage); $this->packages[] = $aliasPackage; $this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage; foreach ($aliasPackage->getNames() as $name) { $this->packageByName[$name][] = $aliasPackage; } } } } } } } public function getPriority(RepositoryInterface $repo) { $priority = array_search($repo, $this->repositories, true); if (false === $priority) { throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool."); } return -$priority; } /** * Retrieves the package object for a given package id. * * @param int $id * @return PackageInterface */ public function packageById($id) { return $this->packages[$id - 1]; } /** * Returns how many packages have been loaded into the pool */ public function count() { return count($this->packages); } /** * Searches all packages providing the given package name and match the constraint * * @param string $name The package name to be searched for * @param ConstraintInterface $constraint A constraint that all returned * packages must match or null to return all * @param bool $mustMatchName Whether the name of returned packages * must match the given name * @param bool $bypassFilters If enabled, filterRequires and stability matching is ignored * @return PackageInterface[] A set of packages */ public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false, $bypassFilters = false) { if ($bypassFilters) { return $this->computeWhatProvides($name, $constraint, $mustMatchName, true); } $key = ((int) $mustMatchName).$constraint; if (isset($this->providerCache[$name][$key])) { return $this->providerCache[$name][$key]; } return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName, $bypassFilters); } /** * @see whatProvides */ private function computeWhatProvides($name, $constraint, $mustMatchName = false, $bypassFilters = false) { $candidates = array(); foreach ($this->providerRepos as $repo) { foreach ($repo->whatProvides($this, $name, $bypassFilters) as $candidate) { $candidates[] = $candidate; if ($candidate->id < 1) { $candidate->setId($this->id++); $this->packages[$this->id - 2] = $candidate; } } } if ($mustMatchName) { $candidates = array_filter($candidates, function ($candidate) use ($name) { return $candidate->getName() == $name; }); if (isset($this->packageByExactName[$name])) { $candidates = array_merge($candidates, $this->packageByExactName[$name]); } } elseif (isset($this->packageByName[$name])) { $candidates = array_merge($candidates, $this->packageByName[$name]); } $matches = $provideMatches = array(); $nameMatch = false; foreach ($candidates as $candidate) { $aliasOfCandidate = null; // alias packages are not white listed, make sure that the package // being aliased is white listed if ($candidate instanceof AliasPackage) { $aliasOfCandidate = $candidate->getAliasOf(); } if ($this->whitelist !== null && !$bypassFilters && ( (!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) || ($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id])) )) { continue; } switch ($this->match($candidate, $name, $constraint, $bypassFilters)) { case self::MATCH_NONE: break; case self::MATCH_NAME: $nameMatch = true; break; case self::MATCH: $nameMatch = true; $matches[] = $candidate; break; case self::MATCH_PROVIDE: $provideMatches[] = $candidate; break; case self::MATCH_REPLACE: $matches[] = $candidate; break; case self::MATCH_FILTERED: break; default: throw new \UnexpectedValueException('Unexpected match type'); } } // if a package with the required name exists, we ignore providers if ($nameMatch) { return $matches; } return array_merge($matches, $provideMatches); } public function literalToPackage($literal) { $packageId = abs($literal); return $this->packageById($packageId); } public function literalToPrettyString($literal, $installedMap) { $package = $this->literalToPackage($literal); if (isset($installedMap[$package->id])) { $prefix = ($literal > 0 ? 'keep' : 'remove'); } else { $prefix = ($literal > 0 ? 'install' : 'don\'t install'); } return $prefix.' '.$package->getPrettyString(); } public function isPackageAcceptable($name, $stability) { foreach ((array) $name as $n) { // allow if package matches the global stability requirement and has no exception if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) { return true; } // allow if package matches the package-specific stability flag if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) { return true; } } return false; } /** * Checks if the package matches the given constraint directly or through * provided or replaced packages * * @param array|PackageInterface $candidate * @param string $name Name of the package to be matched * @param ConstraintInterface $constraint The constraint to verify * @return int One of the MATCH* constants of this class or 0 if there is no match */ private function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters) { $candidateName = $candidate->getName(); $candidateVersion = $candidate->getVersion(); $isDev = $candidate->getStability() === 'dev'; $isAlias = $candidate instanceof AliasPackage; if (!$bypassFilters && !$isDev && !$isAlias && isset($this->filterRequires[$name])) { $requireFilter = $this->filterRequires[$name]; } else { $requireFilter = new EmptyConstraint; } if ($candidateName === $name) { $pkgConstraint = new Constraint('==', $candidateVersion); if ($constraint === null || $constraint->matches($pkgConstraint)) { return $requireFilter->matches($pkgConstraint) ? self::MATCH : self::MATCH_FILTERED; } return self::MATCH_NAME; } $provides = $candidate->getProvides(); $replaces = $candidate->getReplaces(); // aliases create multiple replaces/provides for one target so they can not use the shortcut below if (isset($replaces[0]) || isset($provides[0])) { foreach ($provides as $link) { if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { return $requireFilter->matches($link->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED; } } foreach ($replaces as $link) { if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { return $requireFilter->matches($link->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED; } } return self::MATCH_NONE; } if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) { return $requireFilter->matches($provides[$name]->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED; } if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) { return $requireFilter->matches($replaces[$name]->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED; } return self::MATCH_NONE; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\CompletePackage; use Composer\Package\Link; use Composer\Package\PackageInterface; /** * @author Nils Adermann * @author Ruben Gonzalez */ abstract class Rule { // reason constants const RULE_INTERNAL_ALLOW_UPDATE = 1; const RULE_JOB_INSTALL = 2; const RULE_JOB_REMOVE = 3; const RULE_PACKAGE_CONFLICT = 6; const RULE_PACKAGE_REQUIRES = 7; const RULE_PACKAGE_OBSOLETES = 8; const RULE_INSTALLED_PACKAGE_OBSOLETES = 9; const RULE_PACKAGE_SAME_NAME = 10; const RULE_PACKAGE_IMPLICIT_OBSOLETES = 11; const RULE_LEARNED = 12; const RULE_PACKAGE_ALIAS = 13; // bitfield defs const BITFIELD_TYPE = 0; const BITFIELD_REASON = 8; const BITFIELD_DISABLED = 16; protected $bitfield; protected $reasonData; /** * @param int $reason A RULE_* constant describing the reason for generating this rule * @param Link|PackageInterface $reasonData * @param array $job The job this rule was created from */ public function __construct($reason, $reasonData, $job = null) { $this->reasonData = $reasonData; if ($job) { $this->job = $job; } $this->bitfield = (0 << self::BITFIELD_DISABLED) | ($reason << self::BITFIELD_REASON) | (255 << self::BITFIELD_TYPE); } abstract public function getLiterals(); abstract public function getHash(); public function getJob() { return isset($this->job) ? $this->job : null; } abstract public function equals(Rule $rule); public function getReason() { return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON; } public function getReasonData() { return $this->reasonData; } public function getRequiredPackage() { if ($this->getReason() === self::RULE_JOB_INSTALL) { return $this->reasonData; } if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) { return $this->reasonData->getTarget(); } } public function setType($type) { $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE); } public function getType() { return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE; } public function disable() { $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED); } public function enable() { $this->bitfield = $this->bitfield & ~(255 << self::BITFIELD_DISABLED); } public function isDisabled() { return (bool) (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } public function isEnabled() { return !(($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } abstract public function isAssertion(); public function getPrettyString(Pool $pool, array $installedMap = array()) { $literals = $this->getLiterals(); $ruleText = ''; foreach ($literals as $i => $literal) { if ($i != 0) { $ruleText .= '|'; } $ruleText .= $pool->literalToPrettyString($literal, $installedMap); } switch ($this->getReason()) { case self::RULE_INTERNAL_ALLOW_UPDATE: return $ruleText; case self::RULE_JOB_INSTALL: return "Install command rule ($ruleText)"; case self::RULE_JOB_REMOVE: return "Remove command rule ($ruleText)"; case self::RULE_PACKAGE_CONFLICT: $package1 = $pool->literalToPackage($literals[0]); $package2 = $pool->literalToPackage($literals[1]); return $package1->getPrettyString().' conflicts with '.$this->formatPackagesUnique($pool, array($package2)).'.'; case self::RULE_PACKAGE_REQUIRES: $sourceLiteral = array_shift($literals); $sourcePackage = $pool->literalToPackage($sourceLiteral); $requires = array(); foreach ($literals as $literal) { $requires[] = $pool->literalToPackage($literal); } $text = $this->reasonData->getPrettyString($sourcePackage); if ($requires) { $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires) . '.'; } else { $targetName = $this->reasonData->getTarget(); if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') { // handle php/hhvm if (defined('HHVM_VERSION')) { return $text . ' -> your HHVM version does not satisfy that requirement.'; } if ($targetName === 'hhvm') { return $text . ' -> you are running this with PHP and not HHVM.'; } $packages = $pool->whatProvides($targetName); $package = count($packages) ? current($packages) : phpversion(); if (!($package instanceof CompletePackage)) { return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.'; } $extra = $package->getExtra(); if (!empty($extra['config.platform'])) { $text .= ' -> your PHP version ('.phpversion().') overridden by "config.platform.php" version ('.$package->getPrettyVersion().') does not satisfy that requirement.'; } else { $text .= ' -> your PHP version ('.$package->getPrettyVersion().') does not satisfy that requirement.'; } return $text; } if (0 === strpos($targetName, 'ext-')) { // handle php extensions $ext = substr($targetName, 4); $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; return $text . ' -> the requested PHP extension '.$ext.' '.$error.'.'; } if (0 === strpos($targetName, 'lib-')) { // handle linked libs $lib = substr($targetName, 4); return $text . ' -> the requested linked library '.$lib.' has the wrong version installed or is missing from your system, make sure to have the extension providing it.'; } if ($providers = $pool->whatProvides($targetName, $this->reasonData->getConstraint(), true, true)) { return $text . ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $providers) .' but these conflict with your requirements or minimum-stability.'; } return $text . ' -> no matching package found.'; } return $text; case self::RULE_PACKAGE_OBSOLETES: return $ruleText; case self::RULE_INSTALLED_PACKAGE_OBSOLETES: return $ruleText; case self::RULE_PACKAGE_SAME_NAME: return 'Can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.'; case self::RULE_PACKAGE_IMPLICIT_OBSOLETES: return $ruleText; case self::RULE_LEARNED: return 'Conclusion: '.$ruleText; case self::RULE_PACKAGE_ALIAS: return $ruleText; default: return '('.$ruleText.')'; } } /** * @param Pool $pool * @param array $packages * * @return string */ protected function formatPackagesUnique($pool, array $packages) { $prepared = array(); foreach ($packages as $package) { if (!is_object($package)) { $package = $pool->literalToPackage($package); } $prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion(); } foreach ($prepared as $name => $package) { $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']'; } return implode(', ', $prepared); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; /** * @author Nils Adermann */ interface PolicyInterface { public function versionCompare(PackageInterface $a, PackageInterface $b, $operator); public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package); public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Semver\Constraint\Constraint; /** * @author Nils Adermann * @author Jordi Boggiano */ class DefaultPolicy implements PolicyInterface { private $preferStable; private $preferLowest; public function __construct($preferStable = false, $preferLowest = false) { $this->preferStable = $preferStable; $this->preferLowest = $preferLowest; } public function versionCompare(PackageInterface $a, PackageInterface $b, $operator) { if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) { return BasePackage::$stabilities[$stabA] < BasePackage::$stabilities[$stabB]; } $constraint = new Constraint($operator, $b->getVersion()); $version = new Constraint('==', $a->getVersion()); return $constraint->matchSpecific($version, true); } public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package, $mustMatchName = false) { $packages = array(); foreach ($pool->whatProvides($package->getName(), null, $mustMatchName) as $candidate) { if ($candidate !== $package) { $packages[] = $candidate; } } return $packages; } public function getPriority(Pool $pool, PackageInterface $package) { return $pool->getPriority($package->getRepository()); } public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null) { $packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals); foreach ($packages as &$literals) { $policy = $this; usort($literals, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) { return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true); }); } foreach ($packages as &$literals) { $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals); $literals = $this->pruneToBestVersion($pool, $literals); $literals = $this->pruneRemoteAliases($pool, $literals); } $selected = call_user_func_array('array_merge', $packages); // now sort the result across all packages to respect replaces across packages usort($selected, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) { return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage); }); return $selected; } protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals) { $packages = array(); foreach ($literals as $literal) { $packageName = $pool->literalToPackage($literal)->getName(); if (!isset($packages[$packageName])) { $packages[$packageName] = array(); } if (isset($installedMap[abs($literal)])) { array_unshift($packages[$packageName], $literal); } else { $packages[$packageName][] = $literal; } } return $packages; } /** * @protected */ public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap, PackageInterface $a, PackageInterface $b, $requiredPackage = null, $ignoreReplace = false) { if ($a->getRepository() === $b->getRepository()) { // prefer aliases to the original package if ($a->getName() === $b->getName()) { $aAliased = $a instanceof AliasPackage; $bAliased = $b instanceof AliasPackage; if ($aAliased && !$bAliased) { return -1; // use a } if (!$aAliased && $bAliased) { return 1; // use b } } if (!$ignoreReplace) { // return original, not replaced if ($this->replaces($a, $b)) { return 1; // use b } if ($this->replaces($b, $a)) { return -1; // use a } // for replacers not replacing each other, put a higher prio on replacing // packages with the same vendor as the required package if ($requiredPackage && false !== ($pos = strpos($requiredPackage, '/'))) { $requiredVendor = substr($requiredPackage, 0, $pos); $aIsSameVendor = substr($a->getName(), 0, $pos) === $requiredVendor; $bIsSameVendor = substr($b->getName(), 0, $pos) === $requiredVendor; if ($bIsSameVendor !== $aIsSameVendor) { return $aIsSameVendor ? -1 : 1; } } } // priority equal, sort by package id to make reproducible if ($a->id === $b->id) { return 0; } return ($a->id < $b->id) ? -1 : 1; } if (isset($installedMap[$a->id])) { return -1; } if (isset($installedMap[$b->id])) { return 1; } return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1; } /** * Checks if source replaces a package with the same name as target. * * Replace constraints are ignored. This method should only be used for * prioritisation, not for actual constraint verification. * * @param PackageInterface $source * @param PackageInterface $target * @return bool */ protected function replaces(PackageInterface $source, PackageInterface $target) { foreach ($source->getReplaces() as $link) { if ($link->getTarget() === $target->getName() // && (null === $link->getConstraint() || // $link->getConstraint()->matches(new Constraint('==', $target->getVersion())))) { ) { return true; } } return false; } protected function pruneToBestVersion(Pool $pool, $literals) { $operator = $this->preferLowest ? '<' : '>'; $bestLiterals = array($literals[0]); $bestPackage = $pool->literalToPackage($literals[0]); foreach ($literals as $i => $literal) { if (0 === $i) { continue; } $package = $pool->literalToPackage($literal); if ($this->versionCompare($package, $bestPackage, $operator)) { $bestPackage = $package; $bestLiterals = array($literal); } elseif ($this->versionCompare($package, $bestPackage, '==')) { $bestLiterals[] = $literal; } } return $bestLiterals; } /** * Assumes that installed packages come first and then all highest priority packages */ protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals) { $selected = array(); $priority = null; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if (isset($installedMap[$package->id])) { $selected[] = $literal; continue; } if (null === $priority) { $priority = $this->getPriority($pool, $package); } if ($this->getPriority($pool, $package) != $priority) { break; } $selected[] = $literal; } return $selected; } /** * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones * * If no package is a local alias, nothing happens */ protected function pruneRemoteAliases(Pool $pool, array $literals) { $hasLocalAlias = false; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { $hasLocalAlias = true; break; } } if (!$hasLocalAlias) { return $literals; } $selected = array(); foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { $selected[] = $literal; } } return $selected; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\AliasPackage; /** * @author Nils Adermann */ class Transaction { protected $policy; protected $pool; protected $installedMap; protected $decisions; protected $transaction; public function __construct($policy, $pool, $installedMap, $decisions) { $this->policy = $policy; $this->pool = $pool; $this->installedMap = $installedMap; $this->decisions = $decisions; $this->transaction = array(); } public function getOperations() { $installMeansUpdateMap = $this->findUpdates(); $updateMap = array(); $installMap = array(); $uninstallMap = array(); foreach ($this->decisions as $i => $decision) { $literal = $decision[Decisions::DECISION_LITERAL]; $reason = $decision[Decisions::DECISION_REASON]; $package = $this->pool->literalToPackage($literal); // wanted & installed || !wanted & !installed if (($literal > 0) == (isset($this->installedMap[$package->id]))) { continue; } if ($literal > 0) { if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) { $source = $installMeansUpdateMap[abs($literal)]; $updateMap[$package->id] = array( 'package' => $package, 'source' => $source, 'reason' => $reason, ); // avoid updates to one package from multiple origins unset($installMeansUpdateMap[abs($literal)]); $ignoreRemove[$source->id] = true; } else { $installMap[$package->id] = array( 'package' => $package, 'reason' => $reason, ); } } } foreach ($this->decisions as $i => $decision) { $literal = $decision[Decisions::DECISION_LITERAL]; $reason = $decision[Decisions::DECISION_REASON]; $package = $this->pool->literalToPackage($literal); if ($literal <= 0 && isset($this->installedMap[$package->id]) && !isset($ignoreRemove[$package->id])) { $uninstallMap[$package->id] = array( 'package' => $package, 'reason' => $reason, ); } } $this->transactionFromMaps($installMap, $updateMap, $uninstallMap); return $this->transaction; } protected function transactionFromMaps($installMap, $updateMap, $uninstallMap) { $queue = array_map( function ($operation) { return $operation['package']; }, $this->findRootPackages($installMap, $updateMap) ); $visited = array(); while (!empty($queue)) { $package = array_pop($queue); $packageId = $package->id; if (!isset($visited[$packageId])) { array_push($queue, $package); if ($package instanceof AliasPackage) { array_push($queue, $package->getAliasOf()); } else { foreach ($package->getRequires() as $link) { $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); foreach ($possibleRequires as $require) { array_push($queue, $require); } } } $visited[$package->id] = true; } else { if (isset($installMap[$packageId])) { $this->install( $installMap[$packageId]['package'], $installMap[$packageId]['reason'] ); unset($installMap[$packageId]); } if (isset($updateMap[$packageId])) { $this->update( $updateMap[$packageId]['source'], $updateMap[$packageId]['package'], $updateMap[$packageId]['reason'] ); unset($updateMap[$packageId]); } } } foreach ($uninstallMap as $uninstall) { $this->uninstall($uninstall['package'], $uninstall['reason']); } } protected function findRootPackages($installMap, $updateMap) { $packages = $installMap + $updateMap; $roots = $packages; foreach ($packages as $packageId => $operation) { $package = $operation['package']; if (!isset($roots[$packageId])) { continue; } foreach ($package->getRequires() as $link) { $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); foreach ($possibleRequires as $require) { if ($require !== $package) { unset($roots[$require->id]); } } } } return $roots; } protected function findUpdates() { $installMeansUpdateMap = array(); foreach ($this->decisions as $i => $decision) { $literal = $decision[Decisions::DECISION_LITERAL]; $package = $this->pool->literalToPackage($literal); if ($package instanceof AliasPackage) { continue; } // !wanted & installed if ($literal <= 0 && isset($this->installedMap[$package->id])) { $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package); $literals = array($package->id); foreach ($updates as $update) { $literals[] = $update->id; } foreach ($literals as $updateLiteral) { if ($updateLiteral !== $literal) { $installMeansUpdateMap[abs($updateLiteral)] = $package; } } } } return $installMeansUpdateMap; } protected function install($package, $reason) { if ($package instanceof AliasPackage) { return $this->markAliasInstalled($package, $reason); } $this->transaction[] = new Operation\InstallOperation($package, $reason); } protected function update($from, $to, $reason) { $this->transaction[] = new Operation\UpdateOperation($from, $to, $reason); } protected function uninstall($package, $reason) { if ($package instanceof AliasPackage) { return $this->markAliasUninstalled($package, $reason); } $this->transaction[] = new Operation\UninstallOperation($package, $reason); } protected function markAliasInstalled($package, $reason) { $this->transaction[] = new Operation\MarkAliasInstalledOperation($package, $reason); } protected function markAliasUninstalled($package, $reason) { $this->transaction[] = new Operation\MarkAliasUninstalledOperation($package, $reason); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; use Composer\Package\Link; /** * @author Nils Adermann */ class Rule2Literals extends Rule { protected $literal1; protected $literal2; /** * @param int $literal1 * @param int $literal2 * @param int $reason A RULE_* constant describing the reason for generating this rule * @param Link|PackageInterface $reasonData * @param array $job The job this rule was created from */ public function __construct($literal1, $literal2, $reason, $reasonData, $job = null) { parent::__construct($reason, $reasonData, $job); if ($literal1 < $literal2) { $this->literal1 = $literal1; $this->literal2 = $literal2; } else { $this->literal1 = $literal2; $this->literal2 = $literal1; } } public function getLiterals() { return array($this->literal1, $this->literal2); } public function getHash() { $data = unpack('ihash', md5($this->literal1.','.$this->literal2, true)); return $data['hash']; } /** * Checks if this rule is equal to another one * * Ignores whether either of the rules is disabled. * * @param Rule $rule The rule to check against * @return bool Whether the rules are equal */ public function equals(Rule $rule) { $literals = $rule->getLiterals(); if (2 != count($literals)) { return false; } if ($this->literal1 !== $literals[0]) { return false; } if ($this->literal2 !== $literals[1]) { return false; } return true; } public function isAssertion() { return false; } /** * Formats a rule as a string of the format (Literal1|Literal2|...) * * @return string */ public function __toString() { $result = ($this->isDisabled()) ? 'disabled(' : '('; $result .= $this->literal1 . '|' . $this->literal2 . ')'; return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * An extension of SplDoublyLinkedList with seek and removal of current element * * SplDoublyLinkedList only allows deleting a particular offset and has no * method to set the internal iterator to a particular offset. * * @author Nils Adermann */ class RuleWatchChain extends \SplDoublyLinkedList { protected $offset = 0; /** * Moves the internal iterator to the specified offset * * @param int $offset The offset to seek to. */ public function seek($offset) { $this->rewind(); for ($i = 0; $i < $offset; $i++, $this->next()); } /** * Removes the current element from the list * * As SplDoublyLinkedList only allows deleting a particular offset and * incorrectly sets the internal iterator if you delete the current value * this method sets the internal iterator back to the following element * using the seek method. */ public function remove() { $offset = $this->key(); $this->offsetUnset($offset); $this->seek($offset); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann */ class RuleSetIterator implements \Iterator { protected $rules; protected $types; protected $currentOffset; protected $currentType; protected $currentTypeOffset; public function __construct(array $rules) { $this->rules = $rules; $this->types = array_keys($rules); sort($this->types); $this->rewind(); } public function current() { return $this->rules[$this->currentType][$this->currentOffset]; } public function key() { return $this->currentType; } public function next() { $this->currentOffset++; if (!isset($this->rules[$this->currentType])) { return; } if ($this->currentOffset >= sizeof($this->rules[$this->currentType])) { $this->currentOffset = 0; do { $this->currentTypeOffset++; if (!isset($this->types[$this->currentTypeOffset])) { $this->currentType = -1; break; } $this->currentType = $this->types[$this->currentTypeOffset]; } while (isset($this->types[$this->currentTypeOffset]) && !sizeof($this->rules[$this->currentType])); } } public function rewind() { $this->currentOffset = 0; $this->currentTypeOffset = -1; $this->currentType = -1; do { $this->currentTypeOffset++; if (!isset($this->types[$this->currentTypeOffset])) { $this->currentType = -1; break; } $this->currentType = $this->types[$this->currentTypeOffset]; } while (isset($this->types[$this->currentTypeOffset]) && !sizeof($this->rules[$this->currentType])); } public function valid() { return isset($this->rules[$this->currentType]) && isset($this->rules[$this->currentType][$this->currentOffset]); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Repository\PlatformRepository; /** * @author Nils Adermann */ class RuleSetGenerator { protected $policy; protected $pool; protected $rules; protected $jobs; protected $installedMap; protected $whitelistedMap; protected $addedMap; public function __construct(PolicyInterface $policy, Pool $pool) { $this->policy = $policy; $this->pool = $pool; } /** * Creates a new rule for the requirements of a package * * This rule is of the form (-A|B|C), where B and C are the providers of * one requirement of the package A. * * @param PackageInterface $package The package with a requirement * @param array $providers The providers of the requirement * @param int $reason A RULE_* constant describing the * reason for generating this rule * @param mixed $reasonData Any data, e.g. the requirement name, * that goes with the reason * @return Rule The generated rule or null if tautological */ protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) { $literals = array(-$package->id); foreach ($providers as $provider) { // self fulfilling rule? if ($provider === $package) { return null; } $literals[] = $provider->id; } return new GenericRule($literals, $reason, $reasonData); } /** * Creates a rule to install at least one of a set of packages * * The rule is (A|B|C) with A, B and C different packages. If the given * set of packages is empty an impossible rule is generated. * * @param array $packages The set of packages to choose from * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param array $job The job this rule was created from * @return Rule The generated rule */ protected function createInstallOneOfRule(array $packages, $reason, $job) { $literals = array(); foreach ($packages as $package) { $literals[] = $package->id; } return new GenericRule($literals, $reason, $job['packageName'], $job); } /** * Creates a rule to remove a package * * The rule for a package A is (-A). * * @param PackageInterface $package The package to be removed * @param int $reason A RULE_* constant describing the * reason for generating this rule * @param array $job The job this rule was created from * @return Rule The generated rule */ protected function createRemoveRule(PackageInterface $package, $reason, $job) { return new GenericRule(array(-$package->id), $reason, $job['packageName'], $job); } /** * Creates a rule for two conflicting packages * * The rule for conflicting packages A and B is (-A|-B). A is called the issuer * and B the provider. * * @param PackageInterface $issuer The package declaring the conflict * @param PackageInterface $provider The package causing the conflict * @param int $reason A RULE_* constant describing the * reason for generating this rule * @param mixed $reasonData Any data, e.g. the package name, that * goes with the reason * @return Rule The generated rule */ protected function createRule2Literals(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) { // ignore self conflict if ($issuer === $provider) { return null; } return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData); } /** * Adds a rule unless it duplicates an existing one of any type * * To be able to directly pass in the result of one of the rule creation * methods null is allowed which will not insert a rule. * * @param int $type A TYPE_* constant defining the rule type * @param Rule $newRule The rule about to be added */ private function addRule($type, Rule $newRule = null) { if (!$newRule) { return; } $this->rules->add($newRule, $type); } protected function whitelistFromPackage(PackageInterface $package) { $workQueue = new \SplQueue; $workQueue->enqueue($package); while (!$workQueue->isEmpty()) { $package = $workQueue->dequeue(); if (isset($this->whitelistedMap[$package->id])) { continue; } $this->whitelistedMap[$package->id] = true; foreach ($package->getRequires() as $link) { $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true); foreach ($possibleRequires as $require) { $workQueue->enqueue($require); } } $obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true); foreach ($obsoleteProviders as $provider) { if ($provider === $package) { continue; } if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) { $workQueue->enqueue($provider); } } } } protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs) { $workQueue = new \SplQueue; $workQueue->enqueue($package); while (!$workQueue->isEmpty()) { $package = $workQueue->dequeue(); if (isset($this->addedMap[$package->id])) { continue; } $this->addedMap[$package->id] = true; foreach ($package->getRequires() as $link) { if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { continue; } $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); foreach ($possibleRequires as $require) { $workQueue->enqueue($require); } } foreach ($package->getConflicts() as $link) { $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); foreach ($possibleConflicts as $conflict) { $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link)); } } // check obsoletes and implicit obsoletes of a package $isInstalled = (isset($this->installedMap[$package->id])); foreach ($package->getReplaces() as $link) { $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); foreach ($obsoleteProviders as $provider) { if ($provider === $package) { continue; } if (!$this->obsoleteImpossibleForAlias($package, $provider)) { $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link)); } } } $obsoleteProviders = $this->pool->whatProvides($package->getName(), null); foreach ($obsoleteProviders as $provider) { if ($provider === $package) { continue; } if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) { $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package)); } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) { $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES; $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRule2Literals($package, $provider, $reason, $package)); } } } } protected function obsoleteImpossibleForAlias($package, $provider) { $packageIsAlias = $package instanceof AliasPackage; $providerIsAlias = $provider instanceof AliasPackage; $impossible = ( ($packageIsAlias && $package->getAliasOf() === $provider) || ($providerIsAlias && $provider->getAliasOf() === $package) || ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf()) ); return $impossible; } protected function whitelistFromJobs() { foreach ($this->jobs as $job) { switch ($job['cmd']) { case 'install': $packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true); foreach ($packages as $package) { $this->whitelistFromPackage($package); } break; } } } protected function addRulesForJobs($ignorePlatformReqs) { foreach ($this->jobs as $job) { switch ($job['cmd']) { case 'install': if (!$job['fixed'] && $ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) { continue; } $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); if ($packages) { foreach ($packages as $package) { if (!isset($this->installedMap[$package->id])) { $this->addRulesForPackage($package, $ignorePlatformReqs); } } $rule = $this->createInstallOneOfRule($packages, Rule::RULE_JOB_INSTALL, $job); $this->addRule(RuleSet::TYPE_JOB, $rule); } break; case 'remove': // remove all packages with this name including uninstalled // ones to make sure none of them are picked as replacements $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); foreach ($packages as $package) { $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job); $this->addRule(RuleSet::TYPE_JOB, $rule); } break; } } } public function getRulesFor($jobs, $installedMap, $ignorePlatformReqs = false) { $this->jobs = $jobs; $this->rules = new RuleSet; $this->installedMap = $installedMap; $this->whitelistedMap = array(); foreach ($this->installedMap as $package) { $this->whitelistFromPackage($package); } $this->whitelistFromJobs(); $this->pool->setWhitelist($this->whitelistedMap); $this->addedMap = array(); foreach ($this->installedMap as $package) { $this->addRulesForPackage($package, $ignorePlatformReqs); } $this->addRulesForJobs($ignorePlatformReqs); return $this->rules; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann */ class RuleSet implements \IteratorAggregate, \Countable { // highest priority => lowest number const TYPE_PACKAGE = 0; const TYPE_JOB = 1; const TYPE_LEARNED = 4; /** * READ-ONLY: Lookup table for rule id to rule object * * @var Rule[] */ public $ruleById; protected static $types = array( 255 => 'UNKNOWN', self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_JOB => 'JOB', self::TYPE_LEARNED => 'LEARNED', ); protected $rules; protected $nextRuleId; protected $rulesByHash; public function __construct() { $this->nextRuleId = 0; foreach ($this->getTypes() as $type) { $this->rules[$type] = array(); } $this->rulesByHash = array(); } public function add(Rule $rule, $type) { if (!isset(self::$types[$type])) { throw new \OutOfBoundsException('Unknown rule type: ' . $type); } $hash = $rule->getHash(); // Do not add if rule already exists if (isset($this->rulesByHash[$hash])) { $potentialDuplicates = $this->rulesByHash[$hash]; if (is_array($potentialDuplicates)) { foreach ($potentialDuplicates as $potentialDuplicate) { if ($rule->equals($potentialDuplicate)) { return; } } } else { if ($rule->equals($potentialDuplicates)) { return; } } } if (!isset($this->rules[$type])) { $this->rules[$type] = array(); } $this->rules[$type][] = $rule; $this->ruleById[$this->nextRuleId] = $rule; $rule->setType($type); $this->nextRuleId++; if (!isset($this->rulesByHash[$hash])) { $this->rulesByHash[$hash] = $rule; } elseif (is_array($this->rulesByHash[$hash])) { $this->rulesByHash[$hash][] = $rule; } else { $originalRule = $this->rulesByHash[$hash]; $this->rulesByHash[$hash] = array($originalRule, $rule); } } public function count() { return $this->nextRuleId; } public function ruleById($id) { return $this->ruleById[$id]; } public function getRules() { return $this->rules; } public function getIterator() { return new RuleSetIterator($this->getRules()); } public function getIteratorFor($types) { if (!is_array($types)) { $types = array($types); } $allRules = $this->getRules(); $rules = array(); foreach ($types as $type) { $rules[$type] = $allRules[$type]; } return new RuleSetIterator($rules); } public function getIteratorWithout($types) { if (!is_array($types)) { $types = array($types); } $rules = $this->getRules(); foreach ($types as $type) { unset($rules[$type]); } return new RuleSetIterator($rules); } public function getTypes() { $types = self::$types; unset($types[255]); return array_keys($types); } public function getPrettyString(Pool $pool = null) { $string = "\n"; foreach ($this->rules as $type => $rules) { $string .= str_pad(self::$types[$type], 8, ' ') . ": "; foreach ($rules as $rule) { $string .= ($pool ? $rule->getPrettyString($pool) : $rule)."\n"; } $string .= "\n\n"; } return $string; } public function __toString() { return $this->getPrettyString(null); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * Represents a problem detected while solving dependencies * * @author Nils Adermann */ class Problem { /** * A map containing the id of each rule part of this problem as a key * @var array */ protected $reasonSeen; /** * A set of reasons for the problem, each is a rule or a job and a rule * @var array */ protected $reasons = array(); protected $section = 0; protected $pool; public function __construct(Pool $pool) { $this->pool = $pool; } /** * Add a rule as a reason * * @param Rule $rule A rule which is a reason for this problem */ public function addRule(Rule $rule) { $this->addReason(spl_object_hash($rule), array( 'rule' => $rule, 'job' => $rule->getJob(), )); } /** * Retrieve all reasons for this problem * * @return array The problem's reasons */ public function getReasons() { return $this->reasons; } /** * A human readable textual representation of the problem's reasons * * @param array $installedMap A map of all installed packages * @return string */ public function getPrettyString(array $installedMap = array()) { $reasons = call_user_func_array('array_merge', array_reverse($this->reasons)); if (count($reasons) === 1) { reset($reasons); $reason = current($reasons); $rule = $reason['rule']; $job = $reason['job']; if (isset($job['constraint'])) { $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); } else { $packages = array(); } if ($job && $job['cmd'] === 'install' && empty($packages)) { // handle php/hhvm if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { $available = $this->pool->whatProvides($job['packageName']); $version = count($available) ? $available[0]->getPrettyVersion() : phpversion(); $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; if (defined('HHVM_VERSION')) { return $msg . 'your HHVM version does not satisfy that requirement.'; } if ($job['packageName'] === 'hhvm') { return $msg . 'you are running this with PHP and not HHVM.'; } return $msg . 'your PHP version ('. $version .') does not satisfy that requirement.'; } // handle php extensions if (0 === stripos($job['packageName'], 'ext-')) { $ext = substr($job['packageName'], 4); $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; return "\n - The requested PHP extension ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.'; } // handle linked libs if (0 === stripos($job['packageName'], 'lib-')) { if (strtolower($job['packageName']) === 'lib-icu') { $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.'; return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error; } return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.'; } if (!preg_match('{^[A-Za-z0-9_./-]+$}', $job['packageName'])) { $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $job['packageName']); return "\n - The requested package ".$job['packageName'].' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'; } if ($providers = $this->pool->whatProvides($job['packageName'], $job['constraint'], true, true)) { return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.'; } if ($providers = $this->pool->whatProvides($job['packageName'], null, true, true)) { return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.'; } return "\n - The requested package ".$job['packageName'].' could not be found in any version, there may be a typo in the package name.'; } } $messages = array(); foreach ($reasons as $reason) { $rule = $reason['rule']; $job = $reason['job']; if ($job) { $messages[] = $this->jobToText($job); } elseif ($rule) { if ($rule instanceof Rule) { $messages[] = $rule->getPrettyString($this->pool, $installedMap); } } } return "\n - ".implode("\n - ", $messages); } /** * Store a reason descriptor but ignore duplicates * * @param string $id A canonical identifier for the reason * @param string $reason The reason descriptor */ protected function addReason($id, $reason) { if (!isset($this->reasonSeen[$id])) { $this->reasonSeen[$id] = true; $this->reasons[$this->section][] = $reason; } } public function nextSection() { $this->section++; } /** * Turns a job into a human readable description * * @param array $job * @return string */ protected function jobToText($job) { switch ($job['cmd']) { case 'install': $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); if (!$packages) { return 'No package found to satisfy install request for '.$job['packageName'].$this->constraintToText($job['constraint']); } return 'Installation request for '.$job['packageName'].$this->constraintToText($job['constraint']).' -> satisfiable by '.$this->getPackageList($packages).'.'; case 'update': return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.'; case 'remove': return 'Removal request for '.$job['packageName'].$this->constraintToText($job['constraint']).''; } if (isset($job['constraint'])) { $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); } else { $packages = array(); } return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($packages).'])'; } protected function getPackageList($packages) { $prepared = array(); foreach ($packages as $package) { $prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion(); } foreach ($prepared as $name => $package) { $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']'; } return implode(', ', $prepared); } /** * Turns a constraint into text usable in a sentence describing a job * * @param \Composer\Semver\Constraint\ConstraintInterface $constraint * @return string */ protected function constraintToText($constraint) { return ($constraint) ? ' '.$constraint->getPrettyString() : ''; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Util\IniHelper; /** * @author Nils Adermann */ class SolverProblemsException extends \RuntimeException { protected $problems; protected $installedMap; public function __construct(array $problems, array $installedMap) { $this->problems = $problems; $this->installedMap = $installedMap; parent::__construct($this->createMessage(), 2); } protected function createMessage() { $text = "\n"; $hasExtensionProblems = false; foreach ($this->problems as $i => $problem) { $text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n"; if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) { $hasExtensionProblems = true; } } if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) { $text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n\nRead for further common problems."; } if ($hasExtensionProblems) { $text .= $this->createExtensionHint(); } return $text; } public function getProblems() { return $this->problems; } private function createExtensionHint() { $paths = IniHelper::getAll(); if (count($paths) === 1 && empty($paths[0])) { return ''; } $text = "\n To enable extensions, verify that they are enabled in your .ini files:\n - "; $text .= implode("\n - ", $paths); $text .= "\n You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode."; return $text; } private function hasExtensionProblems(array $reasonSets) { foreach ($reasonSets as $reasonSet) { foreach ($reasonSet as $reason) { if (isset($reason["rule"]) && 0 === strpos($reason["rule"]->getRequiredPackage(), 'ext-')) { return true; } } } return false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * Stores decisions on installing, removing or keeping packages * * @author Nils Adermann */ class Decisions implements \Iterator, \Countable { const DECISION_LITERAL = 0; const DECISION_REASON = 1; protected $pool; protected $decisionMap; protected $decisionQueue = array(); public function __construct($pool) { $this->pool = $pool; $this->decisionMap = array(); } public function decide($literal, $level, $why) { $this->addDecision($literal, $level); $this->decisionQueue[] = array( self::DECISION_LITERAL => $literal, self::DECISION_REASON => $why, ); } public function satisfy($literal) { $packageId = abs($literal); return ( $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 || $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 ); } public function conflict($literal) { $packageId = abs($literal); return ( (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) || (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0) ); } public function decided($literalOrPackageId) { return !empty($this->decisionMap[abs($literalOrPackageId)]); } public function undecided($literalOrPackageId) { return empty($this->decisionMap[abs($literalOrPackageId)]); } public function decidedInstall($literalOrPackageId) { $packageId = abs($literalOrPackageId); return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0; } public function decisionLevel($literalOrPackageId) { $packageId = abs($literalOrPackageId); if (isset($this->decisionMap[$packageId])) { return abs($this->decisionMap[$packageId]); } return 0; } public function decisionRule($literalOrPackageId) { $packageId = abs($literalOrPackageId); foreach ($this->decisionQueue as $i => $decision) { if ($packageId === abs($decision[self::DECISION_LITERAL])) { return $decision[self::DECISION_REASON]; } } return null; } public function atOffset($queueOffset) { return $this->decisionQueue[$queueOffset]; } public function validOffset($queueOffset) { return $queueOffset >= 0 && $queueOffset < count($this->decisionQueue); } public function lastReason() { return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_REASON]; } public function lastLiteral() { return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_LITERAL]; } public function reset() { while ($decision = array_pop($this->decisionQueue)) { $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; } } public function resetToOffset($offset) { while (count($this->decisionQueue) > $offset + 1) { $decision = array_pop($this->decisionQueue); $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; } } public function revertLast() { $this->decisionMap[abs($this->lastLiteral())] = 0; array_pop($this->decisionQueue); } public function count() { return count($this->decisionQueue); } public function rewind() { end($this->decisionQueue); } public function current() { return current($this->decisionQueue); } public function key() { return key($this->decisionQueue); } public function next() { return prev($this->decisionQueue); } public function valid() { return false !== current($this->decisionQueue); } public function isEmpty() { return count($this->decisionQueue) === 0; } protected function addDecision($literal, $level) { $packageId = abs($literal); $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null; if ($previousDecision != 0) { $literalString = $this->pool->literalToString($literal); $package = $this->pool->literalToPackage($literal); throw new SolverBugException( "Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."." ); } if ($literal > 0) { $this->decisionMap[$packageId] = $level; } else { $this->decisionMap[$packageId] = -$level; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\PackageInterface; /** * Solver update operation. * * @author Konstantin Kudryashov */ class UpdateOperation extends SolverOperation { protected $initialPackage; protected $targetPackage; /** * Initializes update operation. * * @param PackageInterface $initial initial package * @param PackageInterface $target target package (updated) * @param string $reason update reason */ public function __construct(PackageInterface $initial, PackageInterface $target, $reason = null) { parent::__construct($reason); $this->initialPackage = $initial; $this->targetPackage = $target; } /** * Returns initial package. * * @return PackageInterface */ public function getInitialPackage() { return $this->initialPackage; } /** * Returns target package. * * @return PackageInterface */ public function getTargetPackage() { return $this->targetPackage; } /** * Returns job type. * * @return string */ public function getJobType() { return 'update'; } /** * {@inheritDoc} */ public function __toString() { return 'Updating '.$this->initialPackage->getPrettyName().' ('.$this->formatVersion($this->initialPackage).') to '. $this->targetPackage->getPrettyName(). ' ('.$this->formatVersion($this->targetPackage).')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; /** * Solver install operation. * * @author Nils Adermann */ class MarkAliasInstalledOperation extends SolverOperation { protected $package; /** * Initializes operation. * * @param AliasPackage $package package instance * @param string $reason operation reason */ public function __construct(AliasPackage $package, $reason = null) { parent::__construct($reason); $this->package = $package; } /** * Returns package instance. * * @return PackageInterface */ public function getPackage() { return $this->package; } /** * Returns job type. * * @return string */ public function getJobType() { return 'markAliasInstalled'; } /** * {@inheritDoc} */ public function __toString() { return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\PackageInterface; /** * Abstract solver operation class. * * @author Konstantin Kudryashov */ abstract class SolverOperation implements OperationInterface { protected $reason; /** * Initializes operation. * * @param string $reason operation reason */ public function __construct($reason = null) { $this->reason = $reason; } /** * Returns operation reason. * * @return string */ public function getReason() { return $this->reason; } protected function formatVersion(PackageInterface $package) { return $package->getFullPrettyVersion(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\PackageInterface; /** * Solver uninstall operation. * * @author Konstantin Kudryashov */ class UninstallOperation extends SolverOperation { protected $package; /** * Initializes operation. * * @param PackageInterface $package package instance * @param string $reason operation reason */ public function __construct(PackageInterface $package, $reason = null) { parent::__construct($reason); $this->package = $package; } /** * Returns package instance. * * @return PackageInterface */ public function getPackage() { return $this->package; } /** * Returns job type. * * @return string */ public function getJobType() { return 'uninstall'; } /** * {@inheritDoc} */ public function __toString() { return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; /** * Solver operation interface. * * @author Konstantin Kudryashov */ interface OperationInterface { /** * Returns job type. * * @return string */ public function getJobType(); /** * Returns operation reason. * * @return string */ public function getReason(); /** * Serializes the operation in a human readable format * * @return string */ public function __toString(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\PackageInterface; /** * Solver install operation. * * @author Konstantin Kudryashov */ class InstallOperation extends SolverOperation { protected $package; /** * Initializes operation. * * @param PackageInterface $package package instance * @param string $reason operation reason */ public function __construct(PackageInterface $package, $reason = null) { parent::__construct($reason); $this->package = $package; } /** * Returns package instance. * * @return PackageInterface */ public function getPackage() { return $this->package; } /** * Returns job type. * * @return string */ public function getJobType() { return 'install'; } /** * {@inheritDoc} */ public function __toString() { return 'Installing '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; /** * Solver install operation. * * @author Nils Adermann */ class MarkAliasUninstalledOperation extends SolverOperation { protected $package; /** * Initializes operation. * * @param AliasPackage $package package instance * @param string $reason operation reason */ public function __construct(AliasPackage $package, $reason = null) { parent::__construct($reason); $this->package = $package; } /** * Returns package instance. * * @return PackageInterface */ public function getPackage() { return $this->package; } /** * Returns job type. * * @return string */ public function getJobType() { return 'markAliasUninstalled'; } /** * {@inheritDoc} */ public function __toString() { return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann */ class SolverBugException extends \RuntimeException { public function __construct($message) { parent::__construct( $message."\nThis exception was most likely caused by a bug in Composer.\n". "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n"); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; /** * ChangeReport interface. * * @author Sascha Egerer */ interface ChangeReportInterface { /** * Checks for changes to the local copy * * @param PackageInterface $package package instance * @param string $path package directory * @return string|null changes or null */ public function getLocalChanges(PackageInterface $package, $path); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; use Composer\Util\RemoteFilesystem; /** * Base downloader for files * * @author Kirill chEbba Chebunin * @author Jordi Boggiano * @author François Pluchino * @author Nils Adermann */ class FileDownloader implements DownloaderInterface { protected $io; protected $config; protected $rfs; protected $filesystem; protected $cache; protected $outputProgress = true; private $lastCacheWrites = array(); private $eventDispatcher; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The config * @param EventDispatcher $eventDispatcher The event dispatcher * @param Cache $cache Optional cache instance * @param RemoteFilesystem $rfs The remote filesystem * @param Filesystem $filesystem The filesystem */ public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null) { $this->io = $io; $this->config = $config; $this->eventDispatcher = $eventDispatcher; $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config); $this->filesystem = $filesystem ?: new Filesystem(); $this->cache = $cache; if ($this->cache && $this->cache->gcIsNecessary()) { $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); } } /** * {@inheritDoc} */ public function getInstallationSource() { return 'dist'; } /** * {@inheritDoc} */ public function download(PackageInterface $package, $path, $output = true) { if (!$package->getDistUrl()) { throw new \InvalidArgumentException('The given package is missing url information'); } if ($output) { $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "): ", false); } $urls = $package->getDistUrls(); while ($url = array_shift($urls)) { try { $fileName = $this->doDownload($package, $path, $url); break; } catch (\Exception $e) { if ($this->io->isDebug()) { $this->io->writeError(''); $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); } elseif (count($urls)) { $this->io->writeError(''); $this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false); } if (!count($urls)) { throw $e; } } } if ($output) { $this->io->writeError(''); } return $fileName; } protected function doDownload(PackageInterface $package, $path, $url) { $this->filesystem->emptyDirectory($path); $fileName = $this->getFileName($package, $path); $processedUrl = $this->processUrl($package, $url); $hostname = parse_url($processedUrl, PHP_URL_HOST); $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl); if ($this->eventDispatcher) { $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } $rfs = $preFileDownloadEvent->getRemoteFilesystem(); try { $checksum = $package->getDistSha1Checksum(); $cacheKey = $this->getCacheKey($package, $processedUrl); // download if we don't have it in cache or the cache is invalidated if (!$this->cache || ($checksum && $checksum !== $this->cache->sha1($cacheKey)) || !$this->cache->copyTo($cacheKey, $fileName)) { if (!$this->outputProgress) { $this->io->writeError('Downloading', false); } // try to download 3 times then fail hard $retries = 3; while ($retries--) { try { $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions()); break; } catch (TransportException $e) { // if we got an http response with a proper code, then requesting again will probably not help, abort if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) { throw $e; } $this->io->writeError(''); $this->io->writeError(' Download failed, retrying...', true, IOInterface::VERBOSE); usleep(500000); } } if (!$this->outputProgress) { $this->io->writeError(' (100%)', false); } if ($this->cache) { $this->lastCacheWrites[$package->getName()] = $cacheKey; $this->cache->copyFrom($cacheKey, $fileName); } } else { $this->io->writeError('Loading from cache', false); } if (!file_exists($fileName)) { throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' .' directory is writable and you have internet connectivity'); } if ($checksum && hash_file('sha1', $fileName) !== $checksum) { throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')'); } } catch (\Exception $e) { // clean up $this->filesystem->removeDirectory($path); $this->clearLastCacheWrite($package); throw $e; } return $fileName; } /** * {@inheritDoc} */ public function setOutputProgress($outputProgress) { $this->outputProgress = $outputProgress; return $this; } protected function clearLastCacheWrite(PackageInterface $package) { if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) { $this->cache->remove($this->lastCacheWrites[$package->getName()]); unset($this->lastCacheWrites[$package->getName()]); } } /** * {@inheritDoc} */ public function update(PackageInterface $initial, PackageInterface $target, $path) { $name = $target->getName(); $from = $initial->getPrettyVersion(); $to = $target->getPrettyVersion(); $this->io->writeError(" - Updating " . $name . " (" . $from . " => " . $to . "): ", false); $this->remove($initial, $path, false); $this->download($target, $path, false); $this->io->writeError(''); } /** * {@inheritDoc} */ public function remove(PackageInterface $package, $path, $output = true) { if ($output) { $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } } /** * Gets file name for specific package * * @param PackageInterface $package package instance * @param string $path download path * @return string file name */ protected function getFileName(PackageInterface $package, $path) { return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); } /** * Process the download url * * @param PackageInterface $package package the url is coming from * @param string $url download url * @throws \RuntimeException If any problem with the url * @return string url */ protected function processUrl(PackageInterface $package, $url) { if (!extension_loaded('openssl') && 0 === strpos($url, 'https:')) { throw new \RuntimeException('You must enable the openssl extension to download files via https'); } return $url; } private function getCacheKey(PackageInterface $package, $processedUrl) { // we use the complete download url here to avoid conflicting entries // from different packages, which would potentially allow a given package // in a third party repo to pre-populate the cache for the same package in // packagist for example. $cacheKey = sha1($processedUrl); return $package->getName().'/'.$cacheKey.'.'.$package->getDistType(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\Git as GitUtil; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use Composer\Util\Filesystem; use Composer\Config; /** * @author Jordi Boggiano */ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface { private $hasStashedChanges = false; private $hasDiscardedChanges = false; private $gitUtil; public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null) { parent::__construct($io, $config, $process, $fs); $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem); } /** * {@inheritDoc} */ public function doDownload(PackageInterface $package, $path, $url) { GitUtil::cleanEnv(); $path = $this->normalizePath($path); $cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/'; $cacheOptions = ''; $ref = $package->getSourceReference(); $flag = Platform::isWindows() ? '/D ' : ''; // --dissociate option is only available since git 2.3.0-rc0 $gitVersion = $this->gitUtil->getVersion(); $msg = "Cloning ".$this->getShortHash($ref); if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=')) { $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG); try { $this->gitUtil->syncMirror($url, $cachePath); if (is_dir($cachePath)) { $cacheOptions = sprintf('--dissociate --reference %s ', ProcessExecutor::escape($cachePath)); $msg = "Cloning ".$this->getShortHash($ref).' from cache'; } } catch (\RuntimeException $e) { } } $command = 'git clone --no-checkout %s %s '.$cacheOptions.'&& cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer'; $this->io->writeError($msg); $commandCallable = function ($url) use ($ref, $path, $command) { return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($path), ProcessExecutor::escape($ref)); }; $this->gitUtil->runCommand($commandCallable, $url, $path, true); if ($url !== $package->getSourceUrl()) { $this->updateOriginUrl($path, $package->getSourceUrl()); } else { $this->setPushUrl($path, $url); } if ($newRef = $this->updateToCommit($path, $ref, $package->getPrettyVersion(), $package->getReleaseDate())) { if ($package->getDistReference() === $package->getSourceReference()) { $package->setDistReference($newRef); } $package->setSourceReference($newRef); } } /** * {@inheritDoc} */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { GitUtil::cleanEnv(); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $updateOriginUrl = false; if ( 0 === $this->process->execute('git remote -v', $output, $path) && preg_match('{^origin\s+(?P\S+)}m', $output, $originMatch) && preg_match('{^composer\s+(?P\S+)}m', $output, $composerMatch) ) { if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) { $updateOriginUrl = true; } } $ref = $target->getSourceReference(); $this->io->writeError(" Checking out ".$this->getShortHash($ref)); $command = 'git remote set-url composer %s && git rev-parse --quiet --verify %s^{commit} || (git fetch composer && git fetch --tags composer)'; $commandCallable = function ($url) use ($command, $ref) { return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($ref)); }; $this->gitUtil->runCommand($commandCallable, $url, $path); if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) { if ($target->getDistReference() === $target->getSourceReference()) { $target->setDistReference($newRef); } $target->setSourceReference($newRef); } if ($updateOriginUrl) { $this->updateOriginUrl($path, $target->getSourceUrl()); } } /** * {@inheritDoc} */ public function getLocalChanges(PackageInterface $package, $path) { GitUtil::cleanEnv(); if (!$this->hasMetadataRepository($path)) { return; } $command = 'git status --porcelain --untracked-files=no'; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return trim($output) ?: null; } public function getUnpushedChanges(PackageInterface $package, $path) { GitUtil::cleanEnv(); $path = $this->normalizePath($path); if (!$this->hasMetadataRepository($path)) { return; } $command = 'git show-ref --head -d'; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $refs = trim($output); if (!preg_match('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) { // could not match the HEAD for some reason return; } $headRef = $match[1]; if (!preg_match_all('{^'.$headRef.' refs/heads/(.+)$}mi', $refs, $matches)) { // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this return; } // use the first match as branch name for now $branch = $matches[1][0]; $unpushedChanges = null; // do two passes, as if we find anything we want to fetch and then re-try for ($i = 0; $i <= 1; $i++) { // try to find the a matching branch name in the composer remote foreach ($matches[1] as $candidate) { if (preg_match('{^[a-f0-9]+ refs/remotes/((?:composer|origin)/'.preg_quote($candidate).')$}mi', $refs, $match)) { $branch = $candidate; $remoteBranch = $match[1]; break; } } // if it doesn't exist, then we assume it is an unpushed branch // this is bad as we have no reference point to do a diff so we just bail listing // the branch as being unpushed if (!isset($remoteBranch)) { $unpushedChanges = 'Branch ' . $branch . ' could not be found on the origin remote and appears to be unpushed'; } else { $command = sprintf('git diff --name-status %s...%s --', $remoteBranch, $branch); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $unpushedChanges = trim($output) ?: null; } // first pass and we found unpushed changes, fetch from both remotes to make sure we have up to date // remotes and then try again as outdated remotes can sometimes cause false-positives if ($unpushedChanges && $i === 0) { $this->process->execute('git fetch composer && git fetch origin', $output, $path); } // abort after first pass if we didn't find anything if (!$unpushedChanges) { break; } } return $unpushedChanges; } /** * {@inheritDoc} */ protected function cleanChanges(PackageInterface $package, $path, $update) { GitUtil::cleanEnv(); $path = $this->normalizePath($path); $unpushed = $this->getUnpushedChanges($package, $path); if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) { throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed); } if (!$changes = $this->getLocalChanges($package, $path)) { return; } if (!$this->io->isInteractive()) { $discardChanges = $this->config->get('discard-changes'); if (true === $discardChanges) { return $this->discardChanges($path); } if ('stash' === $discardChanges) { if (!$update) { return parent::cleanChanges($package, $path, $update); } return $this->stashChanges($path); } return parent::cleanChanges($package, $path, $update); } $changes = array_map(function ($elem) { return ' '.$elem; }, preg_split('{\s*\r?\n\s*}', $changes)); $this->io->writeError(' The package has modified files:'); $this->io->writeError(array_slice($changes, 0, 10)); if (count($changes) > 10) { $this->io->writeError(' ' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list'); } while (true) { switch ($this->io->ask(' Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]? ', '?')) { case 'y': $this->discardChanges($path); break 2; case 's': if (!$update) { goto help; } $this->stashChanges($path); break 2; case 'n': throw new \RuntimeException('Update aborted'); case 'v': $this->io->writeError($changes); break; case 'd': $this->viewDiff($path); break; case '?': default: help: $this->io->writeError(array( ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', ' v - view modified files', ' d - view local modifications (diff)', )); if ($update) { $this->io->writeError(' s - stash changes and try to reapply them after the update'); } $this->io->writeError(' ? - print help'); break; } } } /** * {@inheritDoc} */ protected function reapplyChanges($path) { $path = $this->normalizePath($path); if ($this->hasStashedChanges) { $this->hasStashedChanges = false; $this->io->writeError(' Re-applying stashed changes'); if (0 !== $this->process->execute('git stash pop', $output, $path)) { throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput()); } } $this->hasDiscardedChanges = false; } /** * Updates the given path to the given commit ref * * @param string $path * @param string $reference * @param string $branch * @param \DateTime $date * @throws \RuntimeException * @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found */ protected function updateToCommit($path, $reference, $branch, $date) { $force = $this->hasDiscardedChanges || $this->hasStashedChanges ? '-f ' : ''; // This uses the "--" sequence to separate branch from file parameters. // // Otherwise git tries the branch name as well as file name. // If the non-existent branch is actually the name of a file, the file // is checked out. $template = 'git checkout '.$force.'%s -- && git reset --hard %1$s --'; $branch = preg_replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $branch); $branches = null; if (0 === $this->process->execute('git branch -r', $output, $path)) { $branches = $output; } // check whether non-commitish are branches or tags, and fetch branches with the remote name $gitRef = $reference; if (!preg_match('{^[a-f0-9]{40}$}', $reference) && $branches && preg_match('{^\s+composer/'.preg_quote($reference).'$}m', $branches) ) { $command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference)); if (0 === $this->process->execute($command, $output, $path)) { return; } } // try to checkout branch by name and then reset it so it's on the proper branch name if (preg_match('{^[a-f0-9]{40}$}', $reference)) { // add 'v' in front of the branch if it was stripped when generating the pretty name if (!preg_match('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && preg_match('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) { $branch = 'v' . $branch; } $command = sprintf('git checkout %s --', ProcessExecutor::escape($branch)); $fallbackCommand = sprintf('git checkout '.$force.'-B %s %s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch)); if (0 === $this->process->execute($command, $output, $path) || 0 === $this->process->execute($fallbackCommand, $output, $path) ) { $command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference)); if (0 === $this->process->execute($command, $output, $path)) { return; } } } $command = sprintf($template, ProcessExecutor::escape($gitRef)); if (0 === $this->process->execute($command, $output, $path)) { return; } // reference was not found (prints "fatal: reference is not a tree: $ref") if (false !== strpos($this->process->getErrorOutput(), $reference)) { $this->io->writeError(' '.$reference.' is gone (history was rewritten?)'); } throw new \RuntimeException(GitUtil::sanitizeUrl('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput())); } protected function updateOriginUrl($path, $url) { $this->process->execute(sprintf('git remote set-url origin %s', ProcessExecutor::escape($url)), $output, $path); $this->setPushUrl($path, $url); } protected function setPushUrl($path, $url) { // set push url for github projects if (preg_match('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) { $protocols = $this->config->get('github-protocols'); $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git'; if (!in_array('ssh', $protocols, true)) { $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git'; } $cmd = sprintf('git remote set-url --push origin %s', ProcessExecutor::escape($pushUrl)); $this->process->execute($cmd, $ignoredOutput, $path); } } /** * {@inheritDoc} */ protected function getCommitLogs($fromReference, $toReference, $path) { $path = $this->normalizePath($path); $command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"', $fromReference, $toReference); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return $output; } /** * @param $path * @throws \RuntimeException */ protected function discardChanges($path) { $path = $this->normalizePath($path); if (0 !== $this->process->execute('git reset --hard', $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); } $this->hasDiscardedChanges = true; } /** * @param $path * @throws \RuntimeException */ protected function stashChanges($path) { $path = $this->normalizePath($path); if (0 !== $this->process->execute('git stash --include-untracked', $output, $path)) { throw new \RuntimeException("Could not stash changes\n\n:".$this->process->getErrorOutput()); } $this->hasStashedChanges = true; } /** * @param $path * @throws \RuntimeException */ protected function viewDiff($path) { $path = $this->normalizePath($path); if (0 !== $this->process->execute('git diff HEAD', $output, $path)) { throw new \RuntimeException("Could not view diff\n\n:".$this->process->getErrorOutput()); } $this->io->writeError($output); } protected function normalizePath($path) { if (Platform::isWindows() && strlen($path) > 0) { $basePath = $path; $removed = array(); while (!is_dir($basePath) && $basePath !== '\\') { array_unshift($removed, basename($basePath)); $basePath = dirname($basePath); } if ($basePath === '\\') { return $path; } $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/'); } return $path; } /** * {@inheritDoc} */ protected function hasMetadataRepository($path) { $path = $this->normalizePath($path); return is_dir($path.'/.git'); } protected function getShortHash($reference) { if (!$this->io->isVerbose() && preg_match('{^[0-9a-f]{40}$}', $reference)) { return substr($reference, 0, 10); } return $reference; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; /** * @author BohwaZ */ class FossilDownloader extends VcsDownloader { /** * {@inheritDoc} */ public function doDownload(PackageInterface $package, $path, $url) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($package->getSourceReference()); $repoFile = $path . '.fossil'; $this->io->writeError("Cloning ".$package->getSourceReference()); $command = sprintf('fossil clone %s %s', $url, ProcessExecutor::escape($repoFile)); if (0 !== $this->process->execute($command, $ignoredOutput)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $command = sprintf('fossil open %s', ProcessExecutor::escape($repoFile)); if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $command = sprintf('fossil update %s', $ref); if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } } /** * {@inheritDoc} */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($target->getSourceReference()); $this->io->writeError(" Updating to ".$target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $command = sprintf('fossil pull && fossil up %s', $ref); if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } } /** * {@inheritDoc} */ public function getLocalChanges(PackageInterface $package, $path) { if (!$this->hasMetadataRepository($path)) { return null; } $this->process->execute('fossil changes', $output, realpath($path)); return trim($output) ?: null; } /** * {@inheritDoc} */ protected function getCommitLogs($fromReference, $toReference, $path) { $command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', $toReference); if (0 !== $this->process->execute($command, $output, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $log = ''; $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/'; foreach ($this->process->splitLines($output) as $line) { if (preg_match($match, $line)) { break; } $log .= $line; } return $log; } /** * {@inheritDoc} */ protected function hasMetadataRepository($path) { return is_file($path . '/.fslckout') || is_file($path . '/_FOSSIL_'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; /** * @author Per Bernhardt */ class HgDownloader extends VcsDownloader { /** * {@inheritDoc} */ public function doDownload(PackageInterface $package, $path, $url) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($package->getSourceReference()); $this->io->writeError("Cloning ".$package->getSourceReference()); $command = sprintf('hg clone %s %s', $url, ProcessExecutor::escape($path)); if (0 !== $this->process->execute($command, $ignoredOutput)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $command = sprintf('hg up %s', $ref); if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } } /** * {@inheritDoc} */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($target->getSourceReference()); $this->io->writeError(" Updating to ".$target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $command = sprintf('hg pull %s && hg up %s', $url, $ref); if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } } /** * {@inheritDoc} */ public function getLocalChanges(PackageInterface $package, $path) { if (!is_dir($path.'/.hg')) { return null; } $this->process->execute('hg st', $output, realpath($path)); return trim($output) ?: null; } /** * {@inheritDoc} */ protected function getCommitLogs($fromReference, $toReference, $path) { $command = sprintf('hg log -r %s:%s --style compact', $fromReference, $toReference); if (0 !== $this->process->execute($command, $output, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return $output; } /** * {@inheritDoc} */ protected function hasMetadataRepository($path) { return is_dir($path . '/.hg'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; /** * VCS Capable Downloader interface. * * @author Steve Buzonas */ interface VcsCapableDownloaderInterface { /** * Gets the VCS Reference for the package at path * * @param PackageInterface $package package directory * @param string $path package directory * @return string|null reference or null */ public function getVcsReference(PackageInterface $package, $path); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; /** * Downloader for tar files: tar, tar.gz or tar.bz2 * * @author Kirill chEbba Chebunin */ class TarDownloader extends ArchiveDownloader { /** * {@inheritDoc} */ protected function extract($file, $path) { // Can throw an UnexpectedValueException $archive = new \PharData($file); $archive->extractTo($path, null, true); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Symfony\Component\Finder\Finder; use Composer\IO\IOInterface; /** * Base downloader for archives * * @author Kirill chEbba Chebunin * @author Jordi Boggiano * @author François Pluchino */ abstract class ArchiveDownloader extends FileDownloader { /** * {@inheritDoc} */ public function download(PackageInterface $package, $path, $output = true) { $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); $retries = 3; while ($retries--) { $fileName = parent::download($package, $path, $output); $this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE); try { $this->filesystem->ensureDirectoryExists($temporaryDir); try { $this->extract($fileName, $temporaryDir); } catch (\Exception $e) { // remove cache if the file was corrupted parent::clearLastCacheWrite($package); throw $e; } $this->filesystem->unlink($fileName); $contentDir = $this->getFolderContent($temporaryDir); // only one dir in the archive, extract its contents out of it if (1 === count($contentDir) && is_dir(reset($contentDir))) { $contentDir = $this->getFolderContent((string) reset($contentDir)); } // move files back out of the temp dir foreach ($contentDir as $file) { $file = (string) $file; $this->filesystem->rename($file, $path . '/' . basename($file)); } $this->filesystem->removeDirectory($temporaryDir); if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) { $this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/'); } if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) { $this->filesystem->removeDirectory($this->config->get('vendor-dir')); } } catch (\Exception $e) { // clean up $this->filesystem->removeDirectory($path); $this->filesystem->removeDirectory($temporaryDir); // retry downloading if we have an invalid zip file if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) { $this->io->writeError(''); if ($this->io->isDebug()) { $this->io->writeError(' Invalid zip file ('.$e->getMessage().'), retrying...'); } else { $this->io->writeError(' Invalid zip file, retrying...'); } usleep(500000); continue; } throw $e; } break; } } /** * {@inheritdoc} */ protected function getFileName(PackageInterface $package, $path) { return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); } /** * {@inheritdoc} */ protected function processUrl(PackageInterface $package, $url) { if ($package->getDistReference() && strpos($url, 'github.com')) { if (preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) { // update legacy github archives to API calls with the proper reference $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference(); } elseif ($package->getDistReference() && preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) { // update current github web archives to API calls with the proper reference $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference(); } elseif ($package->getDistReference() && preg_match('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) { // update api archives to the proper reference $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference(); } } elseif ($package->getDistReference() && strpos($url, 'bitbucket.org')) { if (preg_match('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) { // update Bitbucket archives to the proper reference $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $package->getDistReference() . '.' . $match[4]; } } return parent::processUrl($package, $url); } /** * Extract file to directory * * @param string $file Extracted file * @param string $path Directory * * @throws \UnexpectedValueException If can not extract downloaded file to path */ abstract protected function extract($file, $path); /** * Returns the folder content, excluding dotfiles * * @param string $dir Directory * @return \SplFileInfo[] */ private function getFolderContent($dir) { $finder = Finder::create() ->ignoreVCS(false) ->ignoreDotFiles(false) ->depth(0) ->in($dir); return iterator_to_array($finder); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; /** * GZip archive downloader. * * @author Pavel Puchkin */ class GzipDownloader extends ArchiveDownloader { protected $process; public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) { $this->process = $process ?: new ProcessExecutor($io); parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); } protected function extract($file, $path) { $targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3)); // Try to use gunzip on *nix if (!Platform::isWindows()) { $command = 'gzip -cd ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath); if (0 === $this->process->execute($command, $ignoredOutput)) { return; } if (extension_loaded('zlib')) { // Fallback to using the PHP extension. $this->extractUsingExt($file, $targetFilepath); return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } // Windows version of PHP has built-in support of gzip functions $this->extractUsingExt($file, $targetFilepath); } /** * {@inheritdoc} */ protected function getFileName(PackageInterface $package, $path) { return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); } private function extractUsingExt($file, $targetFilepath) { $archiveFile = gzopen($file, 'rb'); $targetFile = fopen($targetFilepath, 'wb'); while ($string = gzread($archiveFile, 4096)) { fwrite($targetFile, $string, Platform::strlen($string)); } gzclose($archiveFile); fclose($targetFile); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; use Symfony\Component\Process\ExecutableFinder; use ZipArchive; /** * @author Jordi Boggiano */ class ZipDownloader extends ArchiveDownloader { protected static $hasSystemUnzip; private static $hasZipArchive; private static $isWindows; protected $process; private $zipArchiveObject; public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) { $this->process = $process ?: new ProcessExecutor($io); parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); } /** * {@inheritDoc} */ public function download(PackageInterface $package, $path, $output = true) { if (null === self::$hasSystemUnzip) { $finder = new ExecutableFinder; self::$hasSystemUnzip = (bool) $finder->find('unzip'); } if (null === self::$hasZipArchive) { self::$hasZipArchive = class_exists('ZipArchive'); } if (null === self::$isWindows) { self::$isWindows = Platform::isWindows(); } if (!self::$hasZipArchive && !self::$hasSystemUnzip) { // php.ini path is added to the error message to help users find the correct file $iniMessage = IniHelper::getMessage(); $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage; throw new \RuntimeException($error); } return parent::download($package, $path, $output); } /** * extract $file to $path with "unzip" command * * @param string $file File to extract * @param string $path Path where to extract file * @param bool $isLastChance If true it is called as a fallback and should throw an exception * @return bool Success status */ protected function extractWithSystemUnzip($file, $path, $isLastChance) { if (!self::$hasZipArchive) { // Force Exception throwing if the Other alternative is not available $isLastChance = true; } if (!self::$hasSystemUnzip && !$isLastChance) { // This was call as the favorite extract way, but is not available // We switch to the alternative return $this->extractWithZipArchive($file, $path, true); } $processError = null; // When called after a ZipArchive failed, perhaps there is some files to overwrite $overwrite = $isLastChance ? '-o' : ''; $command = 'unzip -qq '.$overwrite.' '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path); try { if (0 === $this->process->execute($command, $ignoredOutput)) { return true; } $processError = new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } catch (\Exception $e) { $processError = $e; } if ($isLastChance) { throw $processError; } $this->io->writeError(' '.$processError->getMessage()); $this->io->writeError(' The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)'); $this->io->writeError(' Unzip with unzip command failed, falling back to ZipArchive class'); return $this->extractWithZipArchive($file, $path, true); } /** * extract $file to $path with ZipArchive * * @param string $file File to extract * @param string $path Path where to extract file * @param bool $isLastChance If true it is called as a fallback and should throw an exception * @return bool Success status */ protected function extractWithZipArchive($file, $path, $isLastChance) { if (!self::$hasSystemUnzip) { // Force Exception throwing if the Other alternative is not available $isLastChance = true; } if (!self::$hasZipArchive && !$isLastChance) { // This was call as the favorite extract way, but is not available // We switch to the alternative return $this->extractWithSystemUnzip($file, $path, true); } $processError = null; $zipArchive = $this->zipArchiveObject ?: new ZipArchive(); try { if (true === ($retval = $zipArchive->open($file))) { $extractResult = $zipArchive->extractTo($path); if (true === $extractResult) { $zipArchive->close(); return true; } $processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file, it is either corrupted or using an invalid format.\n")); } else { $processError = new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n"), $retval); } } catch (\ErrorException $e) { $processError = new \RuntimeException('The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0, $e); } catch (\Exception $e) { $processError = $e; } if ($isLastChance) { throw $processError; } $this->io->writeError(' '.$processError->getMessage()); $this->io->writeError(' Unzip with ZipArchive class failed, falling back to unzip command'); return $this->extractWithSystemUnzip($file, $path, true); } /** * extract $file to $path * * @param string $file File to extract * @param string $path Path where to extract file */ public function extract($file, $path) { // Each extract calls its alternative if not available or fails if (self::$isWindows) { $this->extractWithZipArchive($file, $path, false); } else { $this->extractWithSystemUnzip($file, $path, false); } } /** * Give a meaningful error message to the user. * * @param int $retval * @param string $file * @return string */ protected function getErrorMessage($retval, $file) { switch ($retval) { case ZipArchive::ER_EXISTS: return sprintf("File '%s' already exists.", $file); case ZipArchive::ER_INCONS: return sprintf("Zip archive '%s' is inconsistent.", $file); case ZipArchive::ER_INVAL: return sprintf("Invalid argument (%s)", $file); case ZipArchive::ER_MEMORY: return sprintf("Malloc failure (%s)", $file); case ZipArchive::ER_NOENT: return sprintf("No such zip file: '%s'", $file); case ZipArchive::ER_NOZIP: return sprintf("'%s' is not a zip archive.", $file); case ZipArchive::ER_OPEN: return sprintf("Can't open zip file: %s", $file); case ZipArchive::ER_READ: return sprintf("Zip read error (%s)", $file); case ZipArchive::ER_SEEK: return sprintf("Zip seek error (%s)", $file); default: return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; /** * @author Jordi Boggiano */ class TransportException extends \RuntimeException { protected $headers; protected $response; protected $statusCode; public function setHeaders($headers) { $this->headers = $headers; } public function getHeaders() { return $this->headers; } public function setResponse($response) { $this->response = $response; } public function getResponse() { return $this->response; } public function setStatusCode($statusCode) { $this->statusCode = $statusCode; } public function getStatusCode() { return $this->statusCode; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use Composer\Util\Filesystem; /** * @author Jordi Boggiano */ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface { /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var Filesystem */ protected $filesystem; public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); $this->filesystem = $fs ?: new Filesystem($this->process); } /** * {@inheritDoc} */ public function getInstallationSource() { return 'source'; } /** * {@inheritDoc} */ public function download(PackageInterface $package, $path) { if (!$package->getSourceReference()) { throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); } $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "): ", false); $this->filesystem->emptyDirectory($path); $urls = $package->getSourceUrls(); while ($url = array_shift($urls)) { try { if (Filesystem::isLocalPath($url)) { // realpath() below will not understand // url that starts with "file://" $needle = 'file://'; $isFileProtocol = false; if (0 === strpos($url, $needle)) { $url = substr($url, strlen($needle)); $isFileProtocol = true; } // realpath() below will not understand %20 spaces etc. if (false !== strpos($url, '%')) { $url = rawurldecode($url); } $url = realpath($url); if ($isFileProtocol) { $url = $needle . $url; } } $this->doDownload($package, $path, $url); break; } catch (\Exception $e) { // rethrow phpunit exceptions to avoid hard to debug bug failures if ($e instanceof \PHPUnit_Framework_Exception) { throw $e; } if ($this->io->isDebug()) { $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); } elseif (count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } if (!count($urls)) { throw $e; } } } } /** * {@inheritDoc} */ public function update(PackageInterface $initial, PackageInterface $target, $path) { if (!$target->getSourceReference()) { throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information'); } $name = $target->getName(); if ($initial->getPrettyVersion() == $target->getPrettyVersion()) { if ($target->getSourceType() === 'svn') { $from = $initial->getSourceReference(); $to = $target->getSourceReference(); } else { $from = substr($initial->getSourceReference(), 0, 7); $to = substr($target->getSourceReference(), 0, 7); } $name .= ' '.$initial->getPrettyVersion(); } else { $from = $initial->getFullPrettyVersion(); $to = $target->getFullPrettyVersion(); } $this->io->writeError(" - Updating " . $name . " (" . $from . " => " . $to . "): ", false); $this->cleanChanges($initial, $path, true); $urls = $target->getSourceUrls(); $exception = null; while ($url = array_shift($urls)) { try { if (Filesystem::isLocalPath($url)) { $url = realpath($url); } $this->doUpdate($initial, $target, $path, $url); $exception = null; break; } catch (\Exception $exception) { // rethrow phpunit exceptions to avoid hard to debug bug failures if ($exception instanceof \PHPUnit_Framework_Exception) { throw $exception; } if ($this->io->isDebug()) { $this->io->writeError('Failed: ['.get_class($exception).'] '.$exception->getMessage()); } elseif (count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } } } $this->reapplyChanges($path); // print the commit logs if in verbose mode and VCS metadata is present // because in case of missing metadata code would trigger another exception if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) { $message = 'Pulling in changes:'; $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path); if (!trim($logs)) { $message = 'Rolling back changes:'; $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path); } if (trim($logs)) { $logs = implode("\n", array_map(function ($line) { return ' ' . $line; }, explode("\n", $logs))); // escape angle brackets for proper output in the console $logs = str_replace('<', '\<', $logs); $this->io->writeError(' '.$message); $this->io->writeError($logs); } } if (!$urls && $exception) { throw $exception; } } /** * {@inheritDoc} */ public function remove(PackageInterface $package, $path) { $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); $this->cleanChanges($package, $path, false); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } } /** * Download progress information is not available for all VCS downloaders. * {@inheritDoc} */ public function setOutputProgress($outputProgress) { return $this; } /** * {@inheritDoc} */ public function getVcsReference(PackageInterface $package, $path) { $parser = new VersionParser; $guesser = new VersionGuesser($this->config, $this->process, $parser); $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { return $packageVersion['commit']; } } /** * Prompt the user to check if changes should be stashed/removed or the operation aborted * * @param PackageInterface $package * @param string $path * @param bool $update if true (update) the changes can be stashed and reapplied after an update, * if false (remove) the changes should be assumed to be lost if the operation is not aborted * @throws \RuntimeException in case the operation must be aborted */ protected function cleanChanges(PackageInterface $package, $path, $update) { // the default implementation just fails if there are any changes, override in child classes to provide stash-ability if (null !== $this->getLocalChanges($package, $path)) { throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.'); } } /** * Guarantee that no changes have been made to the local copy * * @param string $path * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly */ protected function reapplyChanges($path) { } /** * Downloads specific package into specific folder. * * @param PackageInterface $package package instance * @param string $path download path * @param string $url package url */ abstract protected function doDownload(PackageInterface $package, $path, $url); /** * Updates specific package in specific folder from initial to target version. * * @param PackageInterface $initial initial package * @param PackageInterface $target updated package * @param string $path download path * @param string $url package url */ abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url); /** * Fetches the commit logs between two commits * * @param string $fromReference the source reference * @param string $toReference the target reference * @param string $path the package path * @return string */ abstract protected function getCommitLogs($fromReference, $toReference, $path); /** * Checks if VCS metadata repository has been initialized * repository example: .git|.svn|.hg * * @param string $path * @return bool */ abstract protected function hasMetadataRepository($path); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; /** * Downloader for phar files * * @author Kirill chEbba Chebunin */ class PharDownloader extends ArchiveDownloader { /** * {@inheritDoc} */ protected function extract($file, $path) { // Can throw an UnexpectedValueException $archive = new \Phar($file); $archive->extractTo($path, null, true); /* TODO: handle openssl signed phars * https://github.com/composer/composer/pull/33#issuecomment-2250768 * https://github.com/koto/phar-util * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html */ } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\Archiver\ArchivableFilesFinder; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem as ComposerFilesystem; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; /** * Download a package from a local path. * * @author Samuel Roze * @author Johann Reinke */ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface { const STRATEGY_SYMLINK = 10; const STRATEGY_MIRROR = 20; /** * {@inheritdoc} */ public function download(PackageInterface $package, $path, $output = true) { $url = $package->getDistUrl(); $realUrl = realpath($url); if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { throw new \RuntimeException(sprintf( 'Source path "%s" is not found for package %s', $url, $package->getName() )); } if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) { // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours. // // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174 // for previous attempts that were shut down because they did not work well enough or introduced too many risks. throw new \RuntimeException(sprintf( 'Package %s cannot install to "%s" inside its source at "%s"', $package->getName(), realpath($path), $realUrl )); } // Get the transport options with default values $transportOptions = $package->getTransportOptions() + array('symlink' => null); // When symlink transport option is null, both symlink and mirror are allowed $currentStrategy = self::STRATEGY_SYMLINK; $allowedStrategies = array(self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR); $mirrorPathRepos = getenv('COMPOSER_MIRROR_PATH_REPOS'); if ($mirrorPathRepos) { $currentStrategy = self::STRATEGY_MIRROR; } if (true === $transportOptions['symlink']) { $currentStrategy = self::STRATEGY_SYMLINK; $allowedStrategies = array(self::STRATEGY_SYMLINK); } elseif (false === $transportOptions['symlink']) { $currentStrategy = self::STRATEGY_MIRROR; $allowedStrategies = array(self::STRATEGY_MIRROR); } $fileSystem = new Filesystem(); $this->filesystem->removeDirectory($path); if ($output) { $this->io->writeError(sprintf( ' - Installing %s (%s): ', $package->getName(), $package->getFullPrettyVersion() ), false); } $isFallback = false; if (self::STRATEGY_SYMLINK == $currentStrategy) { try { if (Platform::isWindows()) { // Implement symlinks as NTFS junctions on Windows $this->io->writeError(sprintf('Junctioning from %s', $url), false); $this->filesystem->junction($realUrl, $path); } else { $absolutePath = $path; if (!$this->filesystem->isAbsolutePath($absolutePath)) { $absolutePath = getcwd() . DIRECTORY_SEPARATOR . $path; } $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl); $path = rtrim($path, "/"); $this->io->writeError(sprintf('Symlinking from %s', $url), false); $fileSystem->symlink($shortestPath, $path); } } catch (IOException $e) { if (in_array(self::STRATEGY_MIRROR, $allowedStrategies)) { $this->io->writeError(''); $this->io->writeError(' Symlink failed, fallback to use mirroring!'); $currentStrategy = self::STRATEGY_MIRROR; $isFallback = true; } else { throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path)); } } } // Fallback if symlink failed or if symlink is not allowed for the package if (self::STRATEGY_MIRROR == $currentStrategy) { $fs = new ComposerFilesystem(); $realUrl = $fs->normalizePath($realUrl); $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? ' ' : '', $url), false); $iterator = new ArchivableFilesFinder($realUrl, array()); $fileSystem->mirror($realUrl, $path, $iterator); } $this->io->writeError(''); } /** * {@inheritDoc} */ public function remove(PackageInterface $package, $path, $output = true) { /** * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which * is disastrous within a junction. So in that case we have no other real choice but to fail hard. */ if (Platform::isWindows() && $this->filesystem->isJunction($path)) { if ($output) { $this->io->writeError(" - Removing junction for " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } if (!$this->filesystem->removeJunction($path)) { $this->io->writeError(" Could not remove junction at " . $path . " - is another process locking it?"); throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); } } else { parent::remove($package, $path, $output); } } /** * {@inheritDoc} */ public function getVcsReference(PackageInterface $package, $path) { $parser = new VersionParser; $guesser = new VersionGuesser($this->config, new ProcessExecutor($this->io), $parser); $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { return $packageVersion['commit']; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Util\Filesystem; /** * Extractor for pear packages. * * Composer cannot rely on tar files structure when place it inside package target dir. Correct source files * disposition must be read from package.xml * This extract pear package source files to target dir. * * @author Alexey Prilipko */ class PearPackageExtractor { private static $rolesWithoutPackageNamePrefix = array('php', 'script', 'www'); /** @var Filesystem */ private $filesystem; private $file; public function __construct($file) { if (!is_file($file)) { throw new \UnexpectedValueException('PEAR package file is not found at '.$file); } $this->filesystem = new Filesystem(); $this->file = $file; } /** * Installs PEAR source files according to package.xml definitions and removes extracted files * * @param string $target target install location. all source installation would be performed relative to target path. * @param array $roles types of files to install. default role for PEAR source files are 'php'. * @param array $vars used for replacement tasks * @throws \RuntimeException * @throws \UnexpectedValueException */ public function extractTo($target, array $roles = array('php' => '/', 'script' => '/bin'), $vars = array()) { $extractionPath = $target.'/tarball'; try { $archive = new \PharData($this->file); $archive->extractTo($extractionPath, null, true); if (!is_file($this->combine($extractionPath, '/package.xml'))) { throw new \RuntimeException('Invalid PEAR package. It must contain package.xml file.'); } $fileCopyActions = $this->buildCopyActions($extractionPath, $roles, $vars); $this->copyFiles($fileCopyActions, $extractionPath, $target, $roles, $vars); $this->filesystem->removeDirectory($extractionPath); } catch (\Exception $exception) { throw new \UnexpectedValueException(sprintf('Failed to extract PEAR package %s to %s. Reason: %s', $this->file, $target, $exception->getMessage()), 0, $exception); } } /** * Perform copy actions on files * * @param array $files array of copy actions ('from', 'to') with relative paths * @param $source string path to source dir. * @param $target string path to destination dir * @param array $roles array [role => roleRoot] relative root for files having that role * @param array $vars list of values can be used for replacement tasks */ private function copyFiles($files, $source, $target, $roles, $vars) { foreach ($files as $file) { $from = $this->combine($source, $file['from']); $to = $this->combine($target, $roles[$file['role']]); $to = $this->combine($to, $file['to']); $tasks = $file['tasks']; $this->copyFile($from, $to, $tasks, $vars); } } private function copyFile($from, $to, $tasks, $vars) { if (!is_file($from)) { throw new \RuntimeException('Invalid PEAR package. package.xml defines file that is not located inside tarball.'); } $this->filesystem->ensureDirectoryExists(dirname($to)); if (0 == count($tasks)) { $copied = copy($from, $to); } else { $content = file_get_contents($from); $replacements = array(); foreach ($tasks as $task) { $pattern = $task['from']; $varName = $task['to']; if (isset($vars[$varName])) { if ($varName === 'php_bin' && false === strpos($to, '.bat')) { $replacements[$pattern] = preg_replace('{\.bat$}', '', $vars[$varName]); } else { $replacements[$pattern] = $vars[$varName]; } } } $content = strtr($content, $replacements); $copied = file_put_contents($to, $content); } if (false === $copied) { throw new \RuntimeException(sprintf('Failed to copy %s to %s', $from, $to)); } } /** * Builds list of copy and list of remove actions that would transform extracted PEAR tarball into installed package. * * @param string $source string path to extracted files * @param array $roles array [role => roleRoot] relative root for files having that role * @param array $vars list of values can be used for replacement tasks * @throws \RuntimeException * @return array array of 'source' => 'target', where source is location of file in the tarball (relative to source * path, and target is destination of file (also relative to $source path) */ private function buildCopyActions($source, array $roles, $vars) { /** @var $package \SimpleXmlElement */ $package = simplexml_load_string(file_get_contents($this->combine($source, 'package.xml'))); if (false === $package) { throw new \RuntimeException('Package definition file is not valid.'); } $packageSchemaVersion = $package['version']; if ('1.0' == $packageSchemaVersion) { $children = $package->release->filelist->children(); $packageName = (string) $package->name; $packageVersion = (string) $package->release->version; $sourceDir = $packageName . '-' . $packageVersion; $result = $this->buildSourceList10($children, $roles, $sourceDir, '', null, $packageName); } elseif ('2.0' == $packageSchemaVersion || '2.1' == $packageSchemaVersion) { $children = $package->contents->children(); $packageName = (string) $package->name; $packageVersion = (string) $package->version->release; $sourceDir = $packageName . '-' . $packageVersion; $result = $this->buildSourceList20($children, $roles, $sourceDir, '', null, $packageName); $namespaces = $package->getNamespaces(); $package->registerXPathNamespace('ns', $namespaces['']); $releaseNodes = $package->xpath('ns:phprelease'); $this->applyRelease($result, $releaseNodes, $vars); } else { throw new \RuntimeException('Unsupported schema version of package definition file.'); } return $result; } private function applyRelease(&$actions, $releaseNodes, $vars) { foreach ($releaseNodes as $releaseNode) { $requiredOs = $releaseNode->installconditions && $releaseNode->installconditions->os && $releaseNode->installconditions->os->name ? (string) $releaseNode->installconditions->os->name : ''; if ($requiredOs && $vars['os'] != $requiredOs) { continue; } if ($releaseNode->filelist) { foreach ($releaseNode->filelist->children() as $action) { if ('install' == $action->getName()) { $name = (string) $action['name']; $as = (string) $action['as']; if (isset($actions[$name])) { $actions[$name]['to'] = $as; } } elseif ('ignore' == $action->getName()) { $name = (string) $action['name']; unset($actions[$name]); } else { // unknown action } } } break; } } private function buildSourceList10($children, $targetRoles, $source, $target, $role, $packageName) { $result = array(); // enumerating files foreach ($children as $child) { /** @var $child \SimpleXMLElement */ if ($child->getName() == 'dir') { $dirSource = $this->combine($source, (string) $child['name']); $dirTarget = $child['baseinstalldir'] ?: $target; $dirRole = $child['role'] ?: $role; $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName); $result = array_merge($result, $dirFiles); } elseif ($child->getName() == 'file') { $fileRole = (string) $child['role'] ?: $role; if (isset($targetRoles[$fileRole])) { $fileName = (string) ($child['name'] ?: $child[0]); // $child[0] means text content $fileSource = $this->combine($source, $fileName); $fileTarget = $this->combine((string) $child['baseinstalldir'] ?: $target, $fileName); if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) { $fileTarget = $packageName . '/' . $fileTarget; } $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => array()); } } } return $result; } private function buildSourceList20($children, $targetRoles, $source, $target, $role, $packageName) { $result = array(); // enumerating files foreach ($children as $child) { /** @var $child \SimpleXMLElement */ if ('dir' == $child->getName()) { $dirSource = $this->combine($source, $child['name']); $dirTarget = $child['baseinstalldir'] ?: $target; $dirRole = $child['role'] ?: $role; $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName); $result = array_merge($result, $dirFiles); } elseif ('file' == $child->getName()) { $fileRole = (string) $child['role'] ?: $role; if (isset($targetRoles[$fileRole])) { $fileSource = $this->combine($source, (string) $child['name']); $fileTarget = $this->combine((string) ($child['baseinstalldir'] ?: $target), (string) $child['name']); $fileTasks = array(); foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) { if ('replace' == $taskNode->getName()) { $fileTasks[] = array('from' => (string) $taskNode->attributes()->from, 'to' => (string) $taskNode->attributes()->to); } } if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) { $fileTarget = $packageName . '/' . $fileTarget; } $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => $fileTasks); } } } return $result; } private function combine($left, $right) { return rtrim($left, '/') . '/' . ltrim($right, '/'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; use RarArchive; /** * RAR archive downloader. * * Based on previous work by Jordi Boggiano ({@see ZipDownloader}). * * @author Derrick Nelson */ class RarDownloader extends ArchiveDownloader { protected $process; public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) { $this->process = $process ?: new ProcessExecutor($io); parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); } protected function extract($file, $path) { $processError = null; // Try to use unrar on *nix if (!Platform::isWindows()) { $command = 'unrar x ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' >/dev/null && chmod -R u+w ' . ProcessExecutor::escape($path); if (0 === $this->process->execute($command, $ignoredOutput)) { return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); } if (!class_exists('RarArchive')) { // php.ini path is added to the error message to help users find the correct file $iniMessage = IniHelper::getMessage(); $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" . $iniMessage . "\n" . $processError; if (!Platform::isWindows()) { $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage; } throw new \RuntimeException($error); } $rarArchive = RarArchive::open($file); if (false === $rarArchive) { throw new \UnexpectedValueException('Could not open RAR archive: ' . $file); } $entries = $rarArchive->getEntries(); if (false === $entries) { throw new \RuntimeException('Could not retrieve RAR archive entries'); } foreach ($entries as $entry) { if (false === $entry->extract($path)) { throw new \RuntimeException('Could not extract entry'); } } $rarArchive->close(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Repository\VcsRepository; use Composer\Util\Perforce; /** * @author Matt Whittom */ class PerforceDownloader extends VcsDownloader { /** @var Perforce */ protected $perforce; /** * {@inheritDoc} */ public function doDownload(PackageInterface $package, $path, $url) { $ref = $package->getSourceReference(); $label = $this->getLabelFromSourceReference($ref); $this->io->writeError('Cloning ' . $ref); $this->initPerforce($package, $path, $url); $this->perforce->setStream($ref); $this->perforce->p4Login(); $this->perforce->writeP4ClientSpec(); $this->perforce->connectClient(); $this->perforce->syncCodeBase($label); $this->perforce->cleanupClientSpec(); } private function getLabelFromSourceReference($ref) { $pos = strpos($ref, '@'); if (false !== $pos) { return substr($ref, $pos + 1); } return null; } public function initPerforce(PackageInterface $package, $path, $url) { if (!empty($this->perforce)) { $this->perforce->initializePath($path); return; } $repository = $package->getRepository(); $repoConfig = null; if ($repository instanceof VcsRepository) { $repoConfig = $this->getRepoConfig($repository); } $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io); } private function getRepoConfig(VcsRepository $repository) { return $repository->getRepoConfig(); } /** * {@inheritDoc} */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { $this->doDownload($target, $path, $url); } /** * {@inheritDoc} */ public function getLocalChanges(PackageInterface $package, $path) { $this->io->writeError('Perforce driver does not check for local changes before overriding', true); return; } /** * {@inheritDoc} */ protected function getCommitLogs($fromReference, $toReference, $path) { $commitLogs = $this->perforce->getCommitLogs($fromReference, $toReference); return $commitLogs; } public function setPerforce($perforce) { $this->perforce = $perforce; } /** * {@inheritDoc} */ protected function hasMetadataRepository($path) { return true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\Svn as SvnUtil; use Composer\Repository\VcsRepository; /** * @author Ben Bieker * @author Till Klampaeckel */ class SvnDownloader extends VcsDownloader { protected $cacheCredentials = true; /** * {@inheritDoc} */ public function doDownload(PackageInterface $package, $path, $url) { SvnUtil::cleanEnv(); $ref = $package->getSourceReference(); $repo = $package->getRepository(); if ($repo instanceof VcsRepository) { $repoConfig = $repo->getRepoConfig(); if (array_key_exists('svn-cache-credentials', $repoConfig)) { $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials']; } } $this->io->writeError(" Checking out ".$package->getSourceReference()); $this->execute($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path); } /** * {@inheritDoc} */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { SvnUtil::cleanEnv(); $ref = $target->getSourceReference(); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $flags = ""; if (0 === $this->process->execute('svn --version', $output)) { if (preg_match('{(\d+(?:\.\d+)+)}', $output, $match) && version_compare($match[1], '1.7.0', '>=')) { $flags .= ' --ignore-ancestry'; } } $this->io->writeError(" Checking out " . $ref); $this->execute($url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path); } /** * {@inheritDoc} */ public function getLocalChanges(PackageInterface $package, $path) { if (!$this->hasMetadataRepository($path)) { return null; } $this->process->execute('svn status --ignore-externals', $output, $path); return preg_match('{^ *[^X ] +}m', $output) ? $output : null; } /** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $baseUrl Base URL of the repository * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @throws \RuntimeException * @return string */ protected function execute($baseUrl, $command, $url, $cwd = null, $path = null) { $util = new SvnUtil($baseUrl, $this->io, $this->config); $util->setCacheCredentials($this->cacheCredentials); try { return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose()); } catch (\RuntimeException $e) { throw new \RuntimeException( 'Package could not be downloaded, '.$e->getMessage() ); } } /** * {@inheritDoc} */ protected function cleanChanges(PackageInterface $package, $path, $update) { if (!$changes = $this->getLocalChanges($package, $path)) { return; } if (!$this->io->isInteractive()) { if (true === $this->config->get('discard-changes')) { return $this->discardChanges($path); } return parent::cleanChanges($package, $path, $update); } $changes = array_map(function ($elem) { return ' '.$elem; }, preg_split('{\s*\r?\n\s*}', $changes)); $this->io->writeError(' The package has modified files:'); $this->io->writeError(array_slice($changes, 0, 10)); if (count($changes) > 10) { $this->io->writeError(' '.count($changes) - 10 . ' more files modified, choose "v" to view the full list'); } while (true) { switch ($this->io->ask(' Discard changes [y,n,v,?]? ', '?')) { case 'y': $this->discardChanges($path); break 2; case 'n': throw new \RuntimeException('Update aborted'); case 'v': $this->io->writeError($changes); break; case '?': default: $this->io->writeError(array( ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', ' v - view modified files', ' ? - print help', )); break; } } } /** * {@inheritDoc} */ protected function getCommitLogs($fromReference, $toReference, $path) { if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference)) { // strip paths from references and only keep the actual revision $fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference); $toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference); $command = sprintf('svn log -r%s:%s --incremental', $fromRevision, $toRevision); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException( 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() ); } } else { $output = "Could not retrieve changes between $fromReference and $toReference due to missing revision information"; } return $output; } protected function discardChanges($path) { if (0 !== $this->process->execute('svn revert -R .', $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); } } /** * {@inheritDoc} */ protected function hasMetadataRepository($path) { return is_dir($path.'/.svn'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; /** * Exception thrown when issues exist on local filesystem * * @author Javier Spagnoletti */ class FilesystemException extends \Exception { public function __construct($message = '', $code = 0, \Exception $previous = null) { parent::__construct("Filesystem exception: \n".$message, $code, $previous); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\IO\IOInterface; use Composer\Util\Filesystem; /** * Downloaders manager. * * @author Konstantin Kudryashov */ class DownloadManager { private $io; private $preferDist = false; private $preferSource = false; private $packagePreferences = array(); private $filesystem; private $downloaders = array(); /** * Initializes download manager. * * @param IOInterface $io The Input Output Interface * @param bool $preferSource prefer downloading from source * @param Filesystem|null $filesystem custom Filesystem object */ public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null) { $this->io = $io; $this->preferSource = $preferSource; $this->filesystem = $filesystem ?: new Filesystem(); } /** * Makes downloader prefer source installation over the dist. * * @param bool $preferSource prefer downloading from source * @return DownloadManager */ public function setPreferSource($preferSource) { $this->preferSource = $preferSource; return $this; } /** * Makes downloader prefer dist installation over the source. * * @param bool $preferDist prefer downloading from dist * @return DownloadManager */ public function setPreferDist($preferDist) { $this->preferDist = $preferDist; return $this; } /** * Sets fine tuned preference settings for package level source/dist selection. * * @param array $preferences array of preferences by package patterns * @return DownloadManager */ public function setPreferences(array $preferences) { $this->packagePreferences = $preferences; return $this; } /** * Sets whether to output download progress information for all registered * downloaders * * @param bool $outputProgress * @return DownloadManager */ public function setOutputProgress($outputProgress) { foreach ($this->downloaders as $downloader) { $downloader->setOutputProgress($outputProgress); } return $this; } /** * Sets installer downloader for a specific installation type. * * @param string $type installation type * @param DownloaderInterface $downloader downloader instance * @return DownloadManager */ public function setDownloader($type, DownloaderInterface $downloader) { $type = strtolower($type); $this->downloaders[$type] = $downloader; return $this; } /** * Returns downloader for a specific installation type. * * @param string $type installation type * @throws \InvalidArgumentException if downloader for provided type is not registered * @return DownloaderInterface */ public function getDownloader($type) { $type = strtolower($type); if (!isset($this->downloaders[$type])) { throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders)))); } return $this->downloaders[$type]; } /** * Returns downloader for already installed package. * * @param PackageInterface $package package instance * @throws \InvalidArgumentException if package has no installation source specified * @throws \LogicException if specific downloader used to load package with * wrong type * @return DownloaderInterface|null */ public function getDownloaderForInstalledPackage(PackageInterface $package) { $installationSource = $package->getInstallationSource(); if ('metapackage' === $package->getType()) { return; } if ('dist' === $installationSource) { $downloader = $this->getDownloader($package->getDistType()); } elseif ('source' === $installationSource) { $downloader = $this->getDownloader($package->getSourceType()); } else { throw new \InvalidArgumentException( 'Package '.$package.' seems not been installed properly' ); } if ($installationSource !== $downloader->getInstallationSource()) { throw new \LogicException(sprintf( 'Downloader "%s" is a %s type downloader and can not be used to download %s', get_class($downloader), $downloader->getInstallationSource(), $installationSource )); } return $downloader; } /** * Downloads package into target dir. * * @param PackageInterface $package package instance * @param string $targetDir target dir * @param bool $preferSource prefer installation from source * * @throws \InvalidArgumentException if package have no urls to download from * @throws \RuntimeException */ public function download(PackageInterface $package, $targetDir, $preferSource = null) { $preferSource = null !== $preferSource ? $preferSource : $this->preferSource; $sourceType = $package->getSourceType(); $distType = $package->getDistType(); $sources = array(); if ($sourceType) { $sources[] = 'source'; } if ($distType) { $sources[] = 'dist'; } if (empty($sources)) { throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); } if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) { $sources = array_reverse($sources); } $this->filesystem->ensureDirectoryExists($targetDir); foreach ($sources as $i => $source) { if (isset($e)) { $this->io->writeError(' Now trying to download from ' . $source . ''); } $package->setInstallationSource($source); try { $downloader = $this->getDownloaderForInstalledPackage($package); if ($downloader) { $downloader->download($package, $targetDir); } break; } catch (\RuntimeException $e) { if ($i === count($sources) - 1) { throw $e; } $this->io->writeError( ' Failed to download '. $package->getPrettyName(). ' from ' . $source . ': '. $e->getMessage().'' ); } } } /** * Updates package from initial to target version. * * @param PackageInterface $initial initial package version * @param PackageInterface $target target package version * @param string $targetDir target dir * * @throws \InvalidArgumentException if initial package is not installed */ public function update(PackageInterface $initial, PackageInterface $target, $targetDir) { $downloader = $this->getDownloaderForInstalledPackage($initial); if (!$downloader) { return; } $installationSource = $initial->getInstallationSource(); if ('dist' === $installationSource) { $initialType = $initial->getDistType(); $targetType = $target->getDistType(); } else { $initialType = $initial->getSourceType(); $targetType = $target->getSourceType(); } // upgrading from a dist stable package to a dev package, force source reinstall if ($target->isDev() && 'dist' === $installationSource) { $downloader->remove($initial, $targetDir); $this->download($target, $targetDir); return; } if ($initialType === $targetType) { $target->setInstallationSource($installationSource); try { $downloader->update($initial, $target, $targetDir); return; } catch (\RuntimeException $e) { if (!$this->io->isInteractive()) { throw $e; } $this->io->writeError(' Update failed ('.$e->getMessage().')'); if (!$this->io->askConfirmation(' Would you like to try reinstalling the package instead [yes]? ', true)) { throw $e; } } } $downloader->remove($initial, $targetDir); $this->download($target, $targetDir, 'source' === $installationSource); } /** * Removes package from target dir. * * @param PackageInterface $package package instance * @param string $targetDir target dir */ public function remove(PackageInterface $package, $targetDir) { $downloader = $this->getDownloaderForInstalledPackage($package); if ($downloader) { $downloader->remove($package, $targetDir); } } /** * Determines the install preference of a package * * @param PackageInterface $package package instance * * @return string */ protected function resolvePackageInstallPreference(PackageInterface $package) { foreach ($this->packagePreferences as $pattern => $preference) { $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i'; if (preg_match($pattern, $package->getName())) { if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) { return 'dist'; } return 'source'; } } return $package->isDev() ? 'source' : 'dist'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; /** * Xz archive downloader. * * @author Pavel Puchkin * @author Pierre Rudloff */ class XzDownloader extends ArchiveDownloader { protected $process; public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) { $this->process = $process ?: new ProcessExecutor($io); parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); } protected function extract($file, $path) { $command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path); if (0 === $this->process->execute($command, $ignoredOutput)) { return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } /** * {@inheritdoc} */ protected function getFileName(PackageInterface $package, $path) { return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; /** * DVCS Downloader interface. * * @author James Titcumb */ interface DvcsDownloaderInterface { /** * Checks for unpushed changes to a current branch * * @param PackageInterface $package package directory * @param string $path package directory * @return string|null changes or null */ public function getUnpushedChanges(PackageInterface $package, $path); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; /** * Downloader interface. * * @author Konstantin Kudryashov * @author Jordi Boggiano */ interface DownloaderInterface { /** * Returns installation source (either source or dist). * * @return string "source" or "dist" */ public function getInstallationSource(); /** * Downloads specific package into specific folder. * * @param PackageInterface $package package instance * @param string $path download path */ public function download(PackageInterface $package, $path); /** * Updates specific package in specific folder from initial to target version. * * @param PackageInterface $initial initial package * @param PackageInterface $target updated package * @param string $path download path */ public function update(PackageInterface $initial, PackageInterface $target, $path); /** * Removes specific package from specific folder. * * @param PackageInterface $package package instance * @param string $path download path */ public function remove(PackageInterface $package, $path); /** * Sets whether to output download progress information or not * * @param bool $outputProgress * @return DownloaderInterface */ public function setOutputProgress($outputProgress); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Config\ConfigSourceInterface; use Composer\Downloader\TransportException; use Composer\IO\IOInterface; use Composer\Util\Platform; /** * @author Jordi Boggiano */ class Config { const RELATIVE_PATHS = 1; public static $defaultConfig = array( 'process-timeout' => 300, 'use-include-path' => false, 'preferred-install' => 'auto', 'notify-on-install' => true, 'github-protocols' => array('https', 'ssh', 'git'), 'vendor-dir' => 'vendor', 'bin-dir' => '{$vendor-dir}/bin', 'cache-dir' => '{$home}/cache', 'data-dir' => '{$home}', 'cache-files-dir' => '{$cache-dir}/files', 'cache-repo-dir' => '{$cache-dir}/repo', 'cache-vcs-dir' => '{$cache-dir}/vcs', 'cache-ttl' => 15552000, // 6 months 'cache-files-ttl' => null, // fallback to cache-ttl 'cache-files-maxsize' => '300MiB', 'bin-compat' => 'auto', 'discard-changes' => false, 'autoloader-suffix' => null, 'sort-packages' => false, 'optimize-autoloader' => false, 'classmap-authoritative' => false, 'apcu-autoloader' => false, 'prepend-autoloader' => true, 'github-domains' => array('github.com'), 'bitbucket-expose-hostname' => true, 'disable-tls' => false, 'secure-http' => true, 'cafile' => null, 'capath' => null, 'github-expose-hostname' => true, 'gitlab-domains' => array('gitlab.com'), 'store-auths' => 'prompt', 'platform' => array(), 'archive-format' => 'tar', 'archive-dir' => '.', 'htaccess-protect' => true, // valid keys without defaults (auth config stuff): // bitbucket-oauth // github-oauth // gitlab-oauth // gitlab-token // http-basic ); public static $defaultRepositories = array( 'packagist.org' => array( 'type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true, ), ); private $config; private $baseDir; private $repositories; /** @var ConfigSourceInterface */ private $configSource; /** @var ConfigSourceInterface */ private $authConfigSource; private $useEnvironment; private $warnedHosts = array(); /** * @param bool $useEnvironment Use COMPOSER_ environment variables to replace config settings * @param string $baseDir Optional base directory of the config */ public function __construct($useEnvironment = true, $baseDir = null) { // load defaults $this->config = static::$defaultConfig; $this->repositories = static::$defaultRepositories; $this->useEnvironment = (bool) $useEnvironment; $this->baseDir = $baseDir; } public function setConfigSource(ConfigSourceInterface $source) { $this->configSource = $source; } public function getConfigSource() { return $this->configSource; } public function setAuthConfigSource(ConfigSourceInterface $source) { $this->authConfigSource = $source; } public function getAuthConfigSource() { return $this->authConfigSource; } /** * Merges new config values with the existing ones (overriding) * * @param array $config */ public function merge($config) { // override defaults with given config if (!empty($config['config']) && is_array($config['config'])) { foreach ($config['config'] as $key => $val) { if (in_array($key, array('bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic')) && isset($this->config[$key])) { $this->config[$key] = array_merge($this->config[$key], $val); } elseif ('preferred-install' === $key && isset($this->config[$key])) { if (is_array($val) || is_array($this->config[$key])) { if (is_string($val)) { $val = array('*' => $val); } if (is_string($this->config[$key])) { $this->config[$key] = array('*' => $this->config[$key]); } $this->config[$key] = array_merge($this->config[$key], $val); // the full match pattern needs to be last if (isset($this->config[$key]['*'])) { $wildcard = $this->config[$key]['*']; unset($this->config[$key]['*']); $this->config[$key]['*'] = $wildcard; } } else { $this->config[$key] = $val; } } else { $this->config[$key] = $val; } } } if (!empty($config['repositories']) && is_array($config['repositories'])) { $this->repositories = array_reverse($this->repositories, true); $newRepos = array_reverse($config['repositories'], true); foreach ($newRepos as $name => $repository) { // disable a repository by name if (false === $repository) { $this->disableRepoByName($name); continue; } // disable a repository with an anonymous {"name": false} repo if (is_array($repository) && 1 === count($repository) && false === current($repository)) { $this->disableRepoByName(key($repository)); continue; } // store repo if (is_int($name)) { $this->repositories[] = $repository; } else { if ($name === 'packagist') { // BC support for default "packagist" named repo $this->repositories[$name . '.org'] = $repository; } else { $this->repositories[$name] = $repository; } } } $this->repositories = array_reverse($this->repositories, true); } } /** * @return array */ public function getRepositories() { return $this->repositories; } /** * Returns a setting * * @param string $key * @param int $flags Options (see class constants) * @throws \RuntimeException * @return mixed */ public function get($key, $flags = 0) { switch ($key) { case 'vendor-dir': case 'bin-dir': case 'process-timeout': case 'data-dir': case 'cache-dir': case 'cache-files-dir': case 'cache-repo-dir': case 'cache-vcs-dir': case 'cafile': case 'capath': case 'htaccess-protect': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $val = $this->getComposerEnv($env); $val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\'); $val = Platform::expandPath($val); if (substr($key, -4) !== '-dir') { return $val; } return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val); case 'cache-ttl': return (int) $this->config[$key]; case 'cache-files-maxsize': if (!preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $this->config[$key], $matches)) { throw new \RuntimeException( "Could not parse the value of 'cache-files-maxsize': {$this->config[$key]}" ); } $size = $matches[1]; if (isset($matches[2])) { switch (strtolower($matches[2])) { case 'g': $size *= 1024; // intentional fallthrough case 'm': $size *= 1024; // intentional fallthrough case 'k': $size *= 1024; break; } } return $size; case 'cache-files-ttl': if (isset($this->config[$key])) { return (int) $this->config[$key]; } return (int) $this->config['cache-ttl']; case 'home': $val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $this->config[$key]); return rtrim($this->process($val, $flags), '/\\'); case 'bin-compat': $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key]; if (!in_array($value, array('auto', 'full'))) { throw new \RuntimeException( "Invalid value for 'bin-compat': {$value}. Expected auto, full" ); } return $value; case 'discard-changes': if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) { if (!in_array($env, array('stash', 'true', 'false', '1', '0'), true)) { throw new \RuntimeException( "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash" ); } if ('stash' === $env) { return 'stash'; } // convert string value to bool return $env !== 'false' && (bool) $env; } if (!in_array($this->config[$key], array(true, false, 'stash'), true)) { throw new \RuntimeException( "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash" ); } return $this->config[$key]; case 'github-protocols': $protos = $this->config['github-protocols']; if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) { unset($protos[$index]); } if (reset($protos) === 'http') { throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"'); } return $protos; case 'disable-tls': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; case 'secure-http': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; default: if (!isset($this->config[$key])) { return null; } return $this->process($this->config[$key], $flags); } } public function all($flags = 0) { $all = array( 'repositories' => $this->getRepositories(), ); foreach (array_keys($this->config) as $key) { $all['config'][$key] = $this->get($key, $flags); } return $all; } public function raw() { return array( 'repositories' => $this->getRepositories(), 'config' => $this->config, ); } /** * Checks whether a setting exists * * @param string $key * @return bool */ public function has($key) { return array_key_exists($key, $this->config); } /** * Replaces {$refs} inside a config string * * @param string|int|null $value a config string that can contain {$refs-to-other-config} * @param int $flags Options (see class constants) * @return string|int|null */ private function process($value, $flags) { $config = $this; if (!is_string($value)) { return $value; } return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config, $flags) { return $config->get($match[1], $flags); }, $value); } /** * Turns relative paths in absolute paths without realpath() * * Since the dirs might not exist yet we can not call realpath or it will fail. * * @param string $path * @return string */ private function realpath($path) { if (preg_match('{^(?:/|[a-z]:|[a-z0-9.]+://)}i', $path)) { return $path; } return $this->baseDir . '/' . $path; } /** * Reads the value of a Composer environment variable * * This should be used to read COMPOSER_ environment variables * that overload config values. * * @param string $var * @return string|bool */ private function getComposerEnv($var) { if ($this->useEnvironment) { return getenv($var); } return false; } private function disableRepoByName($name) { if (isset($this->repositories[$name])) { unset($this->repositories[$name]); } elseif ($name === 'packagist') { // BC support for default "packagist" named repo unset($this->repositories['packagist.org']); } } /** * Validates that the passed URL is allowed to be used by current config, or throws an exception. * * @param string $url * @param IOInterface $io */ public function prohibitUrlByConfig($url, IOInterface $io = null) { // Return right away if the URL is malformed or custom (see issue #5173) if (false === filter_var($url, FILTER_VALIDATE_URL)) { return; } // Extract scheme and throw exception on known insecure protocols $scheme = parse_url($url, PHP_URL_SCHEME); if (in_array($scheme, array('http', 'git', 'ftp', 'svn'))) { if ($this->get('secure-http')) { throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-http for details."); } elseif ($io) { $host = parse_url($url, PHP_URL_HOST); if (!isset($this->warnedHosts[$host])) { $io->writeError("Warning: Accessing $host over $scheme which is an insecure protocol."); } $this->warnedHosts[$host] = true; } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin\Capability; /** * Commands Provider Interface * * This capability will receive an array with 'composer' and 'io' keys as * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface * instances. It also contains a 'plugin' key containing the plugin instance that * created the capability. * * @author Jérémy Derussé */ interface CommandProvider extends Capability { /** * Retreives an array of commands * * @return \Composer\Command\BaseCommand[] */ public function getCommands(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin\Capability; /** * Marker interface for Plugin capabilities. * Every new Capability which is added to the Plugin API must implement this interface. * * @api */ interface Capability { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\EventDispatcher\Event; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * An event for all commands. * * @author Nils Adermann */ class CommandEvent extends Event { /** * @var string */ private $commandName; /** * @var InputInterface */ private $input; /** * @var OutputInterface */ private $output; /** * Constructor. * * @param string $name The event name * @param string $commandName The command name * @param InputInterface $input * @param OutputInterface $output * @param array $args Arguments passed by the user * @param array $flags Optional flags to pass data not as argument */ public function __construct($name, $commandName, $input, $output, array $args = array(), array $flags = array()) { parent::__construct($name, $args, $flags); $this->commandName = $commandName; $this->input = $input; $this->output = $output; } /** * Returns the command input interface * * @return InputInterface */ public function getInput() { return $this->input; } /** * Retrieves the command output interface * * @return OutputInterface */ public function getOutput() { return $this->output; } /** * Retrieves the name of the command being run * * @return string */ public function getCommandName() { return $this->commandName; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\EventDispatcher\Event; use Composer\Util\RemoteFilesystem; /** * The pre file download event. * * @author Nils Adermann */ class PreFileDownloadEvent extends Event { /** * @var RemoteFilesystem */ private $rfs; /** * @var string */ private $processedUrl; /** * Constructor. * * @param string $name The event name * @param RemoteFilesystem $rfs * @param string $processedUrl */ public function __construct($name, RemoteFilesystem $rfs, $processedUrl) { parent::__construct($name); $this->rfs = $rfs; $this->processedUrl = $processedUrl; } /** * Returns the remote filesystem * * @return RemoteFilesystem */ public function getRemoteFilesystem() { return $this->rfs; } /** * Sets the remote filesystem * * @param RemoteFilesystem $rfs */ public function setRemoteFilesystem(RemoteFilesystem $rfs) { $this->rfs = $rfs; } /** * Retrieves the processed URL this remote filesystem will be used for * * @return string */ public function getProcessedUrl() { return $this->processedUrl; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; /** * The Plugin Events. * * @author Nils Adermann */ class PluginEvents { /** * The INIT event occurs after a Composer instance is done being initialized * * The event listener method receives a * Composer\EventDispatcher\Event instance. * * @var string */ const INIT = 'init'; /** * The COMMAND event occurs as a command begins * * The event listener method receives a * Composer\Plugin\CommandEvent instance. * * @var string */ const COMMAND = 'command'; /** * The PRE_FILE_DOWNLOAD event occurs before downloading a file * * The event listener method receives a * Composer\Plugin\PreFileDownloadEvent instance. * * @var string */ const PRE_FILE_DOWNLOAD = 'pre-file-download'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\Composer; use Composer\IO\IOInterface; /** * Plugin interface * * @author Nils Adermann */ interface PluginInterface { /** * Version number of the internal composer-plugin-api package * * @var string */ const PLUGIN_API_VERSION = '1.1.0'; /** * Apply plugin modifications to Composer * * @param Composer $composer * @param IOInterface $io */ public function activate(Composer $composer, IOInterface $io); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; use Composer\Package\Package; use Composer\Package\Version\VersionParser; use Composer\Repository\RepositoryInterface; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\Link; use Composer\Semver\Constraint\Constraint; use Composer\DependencyResolver\Pool; use Composer\Plugin\Capability\Capability; /** * Plugin manager * * @author Nils Adermann * @author Jordi Boggiano */ class PluginManager { protected $composer; protected $io; protected $globalComposer; protected $versionParser; protected $disablePlugins = false; protected $plugins = array(); protected $registeredPlugins = array(); private static $classCounter = 0; /** * Initializes plugin manager * * @param IOInterface $io * @param Composer $composer * @param Composer $globalComposer * @param bool $disablePlugins */ public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false) { $this->io = $io; $this->composer = $composer; $this->globalComposer = $globalComposer; $this->versionParser = new VersionParser(); $this->disablePlugins = $disablePlugins; } /** * Loads all plugins from currently installed plugin packages */ public function loadInstalledPlugins() { if ($this->disablePlugins) { return; } $repo = $this->composer->getRepositoryManager()->getLocalRepository(); $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; if ($repo) { $this->loadRepository($repo); } if ($globalRepo) { $this->loadRepository($globalRepo); } } /** * Gets all currently active plugin instances * * @return array plugins */ public function getPlugins() { return $this->plugins; } /** * Gets global composer or null when main composer is not fully loaded * * @return Composer|null */ public function getGlobalComposer() { return $this->globalComposer; } /** * Register a plugin package, activate it etc. * * If it's of type composer-installer it is registered as an installer * instead for BC * * @param PackageInterface $package * @param bool $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception * * @throws \UnexpectedValueException */ public function registerPackage(PackageInterface $package, $failOnMissingClasses = false) { if ($this->disablePlugins) { return; } if ($package->getType() === 'composer-plugin') { $requiresComposer = null; foreach ($package->getRequires() as $link) { /** @var Link $link */ if ('composer-plugin-api' === $link->getTarget()) { $requiresComposer = $link->getConstraint(); break; } } if (!$requiresComposer) { throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); } $currentPluginApiVersion = $this->getPluginApiVersion(); $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); if ($requiresComposer->getPrettyString() === '1.0.0' && $this->getPluginApiVersion() === '1.0.0') { $this->io->writeError('The "' . $package->getName() . '" plugin requires composer-plugin-api 1.0.0, this *WILL* break in the future and it should be fixed ASAP (require ^1.0 for example).'); } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) { $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); return; } } $oldInstallerPlugin = ($package->getType() === 'composer-installer'); if (in_array($package->getName(), $this->registeredPlugins)) { return; } $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']); $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; $pool = new Pool('dev'); $pool->addRepository($localRepo); if ($globalRepo) { $pool->addRepository($globalRepo); } $autoloadPackages = array($package->getName() => $package); $autoloadPackages = $this->collectDependencies($pool, $autoloadPackages, $package); $generator = $this->composer->getAutoloadGenerator(); $autoloads = array(); foreach ($autoloadPackages as $autoloadPackage) { $downloadPath = $this->getInstallPath($autoloadPackage, ($globalRepo && $globalRepo->hasPackage($autoloadPackage))); $autoloads[] = array($autoloadPackage, $downloadPath); } $map = $generator->parseAutoloads($autoloads, new Package('dummy', '1.0.0.0', '1.0.0')); $classLoader = $generator->createLoader($map); $classLoader->register(); foreach ($classes as $class) { if (class_exists($class, false)) { $class = trim($class, '\\'); $path = $classLoader->findFile($class); $code = file_get_contents($path); $separatorPos = strrpos($class, '\\'); $className = $class; if ($separatorPos) { $className = substr($class, $separatorPos + 1); } $code = preg_replace('{^((?:final\s+)?(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1); $code = str_replace('__FILE__', var_export($path, true), $code); $code = str_replace('__DIR__', var_export(dirname($path), true), $code); $code = str_replace('__CLASS__', var_export($class, true), $code); $code = preg_replace('/^\s*<\?(php)?/i', '', $code, 1); eval($code); $class .= '_composer_tmp'.self::$classCounter; self::$classCounter++; } if ($oldInstallerPlugin) { $installer = new $class($this->io, $this->composer); $this->composer->getInstallationManager()->addInstaller($installer); } elseif (class_exists($class)) { $plugin = new $class(); $this->addPlugin($plugin); $this->registeredPlugins[] = $package->getName(); } elseif ($failOnMissingClasses) { throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class); } } } /** * Returns the version of the internal composer-plugin-api package. * * @return string */ protected function getPluginApiVersion() { return PluginInterface::PLUGIN_API_VERSION; } /** * Adds a plugin, activates it and registers it with the event dispatcher * * Ideally plugin packages should be registered via registerPackage, but if you use Composer * programmatically and want to register a plugin class directly this is a valid way * to do it. * * @param PluginInterface $plugin plugin instance */ public function addPlugin(PluginInterface $plugin) { $this->io->writeError('Loading plugin '.get_class($plugin), true, IOInterface::DEBUG); $this->plugins[] = $plugin; $plugin->activate($this->composer, $this->io); if ($plugin instanceof EventSubscriberInterface) { $this->composer->getEventDispatcher()->addSubscriber($plugin); } } /** * Load all plugins and installers from a repository * * Note that plugins in the specified repository that rely on events that * have fired prior to loading will be missed. This means you likely want to * call this method as early as possible. * * @param RepositoryInterface $repo Repository to scan for plugins to install * * @throws \RuntimeException */ private function loadRepository(RepositoryInterface $repo) { foreach ($repo->getPackages() as $package) { /** @var PackageInterface $package */ if ($package instanceof AliasPackage) { continue; } if ('composer-plugin' === $package->getType()) { $this->registerPackage($package); // Backward compatibility } elseif ('composer-installer' === $package->getType()) { $this->registerPackage($package); } } } /** * Recursively generates a map of package names to packages for all deps * * @param Pool $pool Package pool of installed packages * @param array $collected Current state of the map for recursion * @param PackageInterface $package The package to analyze * * @return array Map of package names to packages */ private function collectDependencies(Pool $pool, array $collected, PackageInterface $package) { $requires = array_merge( $package->getRequires(), $package->getDevRequires() ); foreach ($requires as $requireLink) { $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink); if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) { $collected[$requiredPackage->getName()] = $requiredPackage; $collected = $this->collectDependencies($pool, $collected, $requiredPackage); } } return $collected; } /** * Resolves a package link to a package in the installed pool * * Since dependencies are already installed this should always find one. * * @param Pool $pool Pool of installed packages only * @param Link $link Package link to look up * * @return PackageInterface|null The found package */ private function lookupInstalledPackage(Pool $pool, Link $link) { $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint()); return (!empty($packages)) ? $packages[0] : null; } /** * Retrieves the path a package is installed to. * * @param PackageInterface $package * @param bool $global Whether this is a global package * * @return string Install path */ private function getInstallPath(PackageInterface $package, $global = false) { if (!$global) { return $this->composer->getInstallationManager()->getInstallPath($package); } return $this->globalComposer->getInstallationManager()->getInstallPath($package); } /** * @param PluginInterface $plugin * @param string $capability * @throws \RuntimeException On empty or non-string implementation class name value * @return null|string The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it */ protected function getCapabilityImplementationClassName(PluginInterface $plugin, $capability) { if (!($plugin instanceof Capable)) { return null; } $capabilities = (array) $plugin->getCapabilities(); if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) { return trim($capabilities[$capability]); } if ( array_key_exists($capability, $capabilities) && (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability])) ) { throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], 1)); } } /** * @param PluginInterface $plugin * @param string $capabilityClassName The fully qualified name of the API interface which the plugin may provide * an implementation of. * @param array $ctorArgs Arguments passed to Capability's constructor. * Keeping it an array will allow future values to be passed w\o changing the signature. * @return null|Capability */ public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = array()) { if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) { if (!class_exists($capabilityClass)) { throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist."); } $ctorArgs['plugin'] = $plugin; $capabilityObj = new $capabilityClass($ctorArgs); // FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9 if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) { throw new \RuntimeException( 'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.' ); } return $capabilityObj; } } /** * @param string $capabilityClassName The fully qualified name of the API interface which the plugin may provide * an implementation of. * @param array $ctorArgs Arguments passed to Capability's constructor. * Keeping it an array will allow future values to be passed w\o changing the signature. * @return Capability[] */ public function getPluginCapabilities($capabilityClassName, array $ctorArgs = array()) { $capabilities = array(); foreach ($this->getPlugins() as $plugin) { if ($capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs)) { $capabilities[] = $capability; } } return $capabilities; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; /** * Plugins which need to expose various implementations * of the Composer Plugin Capabilities must have their * declared Plugin class implementing this interface. * * @api */ interface Capable { /** * Method by which a Plugin announces its API implementations, through an array * with a special structure. * * The key must be a string, representing a fully qualified class/interface name * which Composer Plugin API exposes. * The value must be a string as well, representing the fully qualified class name * of the implementing class. * * @tutorial * * return array( * 'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider', * 'Composer\Plugin\Capability\Validator' => 'My\Validator', * ); * * @return string[] */ public function getCapabilities(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Exception; /** * @author Jordi Boggiano */ class NoSslException extends \RuntimeException { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ function includeIfExists($file) { return file_exists($file) ? include $file : false; } if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) { echo 'You must set up the project dependencies using `composer install`'.PHP_EOL. 'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL; exit(1); } return $loader; = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit676e4dc925e4d952efb2d5f8c1115412::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\ComposerStaticInit676e4dc925e4d952efb2d5f8c1115412::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire676e4dc925e4d952efb2d5f8c1115412($fileIdentifier, $file); } return $loader; } } function composerRequire676e4dc925e4d952efb2d5f8c1115412($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines the absence of a constraint. */ class EmptyConstraint implements ConstraintInterface { /** @var string */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { return true; } /** * @param $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } /** * @return string */ public function __toString() { return '[]'; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines a conjunctive or disjunctive set of constraints. */ class MultiConstraint implements ConstraintInterface { /** @var ConstraintInterface[] */ protected $constraints; /** @var string */ protected $prettyString; /** @var bool */ protected $conjunctive; /** * @param ConstraintInterface[] $constraints A set of constraints * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive */ public function __construct(array $constraints, $conjunctive = true) { $this->constraints = $constraints; $this->conjunctive = $conjunctive; } /** * @return ConstraintInterface[] */ public function getConstraints() { return $this->constraints; } /** * @return bool */ public function isConjunctive() { return $this->conjunctive; } /** * @return bool */ public function isDisjunctive() { return !$this->conjunctive; } /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { if (false === $this->conjunctive) { foreach ($this->constraints as $constraint) { if ($constraint->matches($provider)) { return true; } } return false; } foreach ($this->constraints as $constraint) { if (!$constraint->matches($provider)) { return false; } } return true; } /** * @param string $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } /** * @return string */ public function __toString() { $constraints = array(); foreach ($this->constraints as $constraint) { $constraints[] = (string) $constraint; } return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines a constraint. */ class Constraint implements ConstraintInterface { /* operator integer values */ const OP_EQ = 0; const OP_LT = 1; const OP_LE = 2; const OP_GT = 3; const OP_GE = 4; const OP_NE = 5; /** * Operator to integer translation table. * * @var array */ private static $transOpStr = array( '=' => self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE, ); /** * Integer to operator translation table. * * @var array */ private static $transOpInt = array( self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!=', ); /** @var string */ protected $operator; /** @var string */ protected $version; /** @var string */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { if ($provider instanceof $this) { return $this->matchSpecific($provider); } // turn matching around to find a match return $provider->matches($this); } /** * @param string $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } /** * Get all supported comparison operators. * * @return array */ public static function getSupportedOperators() { return array_keys(self::$transOpStr); } /** * Sets operator and version to compare with. * * @param string $operator * @param string $version * * @throws \InvalidArgumentException if invalid operator is given. */ public function __construct($operator, $version) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $this->operator = self::$transOpStr[$operator]; $this->version = $version; } /** * @param string $a * @param string $b * @param string $operator * @param bool $compareBranches * * @throws \InvalidArgumentException if invalid operator is given. * * @return bool */ public function versionCompare($a, $b, $operator, $compareBranches = false) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $aIsBranch = 'dev-' === substr($a, 0, 4); $bIsBranch = 'dev-' === substr($b, 0, 4); if ($aIsBranch && $bIsBranch) { return $operator === '==' && $a === $b; } // when branches are not comparable, we make sure dev branches never match anything if (!$compareBranches && ($aIsBranch || $bIsBranch)) { return false; } return version_compare($a, $b, $operator); } /** * @param Constraint $provider * @param bool $compareBranches * * @return bool */ public function matchSpecific(Constraint $provider, $compareBranches = false) { $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); $isEqualOp = self::OP_EQ === $this->operator; $isNonEqualOp = self::OP_NE === $this->operator; $isProviderEqualOp = self::OP_EQ === $provider->operator; $isProviderNonEqualOp = self::OP_NE === $provider->operator; // '!=' operator is match when other operator is not '==' operator or version is not match // these kinds of comparisons always have a solution if ($isNonEqualOp || $isProviderNonEqualOp) { return !$isEqualOp && !$isProviderEqualOp || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); } // an example for the condition is <= 2.0 & < 1.0 // these kinds of comparisons always have a solution if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { return true; } if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) { // special case, e.g. require >= 1.0 and provide < 1.0 // 1.0 >= 1.0 but 1.0 is outside of the provided interval if ($provider->version === $this->version && self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp) { return false; } return true; } return false; } /** * @return string */ public function __toString() { return self::$transOpInt[$this->operator] . ' ' . $this->version; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; trigger_error('The ' . __CLASS__ . ' abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED); /** * Base constraint class. */ abstract class AbstractConstraint implements ConstraintInterface { /** @var string */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider) { if ($provider instanceof $this) { // see note at bottom of this class declaration return $this->matchSpecific($provider); } // turn matching around to find a match return $provider->matches($this); } /** * @param string $prettyString */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * @return string */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } // implementations must implement a method of this format: // not declared abstract here because type hinting violates parameter coherence (TODO right word?) // public function matchSpecific( $provider); } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; interface ConstraintInterface { /** * @param ConstraintInterface $provider * * @return bool */ public function matches(ConstraintInterface $provider); /** * @return string */ public function getPrettyString(); /** * @return string */ public function __toString(); } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\EmptyConstraint; use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Constraint\Constraint; /** * Version parser. * * @author Jordi Boggiano */ class VersionParser { /** * Regex to match pre-release data (sort of). * * Due to backwards compatibility: * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted. * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier. * - Numerical-only pre-release identifiers are not supported, see tests. * * |--------------| * [major].[minor].[patch] -[pre-release] +[build-metadata] * * @var string */ private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?'; /** @var array */ private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev'); /** * Returns the stability of a version. * * @param string $version * * @return string */ public static function parseStability($version) { $version = preg_replace('{#.+$}i', '', $version); if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) { return 'dev'; } preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match); if (!empty($match[3])) { return 'dev'; } if (!empty($match[1])) { if ('beta' === $match[1] || 'b' === $match[1]) { return 'beta'; } if ('alpha' === $match[1] || 'a' === $match[1]) { return 'alpha'; } if ('rc' === $match[1]) { return 'RC'; } } return 'stable'; } /** * @param string $stability * * @return string */ public static function normalizeStability($stability) { $stability = strtolower($stability); return $stability === 'rc' ? 'RC' : $stability; } /** * Normalizes a version string to be able to perform comparisons on it. * * @param string $version * @param string $fullVersion optional complete version string to give more context * * @throws \UnexpectedValueException * * @return string */ public function normalize($version, $fullVersion = null) { $version = trim($version); if (null === $fullVersion) { $fullVersion = $version; } // strip off aliasing if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) { $version = $match[1]; } // match master-like branches if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { return '9999999-dev'; } // if requirement is branch-like, use full name if ('dev-' === strtolower(substr($version, 0, 4))) { return 'dev-' . substr($version, 4); } // strip off build metadata if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) { $version = $match[1]; } // match classical versioning if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { $version = $matches[1] . (!empty($matches[2]) ? $matches[2] : '.0') . (!empty($matches[3]) ? $matches[3] : '.0') . (!empty($matches[4]) ? $matches[4] : '.0'); $index = 5; // match date(time) based versioning } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { $version = preg_replace('{\D}', '.', $matches[1]); $index = 2; } // add version modifiers if a version was matched if (isset($index)) { if (!empty($matches[$index])) { if ('stable' === $matches[$index]) { return $version; } $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? ltrim($matches[$index + 1], '.-') : ''); } if (!empty($matches[$index + 2])) { $version .= '-dev'; } return $version; } // match dev branches if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { try { return $this->normalizeBranch($match[1]); } catch (\Exception $e) { } } $extraMessage = ''; if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; } throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage); } /** * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison. * * @param string $branch Branch name (e.g. 2.1.x-dev) * * @return string|false Numeric prefix if present (e.g. 2.1.) or false */ public function parseNumericAliasPrefix($branch) { if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) { return $matches['version'] . '.'; } return false; } /** * Normalizes a branch name to be able to perform comparisons on it. * * @param string $name * * @return string */ public function normalizeBranch($name) { $name = trim($name); if (in_array($name, array('master', 'trunk', 'default'))) { return $this->normalize($name); } if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { $version = ''; for ($i = 1; $i < 5; ++$i) { $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; } return str_replace('x', '9999999', $version) . '-dev'; } return 'dev-' . $name; } /** * Parses a constraint string into MultiConstraint and/or Constraint objects. * * @param string $constraints * * @return ConstraintInterface */ public function parseConstraints($constraints) { $prettyConstraint = $constraints; if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) { $constraints = empty($match[1]) ? '*' : $match[1]; } if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) { $constraints = $match[1]; } $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints)); $orGroups = array(); foreach ($orConstraints as $constraints) { $andConstraints = preg_split('{(?< ,]) *(? 1) { $constraintObjects = array(); foreach ($andConstraints as $constraint) { foreach ($this->parseConstraint($constraint) as $parsedConstraint) { $constraintObjects[] = $parsedConstraint; } } } else { $constraintObjects = $this->parseConstraint($andConstraints[0]); } if (1 === count($constraintObjects)) { $constraint = $constraintObjects[0]; } else { $constraint = new MultiConstraint($constraintObjects); } $orGroups[] = $constraint; } if (1 === count($orGroups)) { $constraint = $orGroups[0]; } elseif (2 === count($orGroups) // parse the two OR groups and if they are contiguous we collapse // them into one constraint && $orGroups[0] instanceof MultiConstraint && $orGroups[1] instanceof MultiConstraint && 2 === count($orGroups[0]->getConstraints()) && 2 === count($orGroups[1]->getConstraints()) && ($a = (string) $orGroups[0]) && substr($a, 0, 3) === '[>=' && (false !== ($posA = strpos($a, '<', 4))) && ($b = (string) $orGroups[1]) && substr($b, 0, 3) === '[>=' && (false !== ($posB = strpos($b, '<', 4))) && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5) ) { $constraint = new MultiConstraint(array( new Constraint('>=', substr($a, 4, $posA - 5)), new Constraint('<', substr($b, $posB + 2, -1)), )); } else { $constraint = new MultiConstraint($orGroups, false); } $constraint->setPrettyString($prettyConstraint); return $constraint; } /** * @param string $constraint * * @throws \UnexpectedValueException * * @return array */ private function parseConstraint($constraint) { if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) { $constraint = $match[1]; if ($match[2] !== 'stable') { $stabilityModifier = $match[2]; } } if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) { return array(new EmptyConstraint()); } $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?'; // Tilde Range // // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous // version, to ensure that unstable instances of the current version are allowed. However, if a stability // suffix is added to the constraint, then a >= match on the current version is used instead. if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { if (substr($constraint, 0, 2) === '~>') { throw new \UnexpectedValueException( 'Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator' ); } // Work out which position in the version we are operating at if (isset($matches[4]) && '' !== $matches[4]) { $position = 4; } elseif (isset($matches[3]) && '' !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2]) { $position = 2; } else { $position = 1; } // Calculate the stability suffix $stabilitySuffix = ''; if (!empty($matches[5])) { $stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : ''); } if (!empty($matches[7])) { $stabilitySuffix .= '-dev'; } if (!$stabilitySuffix) { $stabilitySuffix = '-dev'; } $lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix; $lowerBound = new Constraint('>=', $lowVersion); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal $highPosition = max(1, $position - 1); $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } // Caret Range // // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for // versions 0.X >=0.1.0, and no updates for versions 0.0.X if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { // Work out which position in the version we are operating at if ('0' !== $matches[1] || '' === $matches[2]) { $position = 1; } elseif ('0' !== $matches[2] || '' === $matches[3]) { $position = 2; } else { $position = 3; } // Calculate the stability suffix $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } // X Range // // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple. // A partial version range is treated as an X-Range, so the special character is in fact optional. if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { if (isset($matches[3]) && '' !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2]) { $position = 2; } else { $position = 1; } $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; if ($lowVersion === '0.0.0.0-dev') { return array(new Constraint('<', $highVersion)); } return array( new Constraint('>=', $lowVersion), new Constraint('<', $highVersion), ); } // Hyphen Range // // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range, // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but // nothing that would be greater than the provided tuple parts. if (preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { // Calculate the stability suffix $lowStabilitySuffix = ''; if (empty($matches[6]) && empty($matches[8])) { $lowStabilitySuffix = '-dev'; } $lowVersion = $this->normalize($matches['from']); $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); $empty = function ($x) { return ($x === 0 || $x === '0') ? false : empty($x); }; if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) { $highVersion = $this->normalize($matches['to']); $upperBound = new Constraint('<=', $highVersion); } else { $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]); $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); } return array( $lowerBound, $upperBound, ); } // Basic Comparators if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { try { $version = $this->normalize($matches[2]); if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') { $version .= '-' . $stabilityModifier; } elseif ('<' === $matches[1] || '>=' === $matches[1]) { if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { if (substr($matches[2], 0, 4) !== 'dev-') { $version .= '-dev'; } } } return array(new Constraint($matches[1] ?: '=', $version)); } catch (\Exception $e) { } } $message = 'Could not parse version constraint ' . $constraint; if (isset($e)) { $message .= ': ' . $e->getMessage(); } throw new \UnexpectedValueException($message); } /** * Increment, decrement, or simply pad a version number. * * Support function for {@link parseConstraint()} * * @param array $matches Array with version parts in array indexes 1,2,3,4 * @param int $position 1,2,3,4 - which segment of the version to increment/decrement * @param int $increment * @param string $pad The string to pad version parts after $position * * @return string The new version */ private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0') { for ($i = 4; $i > 0; --$i) { if ($i > $position) { $matches[$i] = $pad; } elseif ($i === $position && $increment) { $matches[$i] += $increment; // If $matches[$i] was 0, carry the decrement if ($matches[$i] < 0) { $matches[$i] = $pad; --$position; // Return null on a carry overflow if ($i === 1) { return; } } } } return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; } /** * Expand shorthand stability string to long version. * * @param string $stability * * @return string */ private function expandStability($stability) { $stability = strtolower($stability); switch ($stability) { case 'a': return 'alpha'; case 'b': return 'beta'; case 'p': case 'pl': return 'patch'; case 'rc': return 'RC'; default: return $stability; } } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; class Comparator { /** * Evaluates the expression: $version1 > $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function greaterThan($version1, $version2) { return self::compare($version1, '>', $version2); } /** * Evaluates the expression: $version1 >= $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function greaterThanOrEqualTo($version1, $version2) { return self::compare($version1, '>=', $version2); } /** * Evaluates the expression: $version1 < $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function lessThan($version1, $version2) { return self::compare($version1, '<', $version2); } /** * Evaluates the expression: $version1 <= $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function lessThanOrEqualTo($version1, $version2) { return self::compare($version1, '<=', $version2); } /** * Evaluates the expression: $version1 == $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function equalTo($version1, $version2) { return self::compare($version1, '==', $version2); } /** * Evaluates the expression: $version1 != $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function notEqualTo($version1, $version2) { return self::compare($version1, '!=', $version2); } /** * Evaluates the expression: $version1 $operator $version2. * * @param string $version1 * @param string $operator * @param string $version2 * * @return bool */ public static function compare($version1, $operator, $version2) { $constraint = new Constraint($operator, $version2); return $constraint->matches(new Constraint('==', $version1)); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; class Semver { const SORT_ASC = 1; const SORT_DESC = -1; /** @var VersionParser */ private static $versionParser; /** * Determine if given version satisfies given constraints. * * @param string $version * @param string $constraints * * @return bool */ public static function satisfies($version, $constraints) { if (null === self::$versionParser) { self::$versionParser = new VersionParser(); } $versionParser = self::$versionParser; $provider = new Constraint('==', $versionParser->normalize($version)); $constraints = $versionParser->parseConstraints($constraints); return $constraints->matches($provider); } /** * Return all versions that satisfy given constraints. * * @param array $versions * @param string $constraints * * @return array */ public static function satisfiedBy(array $versions, $constraints) { $versions = array_filter($versions, function ($version) use ($constraints) { return Semver::satisfies($version, $constraints); }); return array_values($versions); } /** * Sort given array of versions. * * @param array $versions * * @return array */ public static function sort(array $versions) { return self::usort($versions, self::SORT_ASC); } /** * Sort given array of versions in reverse. * * @param array $versions * * @return array */ public static function rsort(array $versions) { return self::usort($versions, self::SORT_DESC); } /** * @param array $versions * @param int $direction * * @return array */ private static function usort(array $versions, $direction) { if (null === self::$versionParser) { self::$versionParser = new VersionParser(); } $versionParser = self::$versionParser; $normalized = array(); // Normalize outside of usort() scope for minor performance increase. // Creates an array of arrays: [[normalized, key], ...] foreach ($versions as $key => $version) { $normalized[] = array($versionParser->normalize($version), $key); } usort($normalized, function (array $left, array $right) use ($direction) { if ($left[0] === $right[0]) { return 0; } if (Comparator::lessThan($left[0], $right[0])) { return -$direction; } return $direction; }); // Recreate input array, using the original indexes which are now in sorted order. $sorted = array(); foreach ($normalized as $item) { $sorted[] = $versions[$item[1]]; } return $sorted; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier * @author Jordi Boggiano * @see http://www.php-fig.org/psr/psr-0/ * @see http://www.php-fig.org/psr/psr-4/ */ class ClassLoader { // PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); // PSR-0 private $prefixesPsr0 = array(); private $fallbackDirsPsr0 = array(); private $useIncludePath = false; private $classMap = array(); private $classMapAuthoritative = false; private $missingClasses = array(); private $apcuPrefix; public function getPrefixes() { if (!empty($this->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; } /** * @param array $classMap Class to filename map */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param array|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories */ 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 ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param array|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException */ public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $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) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param array|string $paths The PSR-0 base directories */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param array|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException */ 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; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); } /** * Unregisters this instance as an autoloader. */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); } /** * Loads the given class or interface. * * @param string $class The name of the class * @return bool|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath.'\\'; if (isset($this->prefixDirsPsr4[$search])) { foreach ($this->prefixDirsPsr4[$search] as $dir) { $length = $this->prefixLengthsPsr4[$first][$search]; if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $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; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } /** * Scope isolated include. * * Prevents access to $this/self from included files. */ function includeFile($file) { include $file; } array($vendorDir . '/wp-cli/php-cli-tools/lib'), 'WP_CLI' => array($baseDir . '/php'), 'Requests' => array($vendorDir . '/rmccue/requests/library'), 'Oxymel' => array($vendorDir . '/nb/oxymel'), 'Mustache' => array($vendorDir . '/mustache/mustache/src'), 'Behat\\Gherkin' => array($vendorDir . '/behat/gherkin/src'), 'Behat\\Behat' => array($vendorDir . '/behat/behat/src'), ); * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Spdx; class SpdxLicenses { /** @var string */ const LICENSES_FILE = 'spdx-licenses.json'; /** @var string */ const EXCEPTIONS_FILE = 'spdx-exceptions.json'; /** * Contains all the licenses. * * The array is indexed by license identifiers, which contain * a numerically indexed array with license details. * * [ license identifier => * [ 0 => full name (string), 1 => osi certified (bool) ] * , ... * ] * * @var array */ private $licenses; /** * @var string */ private $licensesExpression; /** * Contains all the license exceptions. * * The array is indexed by license exception identifiers, which contain * a numerically indexed array with license exception details. * * [ exception identifier => * [ 0 => full name (string) ] * , ... * ] * * @var array */ private $exceptions; /** * @var string */ private $exceptionsExpression; public function __construct() { $this->loadLicenses(); $this->loadExceptions(); } /** * Returns license metadata by license identifier. * * This function adds a link to the full license text to the license metadata. * The array returned is in the form of: * * [ 0 => full name (string), 1 => osi certified, 2 => link to license text (string) ] * * @param string $identifier * * @return array|null */ public function getLicenseByIdentifier($identifier) { if (!isset($this->licenses[$identifier])) { return; } $license = $this->licenses[$identifier]; $license[] = 'https://spdx.org/licenses/' . $identifier . '.html#licenseText'; return $license; } /** * Returns license exception metadata by license exception identifier. * * This function adds a link to the full license exception text to the license exception metadata. * The array returned is in the form of: * * [ 0 => full name (string), 1 => link to license text (string) ] * * @param string $identifier * * @return array|null */ public function getExceptionByIdentifier($identifier) { if (!isset($this->exceptions[$identifier])) { return; } $license = $this->exceptions[$identifier]; $license[] = 'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText'; return $license; } /** * Returns the short identifier of a license (or license exception) by full name. * * @param string $name * * @return string|null */ public function getIdentifierByName($name) { foreach ($this->licenses as $identifier => $licenseData) { if ($licenseData[0] === $name) { return $identifier; } } foreach ($this->exceptions as $identifier => $licenseData) { if ($licenseData[0] === $name) { return $identifier; } } } /** * Returns the OSI Approved status for a license by identifier. * * @param string $identifier * * @return bool */ public function isOsiApprovedByIdentifier($identifier) { return $this->licenses[$identifier][1]; } /** * @param array|string $license * * @throws \InvalidArgumentException * * @return bool */ public function validate($license) { if (is_array($license)) { $count = count($license); if ($count !== count(array_filter($license, 'is_string'))) { throw new \InvalidArgumentException('Array of strings expected.'); } $license = $count > 1 ? '(' . implode(' OR ', $license) . ')' : (string) reset($license); } if (!is_string($license)) { throw new \InvalidArgumentException(sprintf( 'Array or String expected, %s given.', gettype($license) )); } return $this->isValidLicenseString($license); } /** * @return string */ public static function getResourcesDir() { return dirname(__DIR__) . '/res'; } private function loadLicenses() { if (null === $this->licenses) { $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE); $this->licenses = json_decode($json, true); } } private function loadExceptions() { if (null === $this->exceptions) { $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); $this->exceptions = json_decode($json, true); } } /** * @return string */ private function getLicensesExpression() { if (null === $this->licensesExpression) { $licenses = array_map('preg_quote', array_keys($this->licenses)); rsort($licenses); $licenses = implode('|', $licenses); $this->licensesExpression = $licenses; } return $this->licensesExpression; } /** * @return string */ private function getExceptionsExpression() { if (null === $this->exceptionsExpression) { $exceptions = array_map('preg_quote', array_keys($this->exceptions)); rsort($exceptions); $exceptions = implode('|', $exceptions); $this->exceptionsExpression = $exceptions; } return $this->exceptionsExpression; } /** * @param string $license * * @throws \RuntimeException * * @return bool */ private function isValidLicenseString($license) { if (isset($this->licenses[$license])) { return true; } $licenses = $this->getLicensesExpression(); $exceptions = $this->getExceptionsExpression(); $regex = <<[\pL\pN.-]{1,}) # license-id: taken from list (?${licenses}) # license-exception-id: taken from list (?${exceptions}) # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring) (?(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring)) # simple-expresssion: license-id / license-id+ / license-ref (?(?&licenseid)\+? | (?&licenseid) | (?&licenseref)) # compound-expression: 1*( # simple-expression / # simple-expression WITH license-exception-id / # compound-expression AND compound-expression / # compound-expression OR compound-expression # ) / ( compound-expression ) ) (? (?&simple_expression) ( \s+ (?:with|WITH) \s+ (?&licenseexceptionid))? | \( \s* (?&compound_expression) \s* \) ) (? (?&compound_head) (?: \s+ (?:and|AND|or|OR) \s+ (?&compound_expression))? ) # license-expression: 1*1(simple-expression / compound-expression) (?(?&compound_expression) | (?&simple_expression)) ) # end of define ^(NONE | NOASSERTION | (?&license_expression))$ }x REGEX; $match = preg_match($regex, $license); if (0 === $match) { return false; } if (false === $match) { throw new \RuntimeException('Regex failed to compile/run.'); } return true; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Spdx; /** * The SPDX Licenses Updater scrapes licenses from the spdx website * and updates the "res/*.json" file accordingly. * * The class is used by the update script "bin/update-spdx-licenses". */ class SpdxLicensesUpdater { /** * @param string $file * @param string $url */ public function dumpLicenses($file = null, $url = 'https://spdx.org/licenses/index.html') { if (null === $file) { $file = SpdxLicenses::getResourcesDir() . '/' . SpdxLicenses::LICENSES_FILE; } $options = 0; if (defined('JSON_PRETTY_PRINT')) { $options |= JSON_PRETTY_PRINT; } if (defined('JSON_UNESCAPED_SLASHES')) { $options |= JSON_UNESCAPED_SLASHES; } $licenses = json_encode($this->getLicenses($url), $options); file_put_contents($file, $licenses); } /** * @param string $file * @param string $url */ public function dumpExceptions($file = null, $url = 'https://spdx.org/licenses/exceptions-index.html') { if (null === $file) { $file = SpdxLicenses::getResourcesDir() . '/' . SpdxLicenses::EXCEPTIONS_FILE; } $options = 0; if (defined('JSON_PRETTY_PRINT')) { $options |= JSON_PRETTY_PRINT; } if (defined('JSON_UNESCAPED_SLASHES')) { $options |= JSON_UNESCAPED_SLASHES; } $exceptions = json_encode($this->getExceptions($url), $options); file_put_contents($file, $exceptions); } /** * @param string $url * * @return array */ private function getLicenses($url) { $licenses = array(); $dom = new \DOMDocument(); @$dom->loadHTMLFile($url); /* use silence operator to ignore warnings about invalid dom content */ $xPath = new \DOMXPath($dom); $trs = $xPath->query('//table//tbody//tr'); /** @var \DOMElement $tr */ foreach ($trs as $tr) { $tds = $tr->getElementsByTagName('td'); if ($tds->length !== 4) { continue; } if ('License Text' === trim($tds->item(3)->nodeValue)) { $fullname = trim($tds->item(0)->nodeValue); $identifier = trim($tds->item(1)->nodeValue); $osiApproved = ((isset($tds->item(2)->nodeValue) && $tds->item(2)->nodeValue === 'Y')) ? true : false; $licenses += array($identifier => array($fullname, $osiApproved)); } } uksort($licenses, 'strcasecmp'); return $licenses; } /** * @param string $url * * @return array */ private function getExceptions($url) { $exceptions = array(); $dom = new \DOMDocument(); @$dom->loadHTMLFile($url); /* use silence operator to ignore warnings about invalid dom content */ $xPath = new \DOMXPath($dom); $trs = $xPath->query('//table//tbody//tr'); /** @var \DOMElement $tr */ foreach ($trs as $tr) { $tds = $tr->getElementsByTagName('td'); if ($tds->length !== 3) { continue; } if ('License Exception Text' === trim($tds->item(2)->nodeValue)) { $fullname = trim($tds->item(0)->nodeValue); $identifier = trim($tds->item(1)->nodeValue); $exceptions += array($identifier => array($fullname)); } } uksort($exceptions, 'strcasecmp'); return $exceptions; } } $vendorDir . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php', 'Composer\\Autoload\\ClassLoader' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassLoader.php', 'Composer\\Autoload\\ClassMapGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php', 'Composer\\CaBundle\\CaBundle' => $vendorDir . '/composer/ca-bundle/src/CaBundle.php', 'Composer\\Cache' => $vendorDir . '/composer/composer/src/Composer/Cache.php', 'Composer\\Composer' => $vendorDir . '/composer/composer/src/Composer/Composer.php', 'Composer\\Config' => $vendorDir . '/composer/composer/src/Composer/Config.php', 'Composer\\Config\\ConfigSourceInterface' => $vendorDir . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php', 'Composer\\Config\\JsonConfigSource' => $vendorDir . '/composer/composer/src/Composer/Config/JsonConfigSource.php', 'Composer\\DependencyResolver\\Decisions' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Decisions.php', 'Composer\\DependencyResolver\\DefaultPolicy' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php', 'Composer\\DependencyResolver\\GenericRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php', 'Composer\\DependencyResolver\\Operation\\InstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php', 'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php', 'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php', 'Composer\\DependencyResolver\\Operation\\OperationInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php', 'Composer\\DependencyResolver\\Operation\\SolverOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php', 'Composer\\DependencyResolver\\Operation\\UninstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php', 'Composer\\DependencyResolver\\Operation\\UpdateOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php', 'Composer\\DependencyResolver\\PolicyInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php', 'Composer\\DependencyResolver\\Pool' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Pool.php', 'Composer\\DependencyResolver\\Problem' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Problem.php', 'Composer\\DependencyResolver\\Request' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Request.php', 'Composer\\DependencyResolver\\Rule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule.php', 'Composer\\DependencyResolver\\Rule2Literals' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php', 'Composer\\DependencyResolver\\RuleSet' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php', 'Composer\\DependencyResolver\\RuleSetGenerator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php', 'Composer\\DependencyResolver\\RuleSetIterator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php', 'Composer\\DependencyResolver\\RuleWatchChain' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php', 'Composer\\DependencyResolver\\RuleWatchGraph' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php', 'Composer\\DependencyResolver\\RuleWatchNode' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php', 'Composer\\DependencyResolver\\Solver' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Solver.php', 'Composer\\DependencyResolver\\SolverBugException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php', 'Composer\\DependencyResolver\\SolverProblemsException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php', 'Composer\\DependencyResolver\\Transaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Transaction.php', 'Composer\\Downloader\\ArchiveDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php', 'Composer\\Downloader\\ChangeReportInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php', 'Composer\\Downloader\\DownloadManager' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloadManager.php', 'Composer\\Downloader\\DownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php', 'Composer\\Downloader\\DvcsDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php', 'Composer\\Downloader\\FileDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FileDownloader.php', 'Composer\\Downloader\\FilesystemException' => $vendorDir . '/composer/composer/src/Composer/Downloader/FilesystemException.php', 'Composer\\Downloader\\FossilDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FossilDownloader.php', 'Composer\\Downloader\\GitDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GitDownloader.php', 'Composer\\Downloader\\GzipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GzipDownloader.php', 'Composer\\Downloader\\HgDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/HgDownloader.php', 'Composer\\Downloader\\PathDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PathDownloader.php', 'Composer\\Downloader\\PerforceDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php', 'Composer\\Downloader\\PharDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PharDownloader.php', 'Composer\\Downloader\\RarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/RarDownloader.php', 'Composer\\Downloader\\SvnDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/SvnDownloader.php', 'Composer\\Downloader\\TarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/TarDownloader.php', 'Composer\\Downloader\\TransportException' => $vendorDir . '/composer/composer/src/Composer/Downloader/TransportException.php', 'Composer\\Downloader\\VcsCapableDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php', 'Composer\\Downloader\\VcsDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsDownloader.php', 'Composer\\Downloader\\XzDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/XzDownloader.php', 'Composer\\Downloader\\ZipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ZipDownloader.php', 'Composer\\EventDispatcher\\Event' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/Event.php', 'Composer\\EventDispatcher\\EventDispatcher' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php', 'Composer\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php', 'Composer\\EventDispatcher\\ScriptExecutionException' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php', 'Composer\\Exception\\NoSslException' => $vendorDir . '/composer/composer/src/Composer/Exception/NoSslException.php', 'Composer\\Factory' => $vendorDir . '/composer/composer/src/Composer/Factory.php', 'Composer\\IO\\BaseIO' => $vendorDir . '/composer/composer/src/Composer/IO/BaseIO.php', 'Composer\\IO\\BufferIO' => $vendorDir . '/composer/composer/src/Composer/IO/BufferIO.php', 'Composer\\IO\\ConsoleIO' => $vendorDir . '/composer/composer/src/Composer/IO/ConsoleIO.php', 'Composer\\IO\\IOInterface' => $vendorDir . '/composer/composer/src/Composer/IO/IOInterface.php', 'Composer\\IO\\NullIO' => $vendorDir . '/composer/composer/src/Composer/IO/NullIO.php', 'Composer\\Installer' => $vendorDir . '/composer/composer/src/Composer/Installer.php', 'Composer\\Installer\\BinaryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryInstaller.php', 'Composer\\Installer\\BinaryPresenceInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php', 'Composer\\Installer\\InstallationManager' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallationManager.php', 'Composer\\Installer\\InstallerEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvent.php', 'Composer\\Installer\\InstallerEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvents.php', 'Composer\\Installer\\InstallerInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerInterface.php', 'Composer\\Installer\\LibraryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/LibraryInstaller.php', 'Composer\\Installer\\MetapackageInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php', 'Composer\\Installer\\NoopInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/NoopInstaller.php', 'Composer\\Installer\\PackageEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvent.php', 'Composer\\Installer\\PackageEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvents.php', 'Composer\\Installer\\PluginInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/PluginInstaller.php', 'Composer\\Installer\\ProjectInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/ProjectInstaller.php', 'Composer\\Installer\\SuggestedPackagesReporter' => $vendorDir . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php', 'Composer\\Json\\JsonFile' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFile.php', 'Composer\\Json\\JsonFormatter' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFormatter.php', 'Composer\\Json\\JsonManipulator' => $vendorDir . '/composer/composer/src/Composer/Json/JsonManipulator.php', 'Composer\\Json\\JsonValidationException' => $vendorDir . '/composer/composer/src/Composer/Json/JsonValidationException.php', 'Composer\\Package\\AliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/AliasPackage.php', 'Composer\\Package\\Archiver\\ArchivableFilesFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php', 'Composer\\Package\\Archiver\\ArchivableFilesFinder' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php', 'Composer\\Package\\Archiver\\ArchiveManager' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php', 'Composer\\Package\\Archiver\\ArchiverInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php', 'Composer\\Package\\Archiver\\BaseExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php', 'Composer\\Package\\Archiver\\ComposerExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php', 'Composer\\Package\\Archiver\\GitExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php', 'Composer\\Package\\Archiver\\HgExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/HgExcludeFilter.php', 'Composer\\Package\\Archiver\\PharArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php', 'Composer\\Package\\Archiver\\ZipArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php', 'Composer\\Package\\BasePackage' => $vendorDir . '/composer/composer/src/Composer/Package/BasePackage.php', 'Composer\\Package\\CompletePackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackage.php', 'Composer\\Package\\CompletePackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackageInterface.php', 'Composer\\Package\\Dumper\\ArrayDumper' => $vendorDir . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php', 'Composer\\Package\\Link' => $vendorDir . '/composer/composer/src/Composer/Package/Link.php', 'Composer\\Package\\LinkConstraint\\EmptyConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/EmptyConstraint.php', 'Composer\\Package\\LinkConstraint\\LinkConstraintInterface' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php', 'Composer\\Package\\LinkConstraint\\MultiConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/MultiConstraint.php', 'Composer\\Package\\LinkConstraint\\SpecificConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/SpecificConstraint.php', 'Composer\\Package\\LinkConstraint\\VersionConstraint' => $vendorDir . '/composer/composer/src/Composer/Package/LinkConstraint/VersionConstraint.php', 'Composer\\Package\\Loader\\ArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php', 'Composer\\Package\\Loader\\InvalidPackageException' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php', 'Composer\\Package\\Loader\\JsonLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php', 'Composer\\Package\\Loader\\LoaderInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php', 'Composer\\Package\\Loader\\RootPackageLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php', 'Composer\\Package\\Loader\\ValidatingArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php', 'Composer\\Package\\Locker' => $vendorDir . '/composer/composer/src/Composer/Package/Locker.php', 'Composer\\Package\\Package' => $vendorDir . '/composer/composer/src/Composer/Package/Package.php', 'Composer\\Package\\PackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/PackageInterface.php', 'Composer\\Package\\RootAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootAliasPackage.php', 'Composer\\Package\\RootPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackage.php', 'Composer\\Package\\RootPackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackageInterface.php', 'Composer\\Package\\Version\\VersionGuesser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php', 'Composer\\Package\\Version\\VersionParser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionParser.php', 'Composer\\Package\\Version\\VersionSelector' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionSelector.php', 'Composer\\Plugin\\Capability\\Capability' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/Capability.php', 'Composer\\Plugin\\Capability\\CommandProvider' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php', 'Composer\\Plugin\\Capable' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capable.php', 'Composer\\Plugin\\CommandEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/CommandEvent.php', 'Composer\\Plugin\\PluginEvents' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginEvents.php', 'Composer\\Plugin\\PluginInterface' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginInterface.php', 'Composer\\Plugin\\PluginManager' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginManager.php', 'Composer\\Plugin\\PreFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php', 'Composer\\Repository\\ArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArrayRepository.php', 'Composer\\Repository\\ArtifactRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArtifactRepository.php', 'Composer\\Repository\\BaseRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/BaseRepository.php', 'Composer\\Repository\\ComposerRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ComposerRepository.php', 'Composer\\Repository\\CompositeRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/CompositeRepository.php', 'Composer\\Repository\\ConfigurableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php', 'Composer\\Repository\\FilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilesystemRepository.php', 'Composer\\Repository\\InstalledArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php', 'Composer\\Repository\\InstalledFilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php', 'Composer\\Repository\\InstalledRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php', 'Composer\\Repository\\InvalidRepositoryException' => $vendorDir . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php', 'Composer\\Repository\\PackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PackageRepository.php', 'Composer\\Repository\\PathRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PathRepository.php', 'Composer\\Repository\\PlatformRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PlatformRepository.php', 'Composer\\Repository\\RepositoryFactory' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryFactory.php', 'Composer\\Repository\\RepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryInterface.php', 'Composer\\Repository\\RepositoryManager' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryManager.php', 'Composer\\Repository\\RepositorySecurityException' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php', 'Composer\\Repository\\VcsRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/VcsRepository.php', 'Composer\\Repository\\Vcs\\BitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/BitbucketDriver.php', 'Composer\\Repository\\Vcs\\FossilDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php', 'Composer\\Repository\\Vcs\\GitBitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php', 'Composer\\Repository\\Vcs\\GitDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php', 'Composer\\Repository\\Vcs\\GitHubDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php', 'Composer\\Repository\\Vcs\\GitLabDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php', 'Composer\\Repository\\Vcs\\HgBitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/HgBitbucketDriver.php', 'Composer\\Repository\\Vcs\\HgDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php', 'Composer\\Repository\\Vcs\\PerforceDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php', 'Composer\\Repository\\Vcs\\SvnDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php', 'Composer\\Repository\\Vcs\\VcsDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php', 'Composer\\Repository\\Vcs\\VcsDriverInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php', 'Composer\\Repository\\WritableArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php', 'Composer\\Repository\\WritableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php', 'Composer\\Script\\CommandEvent' => $vendorDir . '/composer/composer/src/Composer/Script/CommandEvent.php', 'Composer\\Script\\Event' => $vendorDir . '/composer/composer/src/Composer/Script/Event.php', 'Composer\\Script\\PackageEvent' => $vendorDir . '/composer/composer/src/Composer/Script/PackageEvent.php', 'Composer\\Script\\ScriptEvents' => $vendorDir . '/composer/composer/src/Composer/Script/ScriptEvents.php', 'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php', 'Composer\\Semver\\Constraint\\AbstractConstraint' => $vendorDir . '/composer/semver/src/Constraint/AbstractConstraint.php', 'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php', 'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php', 'Composer\\Semver\\Constraint\\EmptyConstraint' => $vendorDir . '/composer/semver/src/Constraint/EmptyConstraint.php', 'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php', 'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php', 'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php', 'Composer\\Util\\AuthHelper' => $vendorDir . '/composer/composer/src/Composer/Util/AuthHelper.php', 'Composer\\Util\\Bitbucket' => $vendorDir . '/composer/composer/src/Composer/Util/Bitbucket.php', 'Composer\\Util\\ComposerMirror' => $vendorDir . '/composer/composer/src/Composer/Util/ComposerMirror.php', 'Composer\\Util\\ConfigValidator' => $vendorDir . '/composer/composer/src/Composer/Util/ConfigValidator.php', 'Composer\\Util\\ErrorHandler' => $vendorDir . '/composer/composer/src/Composer/Util/ErrorHandler.php', 'Composer\\Util\\Filesystem' => $vendorDir . '/composer/composer/src/Composer/Util/Filesystem.php', 'Composer\\Util\\Git' => $vendorDir . '/composer/composer/src/Composer/Util/Git.php', 'Composer\\Util\\GitHub' => $vendorDir . '/composer/composer/src/Composer/Util/GitHub.php', 'Composer\\Util\\GitLab' => $vendorDir . '/composer/composer/src/Composer/Util/GitLab.php', 'Composer\\Util\\IniHelper' => $vendorDir . '/composer/composer/src/Composer/Util/IniHelper.php', 'Composer\\Util\\NoProxyPattern' => $vendorDir . '/composer/composer/src/Composer/Util/NoProxyPattern.php', 'Composer\\Util\\Perforce' => $vendorDir . '/composer/composer/src/Composer/Util/Perforce.php', 'Composer\\Util\\Platform' => $vendorDir . '/composer/composer/src/Composer/Util/Platform.php', 'Composer\\Util\\ProcessExecutor' => $vendorDir . '/composer/composer/src/Composer/Util/ProcessExecutor.php', 'Composer\\Util\\RemoteFilesystem' => $vendorDir . '/composer/composer/src/Composer/Util/RemoteFilesystem.php', 'Composer\\Util\\Silencer' => $vendorDir . '/composer/composer/src/Composer/Util/Silencer.php', 'Composer\\Util\\SpdxLicense' => $vendorDir . '/composer/composer/src/Composer/Util/SpdxLicense.php', 'Composer\\Util\\StreamContextFactory' => $vendorDir . '/composer/composer/src/Composer/Util/StreamContextFactory.php', 'Composer\\Util\\Svn' => $vendorDir . '/composer/composer/src/Composer/Util/Svn.php', 'Composer\\Util\\TlsHelper' => $vendorDir . '/composer/composer/src/Composer/Util/TlsHelper.php', 'Composer\\XdebugHandler' => $vendorDir . '/composer/composer/src/Composer/XdebugHandler.php', 'JsonSchema\\Constraints\\BaseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php', 'JsonSchema\\Constraints\\CollectionConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php', 'JsonSchema\\Constraints\\Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php', 'JsonSchema\\Constraints\\ConstraintInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php', 'JsonSchema\\Constraints\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php', 'JsonSchema\\Constraints\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php', 'JsonSchema\\Constraints\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php', 'JsonSchema\\Constraints\\NumberConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php', 'JsonSchema\\Constraints\\ObjectConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php', 'JsonSchema\\Constraints\\SchemaConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php', 'JsonSchema\\Constraints\\StringConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php', 'JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php', 'JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php', 'JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php', 'JsonSchema\\Constraints\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php', 'JsonSchema\\Constraints\\UndefinedConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php', 'JsonSchema\\Entity\\JsonPointer' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php', 'JsonSchema\\Exception\\ExceptionInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php', 'JsonSchema\\Exception\\InvalidArgumentException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php', 'JsonSchema\\Exception\\InvalidConfigException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php', 'JsonSchema\\Exception\\InvalidSchemaException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php', 'JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php', 'JsonSchema\\Exception\\InvalidSourceUriException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php', 'JsonSchema\\Exception\\JsonDecodingException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php', 'JsonSchema\\Exception\\ResourceNotFoundException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php', 'JsonSchema\\Exception\\RuntimeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php', 'JsonSchema\\Exception\\UnresolvableJsonPointerException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php', 'JsonSchema\\Exception\\UriResolverException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php', 'JsonSchema\\Exception\\ValidationException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php', 'JsonSchema\\Iterator\\ObjectIterator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php', 'JsonSchema\\Rfc3339' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php', 'JsonSchema\\SchemaStorage' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php', 'JsonSchema\\SchemaStorageInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php', 'JsonSchema\\UriResolverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php', 'JsonSchema\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php', 'JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php', 'JsonSchema\\Uri\\Retrievers\\Curl' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php', 'JsonSchema\\Uri\\Retrievers\\FileGetContents' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php', 'JsonSchema\\Uri\\Retrievers\\PredefinedArray' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php', 'JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php', 'JsonSchema\\Uri\\UriResolver' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php', 'JsonSchema\\Uri\\UriRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php', 'JsonSchema\\Validator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Validator.php', 'Mustache_Autoloader' => $vendorDir . '/mustache/mustache/src/Mustache/Autoloader.php', 'Mustache_Cache' => $vendorDir . '/mustache/mustache/src/Mustache/Cache.php', 'Mustache_Cache_AbstractCache' => $vendorDir . '/mustache/mustache/src/Mustache/Cache/AbstractCache.php', 'Mustache_Cache_FilesystemCache' => $vendorDir . '/mustache/mustache/src/Mustache/Cache/FilesystemCache.php', 'Mustache_Cache_NoopCache' => $vendorDir . '/mustache/mustache/src/Mustache/Cache/NoopCache.php', 'Mustache_Compiler' => $vendorDir . '/mustache/mustache/src/Mustache/Compiler.php', 'Mustache_Context' => $vendorDir . '/mustache/mustache/src/Mustache/Context.php', 'Mustache_Engine' => $vendorDir . '/mustache/mustache/src/Mustache/Engine.php', 'Mustache_Exception' => $vendorDir . '/mustache/mustache/src/Mustache/Exception.php', 'Mustache_Exception_InvalidArgumentException' => $vendorDir . '/mustache/mustache/src/Mustache/Exception/InvalidArgumentException.php', 'Mustache_Exception_LogicException' => $vendorDir . '/mustache/mustache/src/Mustache/Exception/LogicException.php', 'Mustache_Exception_RuntimeException' => $vendorDir . '/mustache/mustache/src/Mustache/Exception/RuntimeException.php', 'Mustache_Exception_SyntaxException' => $vendorDir . '/mustache/mustache/src/Mustache/Exception/SyntaxException.php', 'Mustache_Exception_UnknownFilterException' => $vendorDir . '/mustache/mustache/src/Mustache/Exception/UnknownFilterException.php', 'Mustache_Exception_UnknownHelperException' => $vendorDir . '/mustache/mustache/src/Mustache/Exception/UnknownHelperException.php', 'Mustache_Exception_UnknownTemplateException' => $vendorDir . '/mustache/mustache/src/Mustache/Exception/UnknownTemplateException.php', 'Mustache_HelperCollection' => $vendorDir . '/mustache/mustache/src/Mustache/HelperCollection.php', 'Mustache_LambdaHelper' => $vendorDir . '/mustache/mustache/src/Mustache/LambdaHelper.php', 'Mustache_Loader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader.php', 'Mustache_Loader_ArrayLoader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader/ArrayLoader.php', 'Mustache_Loader_CascadingLoader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader/CascadingLoader.php', 'Mustache_Loader_FilesystemLoader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader/FilesystemLoader.php', 'Mustache_Loader_InlineLoader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader/InlineLoader.php', 'Mustache_Loader_MutableLoader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader/MutableLoader.php', 'Mustache_Loader_ProductionFilesystemLoader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader/ProductionFilesystemLoader.php', 'Mustache_Loader_StringLoader' => $vendorDir . '/mustache/mustache/src/Mustache/Loader/StringLoader.php', 'Mustache_Logger' => $vendorDir . '/mustache/mustache/src/Mustache/Logger.php', 'Mustache_Logger_AbstractLogger' => $vendorDir . '/mustache/mustache/src/Mustache/Logger/AbstractLogger.php', 'Mustache_Logger_StreamLogger' => $vendorDir . '/mustache/mustache/src/Mustache/Logger/StreamLogger.php', 'Mustache_Parser' => $vendorDir . '/mustache/mustache/src/Mustache/Parser.php', 'Mustache_Source' => $vendorDir . '/mustache/mustache/src/Mustache/Source.php', 'Mustache_Source_FilesystemSource' => $vendorDir . '/mustache/mustache/src/Mustache/Source/FilesystemSource.php', 'Mustache_Template' => $vendorDir . '/mustache/mustache/src/Mustache/Template.php', 'Mustache_Tokenizer' => $vendorDir . '/mustache/mustache/src/Mustache/Tokenizer.php', 'Mustangostang\\Spyc' => $vendorDir . '/wp-cli/mustangostang-spyc/src/Spyc.php', 'Oxymel' => $vendorDir . '/nb/oxymel/Oxymel.php', 'OxymelException' => $vendorDir . '/nb/oxymel/Oxymel.php', 'OxymelTest' => $vendorDir . '/nb/oxymel/OxymelTest.php', 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', 'Requests' => $vendorDir . '/rmccue/requests/library/Requests.php', 'Requests_Auth' => $vendorDir . '/rmccue/requests/library/Requests/Auth.php', 'Requests_Auth_Basic' => $vendorDir . '/rmccue/requests/library/Requests/Auth/Basic.php', 'Requests_Cookie' => $vendorDir . '/rmccue/requests/library/Requests/Cookie.php', 'Requests_Cookie_Jar' => $vendorDir . '/rmccue/requests/library/Requests/Cookie/Jar.php', 'Requests_Exception' => $vendorDir . '/rmccue/requests/library/Requests/Exception.php', 'Requests_Exception_HTTP' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP.php', 'Requests_Exception_HTTP_304' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/304.php', 'Requests_Exception_HTTP_305' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/305.php', 'Requests_Exception_HTTP_306' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/306.php', 'Requests_Exception_HTTP_400' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/400.php', 'Requests_Exception_HTTP_401' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/401.php', 'Requests_Exception_HTTP_402' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/402.php', 'Requests_Exception_HTTP_403' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/403.php', 'Requests_Exception_HTTP_404' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/404.php', 'Requests_Exception_HTTP_405' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/405.php', 'Requests_Exception_HTTP_406' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/406.php', 'Requests_Exception_HTTP_407' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/407.php', 'Requests_Exception_HTTP_408' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/408.php', 'Requests_Exception_HTTP_409' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/409.php', 'Requests_Exception_HTTP_410' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/410.php', 'Requests_Exception_HTTP_411' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/411.php', 'Requests_Exception_HTTP_412' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/412.php', 'Requests_Exception_HTTP_413' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/413.php', 'Requests_Exception_HTTP_414' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/414.php', 'Requests_Exception_HTTP_415' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/415.php', 'Requests_Exception_HTTP_416' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/416.php', 'Requests_Exception_HTTP_417' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/417.php', 'Requests_Exception_HTTP_418' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/418.php', 'Requests_Exception_HTTP_428' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/428.php', 'Requests_Exception_HTTP_429' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/429.php', 'Requests_Exception_HTTP_431' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/431.php', 'Requests_Exception_HTTP_500' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/500.php', 'Requests_Exception_HTTP_501' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/501.php', 'Requests_Exception_HTTP_502' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/502.php', 'Requests_Exception_HTTP_503' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/503.php', 'Requests_Exception_HTTP_504' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/504.php', 'Requests_Exception_HTTP_505' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/505.php', 'Requests_Exception_HTTP_511' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/511.php', 'Requests_Exception_HTTP_Unknown' => $vendorDir . '/rmccue/requests/library/Requests/Exception/HTTP/Unknown.php', 'Requests_Exception_Transport' => $vendorDir . '/rmccue/requests/library/Requests/Exception/Transport.php', 'Requests_Exception_Transport_cURL' => $vendorDir . '/rmccue/requests/library/Requests/Exception/Transport/cURL.php', 'Requests_Hooker' => $vendorDir . '/rmccue/requests/library/Requests/Hooker.php', 'Requests_Hooks' => $vendorDir . '/rmccue/requests/library/Requests/Hooks.php', 'Requests_IDNAEncoder' => $vendorDir . '/rmccue/requests/library/Requests/IDNAEncoder.php', 'Requests_IPv6' => $vendorDir . '/rmccue/requests/library/Requests/IPv6.php', 'Requests_IRI' => $vendorDir . '/rmccue/requests/library/Requests/IRI.php', 'Requests_Proxy' => $vendorDir . '/rmccue/requests/library/Requests/Proxy.php', 'Requests_Proxy_HTTP' => $vendorDir . '/rmccue/requests/library/Requests/Proxy/HTTP.php', 'Requests_Response' => $vendorDir . '/rmccue/requests/library/Requests/Response.php', 'Requests_Response_Headers' => $vendorDir . '/rmccue/requests/library/Requests/Response/Headers.php', 'Requests_SSL' => $vendorDir . '/rmccue/requests/library/Requests/SSL.php', 'Requests_Session' => $vendorDir . '/rmccue/requests/library/Requests/Session.php', 'Requests_Transport' => $vendorDir . '/rmccue/requests/library/Requests/Transport.php', 'Requests_Transport_cURL' => $vendorDir . '/rmccue/requests/library/Requests/Transport/cURL.php', 'Requests_Transport_fsockopen' => $vendorDir . '/rmccue/requests/library/Requests/Transport/fsockopen.php', 'Requests_Utility_CaseInsensitiveDictionary' => $vendorDir . '/rmccue/requests/library/Requests/Utility/CaseInsensitiveDictionary.php', 'Requests_Utility_FilteredIterator' => $vendorDir . '/rmccue/requests/library/Requests/Utility/FilteredIterator.php', 'Seld\\CliPrompt\\CliPrompt' => $vendorDir . '/seld/cli-prompt/src/CliPrompt.php', 'Seld\\JsonLint\\DuplicateKeyException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php', 'Seld\\JsonLint\\JsonParser' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php', 'Seld\\JsonLint\\Lexer' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php', 'Seld\\JsonLint\\ParsingException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php', 'Seld\\JsonLint\\Undefined' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php', 'Seld\\PharUtils\\Timestamps' => $vendorDir . '/seld/phar-utils/src/Timestamps.php', 'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php', 'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php', 'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php', 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php', 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php', 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php', 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => $vendorDir . '/symfony/console/Event/ConsoleExceptionEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php', 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php', 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php', 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php', 'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', 'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php', 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', 'Symfony\\Component\\Console\\Helper\\DialogHelper' => $vendorDir . '/symfony/console/Helper/DialogHelper.php', 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', 'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php', 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php', 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php', 'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php', 'Symfony\\Component\\Console\\Helper\\ProgressHelper' => $vendorDir . '/symfony/console/Helper/ProgressHelper.php', 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php', 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php', 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php', 'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php', 'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php', 'Symfony\\Component\\Console\\Helper\\TableHelper' => $vendorDir . '/symfony/console/Helper/TableHelper.php', 'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php', 'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php', 'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php', 'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php', 'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php', 'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php', 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php', 'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php', 'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php', 'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php', 'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php', 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php', 'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', 'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php', 'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php', 'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php', 'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php', 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', 'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', 'Symfony\\Component\\Console\\Shell' => $vendorDir . '/symfony/console/Shell.php', 'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', 'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php', 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php', 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php', 'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php', 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', 'Symfony\\Component\\Filesystem\\LockHandler' => $vendorDir . '/symfony/filesystem/LockHandler.php', 'Symfony\\Component\\Finder\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/finder/Adapter/AbstractAdapter.php', 'Symfony\\Component\\Finder\\Adapter\\AbstractFindAdapter' => $vendorDir . '/symfony/finder/Adapter/AbstractFindAdapter.php', 'Symfony\\Component\\Finder\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/finder/Adapter/AdapterInterface.php', 'Symfony\\Component\\Finder\\Adapter\\BsdFindAdapter' => $vendorDir . '/symfony/finder/Adapter/BsdFindAdapter.php', 'Symfony\\Component\\Finder\\Adapter\\GnuFindAdapter' => $vendorDir . '/symfony/finder/Adapter/GnuFindAdapter.php', 'Symfony\\Component\\Finder\\Adapter\\PhpAdapter' => $vendorDir . '/symfony/finder/Adapter/PhpAdapter.php', 'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php', 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php', 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php', 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php', 'Symfony\\Component\\Finder\\Exception\\AdapterFailureException' => $vendorDir . '/symfony/finder/Exception/AdapterFailureException.php', 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/finder/Exception/ExceptionInterface.php', 'Symfony\\Component\\Finder\\Exception\\OperationNotPermitedException' => $vendorDir . '/symfony/finder/Exception/OperationNotPermitedException.php', 'Symfony\\Component\\Finder\\Exception\\ShellCommandFailureException' => $vendorDir . '/symfony/finder/Exception/ShellCommandFailureException.php', 'Symfony\\Component\\Finder\\Expression\\Expression' => $vendorDir . '/symfony/finder/Expression/Expression.php', 'Symfony\\Component\\Finder\\Expression\\Glob' => $vendorDir . '/symfony/finder/Expression/Glob.php', 'Symfony\\Component\\Finder\\Expression\\Regex' => $vendorDir . '/symfony/finder/Expression/Regex.php', 'Symfony\\Component\\Finder\\Expression\\ValueInterface' => $vendorDir . '/symfony/finder/Expression/ValueInterface.php', 'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php', 'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php', 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilePathsIterator' => $vendorDir . '/symfony/finder/Iterator/FilePathsIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php', 'Symfony\\Component\\Finder\\Shell\\Command' => $vendorDir . '/symfony/finder/Shell/Command.php', 'Symfony\\Component\\Finder\\Shell\\Shell' => $vendorDir . '/symfony/finder/Shell/Shell.php', 'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php', 'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php', 'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php', 'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php', 'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php', 'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php', 'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php', 'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php', 'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php', 'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php', 'Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php', 'Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php', 'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php', 'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php', 'Symfony\\Component\\Process\\ProcessBuilder' => $vendorDir . '/symfony/process/ProcessBuilder.php', 'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', 'WP_CLI' => $baseDir . '/php/class-wp-cli.php', 'WP_CLI\\AutoloadSplitter' => $baseDir . '/php/WP_CLI/AutoloadSplitter.php', 'WP_CLI\\AutoloadSplitter\\AutoloadGenerator' => $vendorDir . '/wp-cli/autoload-splitter/src/AutoloadGenerator.php', 'WP_CLI\\AutoloadSplitter\\ComposerPlugin' => $vendorDir . '/wp-cli/autoload-splitter/src/ComposerPlugin.php', 'WP_CLI\\Autoloader' => $baseDir . '/php/WP_CLI/Autoloader.php', 'WP_CLI\\Bootstrap\\AutoloaderStep' => $baseDir . '/php/WP_CLI/Bootstrap/AutoloaderStep.php', 'WP_CLI\\Bootstrap\\BootstrapState' => $baseDir . '/php/WP_CLI/Bootstrap/BootstrapState.php', 'WP_CLI\\Bootstrap\\BootstrapStep' => $baseDir . '/php/WP_CLI/Bootstrap/BootstrapStep.php', 'WP_CLI\\Bootstrap\\ConfigureRunner' => $baseDir . '/php/WP_CLI/Bootstrap/ConfigureRunner.php', 'WP_CLI\\Bootstrap\\DeclareAbstractBaseCommand' => $baseDir . '/php/WP_CLI/Bootstrap/DeclareAbstractBaseCommand.php', 'WP_CLI\\Bootstrap\\DeclareMainClass' => $baseDir . '/php/WP_CLI/Bootstrap/DeclareMainClass.php', 'WP_CLI\\Bootstrap\\DefineProtectedCommands' => $baseDir . '/php/WP_CLI/Bootstrap/DefineProtectedCommands.php', 'WP_CLI\\Bootstrap\\IncludeBundledAutoloader' => $baseDir . '/php/WP_CLI/Bootstrap/IncludeBundledAutoloader.php', 'WP_CLI\\Bootstrap\\IncludeFallbackAutoloader' => $baseDir . '/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php', 'WP_CLI\\Bootstrap\\IncludeFrameworkAutoloader' => $baseDir . '/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php', 'WP_CLI\\Bootstrap\\IncludePackageAutoloader' => $baseDir . '/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php', 'WP_CLI\\Bootstrap\\InitializeColorization' => $baseDir . '/php/WP_CLI/Bootstrap/InitializeColorization.php', 'WP_CLI\\Bootstrap\\InitializeLogger' => $baseDir . '/php/WP_CLI/Bootstrap/InitializeLogger.php', 'WP_CLI\\Bootstrap\\LaunchRunner' => $baseDir . '/php/WP_CLI/Bootstrap/LaunchRunner.php', 'WP_CLI\\Bootstrap\\LoadDispatcher' => $baseDir . '/php/WP_CLI/Bootstrap/LoadDispatcher.php', 'WP_CLI\\Bootstrap\\LoadRequiredCommand' => $baseDir . '/php/WP_CLI/Bootstrap/LoadRequiredCommand.php', 'WP_CLI\\Bootstrap\\LoadUtilityFunctions' => $baseDir . '/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php', 'WP_CLI\\Bootstrap\\RegisterDeferredCommands' => $baseDir . '/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php', 'WP_CLI\\Bootstrap\\RegisterFrameworkCommands' => $baseDir . '/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.php', 'WP_CLI\\Bootstrap\\RunnerInstance' => $baseDir . '/php/WP_CLI/Bootstrap/RunnerInstance.php', 'WP_CLI\\Completions' => $baseDir . '/php/WP_CLI/Completions.php', 'WP_CLI\\ComposerIO' => $baseDir . '/php/WP_CLI/ComposerIO.php', 'WP_CLI\\Configurator' => $baseDir . '/php/WP_CLI/Configurator.php', 'WP_CLI\\Dispatcher\\CommandAddition' => $baseDir . '/php/WP_CLI/Dispatcher/CommandAddition.php', 'WP_CLI\\Dispatcher\\CommandFactory' => $baseDir . '/php/WP_CLI/Dispatcher/CommandFactory.php', 'WP_CLI\\Dispatcher\\CompositeCommand' => $baseDir . '/php/WP_CLI/Dispatcher/CompositeCommand.php', 'WP_CLI\\Dispatcher\\RootCommand' => $baseDir . '/php/WP_CLI/Dispatcher/RootCommand.php', 'WP_CLI\\Dispatcher\\Subcommand' => $baseDir . '/php/WP_CLI/Dispatcher/Subcommand.php', 'WP_CLI\\DocParser' => $baseDir . '/php/WP_CLI/DocParser.php', 'WP_CLI\\ExitException' => $baseDir . '/php/WP_CLI/ExitException.php', 'WP_CLI\\Extractor' => $baseDir . '/php/WP_CLI/Extractor.php', 'WP_CLI\\Fetchers\\Base' => $baseDir . '/php/WP_CLI/Fetchers/Base.php', 'WP_CLI\\FileCache' => $baseDir . '/php/WP_CLI/FileCache.php', 'WP_CLI\\Formatter' => $baseDir . '/php/WP_CLI/Formatter.php', 'WP_CLI\\Iterators\\CSV' => $baseDir . '/php/WP_CLI/Iterators/CSV.php', 'WP_CLI\\Iterators\\Exception' => $baseDir . '/php/WP_CLI/Iterators/Exception.php', 'WP_CLI\\Iterators\\Query' => $baseDir . '/php/WP_CLI/Iterators/Query.php', 'WP_CLI\\Iterators\\Table' => $baseDir . '/php/WP_CLI/Iterators/Table.php', 'WP_CLI\\Iterators\\Transform' => $baseDir . '/php/WP_CLI/Iterators/Transform.php', 'WP_CLI\\Loggers\\Base' => $baseDir . '/php/WP_CLI/Loggers/Base.php', 'WP_CLI\\Loggers\\Execution' => $baseDir . '/php/WP_CLI/Loggers/Execution.php', 'WP_CLI\\Loggers\\Quiet' => $baseDir . '/php/WP_CLI/Loggers/Quiet.php', 'WP_CLI\\Loggers\\Regular' => $baseDir . '/php/WP_CLI/Loggers/Regular.php', 'WP_CLI\\NoOp' => $baseDir . '/php/WP_CLI/NoOp.php', 'WP_CLI\\PackageManagerEventSubscriber' => $baseDir . '/php/WP_CLI/PackageManagerEventSubscriber.php', 'WP_CLI\\Process' => $baseDir . '/php/WP_CLI/Process.php', 'WP_CLI\\ProcessRun' => $baseDir . '/php/WP_CLI/ProcessRun.php', 'WP_CLI\\Runner' => $baseDir . '/php/WP_CLI/Runner.php', 'WP_CLI\\SynopsisParser' => $baseDir . '/php/WP_CLI/SynopsisParser.php', 'WP_CLI\\SynopsisValidator' => $baseDir . '/php/WP_CLI/SynopsisValidator.php', 'WP_CLI\\UpgraderSkin' => $baseDir . '/php/WP_CLI/UpgraderSkin.php', 'WP_CLI\\WpHttpCacheManager' => $baseDir . '/php/WP_CLI/WpHttpCacheManager.php', 'WP_CLI_Command' => $baseDir . '/php/class-wp-cli-command.php', 'cli\\Arguments' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Arguments.php', 'cli\\Colors' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Colors.php', 'cli\\Memoize' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Memoize.php', 'cli\\Notify' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Notify.php', 'cli\\Progress' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Progress.php', 'cli\\Shell' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Shell.php', 'cli\\Streams' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Streams.php', 'cli\\Table' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Table.php', 'cli\\Tree' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/Tree.php', 'cli\\arguments\\Argument' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/arguments/Argument.php', 'cli\\arguments\\HelpScreen' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/arguments/HelpScreen.php', 'cli\\arguments\\InvalidArguments' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/arguments/InvalidArguments.php', 'cli\\arguments\\Lexer' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/arguments/Lexer.php', 'cli\\notify\\Dots' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/notify/Dots.php', 'cli\\notify\\Spinner' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/notify/Spinner.php', 'cli\\progress\\Bar' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/progress/Bar.php', 'cli\\table\\Ascii' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/table/Ascii.php', 'cli\\table\\Renderer' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/table/Renderer.php', 'cli\\table\\Tabular' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/table/Tabular.php', 'cli\\tree\\Ascii' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/tree/Ascii.php', 'cli\\tree\\Markdown' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/tree/Markdown.php', 'cli\\tree\\Renderer' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/tree/Renderer.php', ); addClassMap($classMap); } $loader->register(true); return $loader; } } $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '8cd2fca4db21bffce1ad0612f7caeec4' => $vendorDir . '/ramsey/array_column/src/array_column.php', 'b66d29757fcb2fb7a9608d068e3716b0' => $vendorDir . '/wp-cli/checksum-command/checksum-command.php', '5deaf6ce9c8bbdfb65104c7e938d5875' => $vendorDir . '/wp-cli/config-command/config-command.php', '7654e00bf0e632580764400bd8293a9c' => $vendorDir . '/wp-cli/cron-command/cron-command.php', 'f958dca3f412fd7975da1700912a9321' => $vendorDir . '/wp-cli/eval-command/eval-command.php', '5c6ec5cff8f9d625772c8ed147f6b894' => $vendorDir . '/wp-cli/export-command/export-command.php', '30cbb6e4122dc988e494c6b9c0438233' => $vendorDir . '/wp-cli/import-command/import-command.php', 'ace0d205db7f4135ec32132a0076d555' => $vendorDir . '/wp-cli/language-command/language-command.php', '3937806105cc8e221b8fa8db5b70d2f2' => $vendorDir . '/wp-cli/mustangostang-spyc/includes/functions.php', 'f399c1c8d0c787d5c94c09884cdd9762' => $vendorDir . '/wp-cli/rewrite-command/rewrite-command.php', '080fadd667195d055c5a23386f270261' => $vendorDir . '/wp-cli/role-command/role-command.php', '9f04dd0aa5d67ec75a75c88c345a079e' => $vendorDir . '/wp-cli/server-command/server-command.php', '129d58fa8151374aceb8571bcaa97504' => $vendorDir . '/wp-cli/shell-command/shell-command.php', '8519779bbb65eeb842af2f629ce7b6f8' => $vendorDir . '/wp-cli/super-admin-command/super-admin-command.php', '1f05372afcc7d0c51a305cef1d56dd01' => $vendorDir . '/wp-cli/widget-command/widget-command.php', '8a0ad02df6a5087f2c380f8fd52db273' => $vendorDir . '/wp-cli/cache-command/cache-command.php', '5e099d3cac677dd2bec1003ea7707745' => $vendorDir . '/wp-cli/media-command/media-command.php', 'be01b9b16925dcb22165c40b46681ac6' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/cli.php', '68c39b88215b6cf7a0da164166670ef9' => $vendorDir . '/wp-cli/core-command/core-command.php', 'f3f0199a3ecd9f501d0a3b361bd2f61c' => $vendorDir . '/wp-cli/entity-command/entity-command.php', 'ba366f96f4fddbdef61ad7a862b44f61' => $vendorDir . '/wp-cli/package-command/package-command.php', 'c65f753375faee349b7adc48c2ee7cc2' => $vendorDir . '/wp-cli/db-command/db-command.php', '3f201033d5aceb2293314273be88f7c6' => $vendorDir . '/wp-cli/extension-command/extension-command.php', 'd979c11fe80ba96ae3037b43429fe546' => $vendorDir . '/wp-cli/scaffold-command/scaffold-command.php', '8ecb13f8bbc22b1b34d12b14ec01077a' => $vendorDir . '/wp-cli/search-replace-command/search-replace-command.php', ); addClassMap($classMap); } $loader->register(true); return $loader; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; @trigger_error('The '.__NAMESPACE__.'\GnuFindAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); use Symfony\Component\Finder\Shell\Shell; use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Iterator\SortableIterator; use Symfony\Component\Finder\Expression\Expression; /** * Shell engine implementation using GNU find command. * * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. */ class GnuFindAdapter extends AbstractFindAdapter { /** * {@inheritdoc} */ public function getName() { return 'gnu_find'; } /** * {@inheritdoc} */ protected function buildFormatSorting(Command $command, $sort) { switch ($sort) { case SortableIterator::SORT_BY_NAME: $command->ins('sort')->add('| sort'); return; case SortableIterator::SORT_BY_TYPE: $format = '%y'; break; case SortableIterator::SORT_BY_ACCESSED_TIME: $format = '%A@'; break; case SortableIterator::SORT_BY_CHANGED_TIME: $format = '%C@'; break; case SortableIterator::SORT_BY_MODIFIED_TIME: $format = '%T@'; break; default: throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); } $command ->get('find') ->add('-printf') ->arg($format.' %h/%f\\n') ->add('| sort | cut') ->arg('-d ') ->arg('-f2-') ; } /** * {@inheritdoc} */ protected function canBeUsed() { return Shell::TYPE_UNIX === $this->shell->getType() && parent::canBeUsed(); } /** * {@inheritdoc} */ protected function buildFindCommand(Command $command, $dir) { return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended'); } /** * {@inheritdoc} */ protected function buildContentFiltering(Command $command, array $contains, $not = false) { foreach ($contains as $contain) { $expr = Expression::create($contain); // todo: avoid forking process for each $pattern by using multiple -e options $command ->add('| xargs -I{} -r grep -I') ->add($expr->isCaseSensitive() ? null : '-i') ->add($not ? '-L' : '-l') ->add('-Ee')->arg($expr->renderPattern()) ->add('{}') ; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; @trigger_error('The '.__NAMESPACE__.'\AbstractAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); /** * Interface for finder engine implementations. * * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. */ abstract class AbstractAdapter implements AdapterInterface { protected $followLinks = false; protected $mode = 0; protected $minDepth = 0; protected $maxDepth = PHP_INT_MAX; protected $exclude = array(); protected $names = array(); protected $notNames = array(); protected $contains = array(); protected $notContains = array(); protected $sizes = array(); protected $dates = array(); protected $filters = array(); protected $sort = false; protected $paths = array(); protected $notPaths = array(); protected $ignoreUnreadableDirs = false; private static $areSupported = array(); /** * {@inheritdoc} */ public function isSupported() { $name = $this->getName(); if (!array_key_exists($name, self::$areSupported)) { self::$areSupported[$name] = $this->canBeUsed(); } return self::$areSupported[$name]; } /** * {@inheritdoc} */ public function setFollowLinks($followLinks) { $this->followLinks = $followLinks; return $this; } /** * {@inheritdoc} */ public function setMode($mode) { $this->mode = $mode; return $this; } /** * {@inheritdoc} */ public function setDepths(array $depths) { $this->minDepth = 0; $this->maxDepth = PHP_INT_MAX; foreach ($depths as $comparator) { switch ($comparator->getOperator()) { case '>': $this->minDepth = $comparator->getTarget() + 1; break; case '>=': $this->minDepth = $comparator->getTarget(); break; case '<': $this->maxDepth = $comparator->getTarget() - 1; break; case '<=': $this->maxDepth = $comparator->getTarget(); break; default: $this->minDepth = $this->maxDepth = $comparator->getTarget(); } } return $this; } /** * {@inheritdoc} */ public function setExclude(array $exclude) { $this->exclude = $exclude; return $this; } /** * {@inheritdoc} */ public function setNames(array $names) { $this->names = $names; return $this; } /** * {@inheritdoc} */ public function setNotNames(array $notNames) { $this->notNames = $notNames; return $this; } /** * {@inheritdoc} */ public function setContains(array $contains) { $this->contains = $contains; return $this; } /** * {@inheritdoc} */ public function setNotContains(array $notContains) { $this->notContains = $notContains; return $this; } /** * {@inheritdoc} */ public function setSizes(array $sizes) { $this->sizes = $sizes; return $this; } /** * {@inheritdoc} */ public function setDates(array $dates) { $this->dates = $dates; return $this; } /** * {@inheritdoc} */ public function setFilters(array $filters) { $this->filters = $filters; return $this; } /** * {@inheritdoc} */ public function setSort($sort) { $this->sort = $sort; return $this; } /** * {@inheritdoc} */ public function setPath(array $paths) { $this->paths = $paths; return $this; } /** * {@inheritdoc} */ public function setNotPath(array $notPaths) { $this->notPaths = $notPaths; return $this; } /** * {@inheritdoc} */ public function ignoreUnreadableDirs($ignore = true) { $this->ignoreUnreadableDirs = (bool) $ignore; return $this; } /** * Returns whether the adapter is supported in the current environment. * * This method should be implemented in all adapters. Do not implement * isSupported in the adapters as the generic implementation provides a cache * layer. * * @see isSupported() * * @return bool Whether the adapter is supported */ abstract protected function canBeUsed(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; /** * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. */ interface AdapterInterface { /** * @param bool $followLinks * * @return $this */ public function setFollowLinks($followLinks); /** * @param int $mode * * @return $this */ public function setMode($mode); /** * @param array $exclude * * @return $this */ public function setExclude(array $exclude); /** * @param array $depths * * @return $this */ public function setDepths(array $depths); /** * @param array $names * * @return $this */ public function setNames(array $names); /** * @param array $notNames * * @return $this */ public function setNotNames(array $notNames); /** * @param array $contains * * @return $this */ public function setContains(array $contains); /** * @param array $notContains * * @return $this */ public function setNotContains(array $notContains); /** * @param array $sizes * * @return $this */ public function setSizes(array $sizes); /** * @param array $dates * * @return $this */ public function setDates(array $dates); /** * @param array $filters * * @return $this */ public function setFilters(array $filters); /** * @param \Closure|int $sort * * @return $this */ public function setSort($sort); /** * @param array $paths * * @return $this */ public function setPath(array $paths); /** * @param array $notPaths * * @return $this */ public function setNotPath(array $notPaths); /** * @param bool $ignore * * @return $this */ public function ignoreUnreadableDirs($ignore = true); /** * @param string $dir * * @return \Iterator Result iterator */ public function searchInDirectory($dir); /** * Tests adapter support for current platform. * * @return bool */ public function isSupported(); /** * Returns adapter name. * * @return string */ public function getName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; @trigger_error('The '.__NAMESPACE__.'\PhpAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); use Symfony\Component\Finder\Iterator; /** * PHP finder engine implementation. * * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. */ class PhpAdapter extends AbstractAdapter { /** * {@inheritdoc} */ public function searchInDirectory($dir) { $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 ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) { $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->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; } /** * {@inheritdoc} */ public function getName() { return 'php'; } /** * {@inheritdoc} */ protected function canBeUsed() { return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; @trigger_error('The '.__NAMESPACE__.'\AbstractFindAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); use Symfony\Component\Finder\Exception\AccessDeniedException; use Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Shell\Shell; use Symfony\Component\Finder\Expression\Expression; use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Comparator\NumberComparator; use Symfony\Component\Finder\Comparator\DateComparator; /** * Shell engine implementation using GNU find command. * * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. */ abstract class AbstractFindAdapter extends AbstractAdapter { /** * @var Shell */ protected $shell; public function __construct() { $this->shell = new Shell(); } /** * {@inheritdoc} */ public function searchInDirectory($dir) { // having "/../" in path make find fail $dir = realpath($dir); // searching directories containing or not containing strings leads to no result if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) { return new Iterator\FilePathsIterator(array(), $dir); } $command = Command::create(); $find = $this->buildFindCommand($command, $dir); if ($this->followLinks) { $find->add('-follow'); } $find->add('-mindepth')->add($this->minDepth + 1); if (PHP_INT_MAX !== $this->maxDepth) { $find->add('-maxdepth')->add($this->maxDepth + 1); } if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) { $find->add('-type d'); } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) { $find->add('-type f'); } $this->buildNamesFiltering($find, $this->names); $this->buildNamesFiltering($find, $this->notNames, true); $this->buildPathsFiltering($find, $dir, $this->paths); $this->buildPathsFiltering($find, $dir, $this->notPaths, true); $this->buildSizesFiltering($find, $this->sizes); $this->buildDatesFiltering($find, $this->dates); $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs'); $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut'); if ($useGrep && ($this->contains || $this->notContains)) { $grep = $command->ins('grep'); $this->buildContentFiltering($grep, $this->contains); $this->buildContentFiltering($grep, $this->notContains, true); } if ($useSort) { $this->buildSorting($command, $this->sort); } $command->setErrorHandler( $this->ignoreUnreadableDirs // If directory is unreadable and finder is set to ignore it, `stderr` is ignored. ? function ($stderr) { } : function ($stderr) { throw new AccessDeniedException($stderr); } ); $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute()); $iterator = new Iterator\FilePathsIterator($paths, $dir); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); } if (!$useGrep && ($this->contains || $this->notContains)) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->filters) { $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } if (!$useSort && $this->sort) { $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); $iterator = $iteratorAggregate->getIterator(); } return $iterator; } /** * {@inheritdoc} */ protected function canBeUsed() { return $this->shell->testCommand('find'); } /** * @param Command $command * @param string $dir * * @return Command */ protected function buildFindCommand(Command $command, $dir) { return $command ->ins('find') ->add('find ') ->arg($dir) ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions } /** * @param Command $command * @param string[] $names * @param bool $not */ private function buildNamesFiltering(Command $command, array $names, $not = false) { if (0 === count($names)) { return; } $command->add($not ? '-not' : null)->cmd('('); foreach ($names as $i => $name) { $expr = Expression::create($name); // Find does not support expandable globs ("*.{a,b}" syntax). if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { $expr = Expression::create($expr->getGlob()->toRegex(false)); } // Fixes 'not search' and 'full path matching' regex problems. // - Jokers '.' are replaced by [^/]. // - We add '[^/]*' before and after regex (if no ^|$ flags are present). if ($expr->isRegex()) { $regex = $expr->getRegex(); $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*') ->setStartFlag(false) ->setStartJoker(true) ->replaceJokers('[^/]'); if (!$regex->hasEndFlag() || $regex->hasEndJoker()) { $regex->setEndJoker(false)->append('[^/]*'); } } $command ->add($i > 0 ? '-or' : null) ->add($expr->isRegex() ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') : ($expr->isCaseSensitive() ? '-name' : '-iname') ) ->arg($expr->renderPattern()); } $command->cmd(')'); } /** * @param Command $command * @param string $dir * @param string[] $paths * @param bool $not */ private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false) { if (0 === count($paths)) { return; } $command->add($not ? '-not' : null)->cmd('('); foreach ($paths as $i => $path) { $expr = Expression::create($path); // Find does not support expandable globs ("*.{a,b}" syntax). if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { $expr = Expression::create($expr->getGlob()->toRegex(false)); } // Fixes 'not search' regex problems. if ($expr->isRegex()) { $regex = $expr->getRegex(); $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag()); } else { $expr->prepend('*')->append('*'); } $command ->add($i > 0 ? '-or' : null) ->add($expr->isRegex() ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') : ($expr->isCaseSensitive() ? '-path' : '-ipath') ) ->arg($expr->renderPattern()); } $command->cmd(')'); } /** * @param Command $command * @param NumberComparator[] $sizes */ private function buildSizesFiltering(Command $command, array $sizes) { foreach ($sizes as $i => $size) { $command->add($i > 0 ? '-and' : null); switch ($size->getOperator()) { case '<=': $command->add('-size -'.($size->getTarget() + 1).'c'); break; case '>=': $command->add('-size +'.($size->getTarget() - 1).'c'); break; case '>': $command->add('-size +'.$size->getTarget().'c'); break; case '!=': $command->add('-size -'.$size->getTarget().'c'); $command->add('-size +'.$size->getTarget().'c'); break; case '<': default: $command->add('-size -'.$size->getTarget().'c'); } } } /** * @param Command $command * @param DateComparator[] $dates */ private function buildDatesFiltering(Command $command, array $dates) { foreach ($dates as $i => $date) { $command->add($i > 0 ? '-and' : null); $mins = (int) round((time() - $date->getTarget()) / 60); if (0 > $mins) { // mtime is in the future $command->add(' -mmin -0'); // we will have no result so we don't need to continue return; } switch ($date->getOperator()) { case '<=': $command->add('-mmin +'.($mins - 1)); break; case '>=': $command->add('-mmin -'.($mins + 1)); break; case '>': $command->add('-mmin -'.$mins); break; case '!=': $command->add('-mmin +'.$mins.' -or -mmin -'.$mins); break; case '<': default: $command->add('-mmin +'.$mins); } } } /** * @param Command $command * @param string $sort * * @throws \InvalidArgumentException */ private function buildSorting(Command $command, $sort) { $this->buildFormatSorting($command, $sort); } /** * @param Command $command * @param string $sort */ abstract protected function buildFormatSorting(Command $command, $sort); /** * @param Command $command * @param array $contains * @param bool $not */ abstract protected function buildContentFiltering(Command $command, array $contains, $not = false); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; @trigger_error('The '.__NAMESPACE__.'\BsdFindAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); use Symfony\Component\Finder\Shell\Shell; use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Iterator\SortableIterator; use Symfony\Component\Finder\Expression\Expression; /** * Shell engine implementation using BSD find command. * * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. */ class BsdFindAdapter extends AbstractFindAdapter { /** * {@inheritdoc} */ public function getName() { return 'bsd_find'; } /** * {@inheritdoc} */ protected function canBeUsed() { return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed(); } /** * {@inheritdoc} */ protected function buildFormatSorting(Command $command, $sort) { switch ($sort) { case SortableIterator::SORT_BY_NAME: $command->ins('sort')->add('| sort'); return; case SortableIterator::SORT_BY_TYPE: $format = '%HT'; break; case SortableIterator::SORT_BY_ACCESSED_TIME: $format = '%a'; break; case SortableIterator::SORT_BY_CHANGED_TIME: $format = '%c'; break; case SortableIterator::SORT_BY_MODIFIED_TIME: $format = '%m'; break; default: throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); } $command ->add('-print0 | xargs -0 stat -f') ->arg($format.'%t%N') ->add('| sort | cut -f 2'); } /** * {@inheritdoc} */ protected function buildFindCommand(Command $command, $dir) { parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1); return $command; } /** * {@inheritdoc} */ protected function buildContentFiltering(Command $command, array $contains, $not = false) { foreach ($contains as $contain) { $expr = Expression::create($contain); // todo: avoid forking process for each $pattern by using multiple -e options $command ->add('| grep -v \'^$\'') ->add('| xargs -I{} grep -I') ->add($expr->isCaseSensitive() ? null : '-i') ->add($not ? '-L' : '-l') ->add('-Ee')->arg($expr->renderPattern()) ->add('{}') ; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder; use Symfony\Component\Finder\Adapter\AdapterInterface; use Symfony\Component\Finder\Adapter\GnuFindAdapter; use Symfony\Component\Finder\Adapter\BsdFindAdapter; use Symfony\Component\Finder\Adapter\PhpAdapter; use Symfony\Component\Finder\Comparator\DateComparator; use Symfony\Component\Finder\Comparator\NumberComparator; use Symfony\Component\Finder\Exception\ExceptionInterface; use Symfony\Component\Finder\Iterator\CustomFilterIterator; use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; use Symfony\Component\Finder\Iterator\FilenameFilterIterator; use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; use Symfony\Component\Finder\Iterator\SortableIterator; /** * Finder allows to build rules to find files and directories. * * It is a thin wrapper around several specialized iterator classes. * * All rules may be invoked several times. * * All methods return the current Finder object to allow easy chaining: * * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); * * @author Fabien Potencier */ class Finder implements \IteratorAggregate, \Countable { const IGNORE_VCS_FILES = 1; const IGNORE_DOT_FILES = 2; private $mode = 0; private $names = array(); private $notNames = array(); private $exclude = array(); private $filters = array(); private $depths = array(); private $sizes = array(); private $followLinks = false; private $sort = false; private $ignore = 0; private $dirs = array(); private $dates = array(); private $iterators = array(); private $contains = array(); private $notContains = array(); private $adapters = null; private $paths = array(); private $notPaths = array(); private $ignoreUnreadableDirs = false; private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); public function __construct() { $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; } /** * Creates a new Finder. * * @return static */ public static function create() { return new static(); } /** * Registers a finder engine implementation. * * @param AdapterInterface $adapter An adapter instance * @param int $priority Highest is selected first * * @return $this * * @deprecated since 2.8, to be removed in 3.0. */ public function addAdapter(AdapterInterface $adapter, $priority = 0) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); $this->initDefaultAdapters(); $this->adapters[$adapter->getName()] = array( 'adapter' => $adapter, 'priority' => $priority, 'selected' => false, ); return $this->sortAdapters(); } /** * Sets the selected adapter to the best one according to the current platform the code is run on. * * @return $this * * @deprecated since 2.8, to be removed in 3.0. */ public function useBestAdapter() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); $this->initDefaultAdapters(); $this->resetAdapterSelection(); return $this->sortAdapters(); } /** * Selects the adapter to use. * * @param string $name * * @return $this * * @throws \InvalidArgumentException * * @deprecated since 2.8, to be removed in 3.0. */ public function setAdapter($name) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); $this->initDefaultAdapters(); if (!isset($this->adapters[$name])) { throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name)); } $this->resetAdapterSelection(); $this->adapters[$name]['selected'] = true; return $this->sortAdapters(); } /** * Removes all adapters registered in the finder. * * @return $this * * @deprecated since 2.8, to be removed in 3.0. */ public function removeAdapters() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); $this->adapters = array(); return $this; } /** * Returns registered adapters ordered by priority without extra information. * * @return AdapterInterface[] * * @deprecated since 2.8, to be removed in 3.0. */ public function getAdapters() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); $this->initDefaultAdapters(); return array_values(array_map(function (array $adapter) { return $adapter['adapter']; }, $this->adapters)); } /** * Restricts the matching to directories only. * * @return $this */ public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; return $this; } /** * Restricts the matching to files only. * * @return $this */ public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } /** * Adds tests for the directory depth. * * Usage: * * $finder->depth('> 1') // the Finder will start matching at level 1. * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. * * @param string|int $level The depth level expression * * @return $this * * @see DepthRangeFilterIterator * @see NumberComparator */ public function depth($level) { $this->depths[] = new Comparator\NumberComparator($level); return $this; } /** * Adds tests for file dates (last modified). * * The date must be something that strtotime() is able to parse: * * $finder->date('since yesterday'); * $finder->date('until 2 days ago'); * $finder->date('> now - 2 hours'); * $finder->date('>= 2005-10-15'); * * @param string $date A date range string * * @return $this * * @see strtotime * @see DateRangeFilterIterator * @see DateComparator */ public function date($date) { $this->dates[] = new Comparator\DateComparator($date); return $this; } /** * Adds rules that files must match. * * You can use patterns (delimited with / sign), globs or simple strings. * * $finder->name('*.php') * $finder->name('/\.php$/') // same as above * $finder->name('test.php') * * @param string $pattern A pattern (a regexp, a glob, or a string) * * @return $this * * @see FilenameFilterIterator */ public function name($pattern) { $this->names[] = $pattern; return $this; } /** * Adds rules that files must not match. * * @param string $pattern A pattern (a regexp, a glob, or a string) * * @return $this * * @see FilenameFilterIterator */ public function notName($pattern) { $this->notNames[] = $pattern; return $this; } /** * Adds tests that file contents must match. * * Strings or PCRE patterns can be used: * * $finder->contains('Lorem ipsum') * $finder->contains('/Lorem ipsum/i') * * @param string $pattern A pattern (string or regexp) * * @return $this * * @see FilecontentFilterIterator */ public function contains($pattern) { $this->contains[] = $pattern; return $this; } /** * Adds tests that file contents must not match. * * Strings or PCRE patterns can be used: * * $finder->notContains('Lorem ipsum') * $finder->notContains('/Lorem ipsum/i') * * @param string $pattern A pattern (string or regexp) * * @return $this * * @see FilecontentFilterIterator */ public function notContains($pattern) { $this->notContains[] = $pattern; return $this; } /** * Adds rules that filenames must match. * * You can use patterns (delimited with / sign) or simple strings. * * $finder->path('some/special/dir') * $finder->path('/some\/special\/dir/') // same as above * * Use only / as dirname separator. * * @param string $pattern A pattern (a regexp or a string) * * @return $this * * @see FilenameFilterIterator */ public function path($pattern) { $this->paths[] = $pattern; return $this; } /** * Adds rules that filenames must not match. * * You can use patterns (delimited with / sign) or simple strings. * * $finder->notPath('some/special/dir') * $finder->notPath('/some\/special\/dir/') // same as above * * Use only / as dirname separator. * * @param string $pattern A pattern (a regexp or a string) * * @return $this * * @see FilenameFilterIterator */ public function notPath($pattern) { $this->notPaths[] = $pattern; return $this; } /** * Adds tests for file sizes. * * $finder->size('> 10K'); * $finder->size('<= 1Ki'); * $finder->size(4); * * @param string|int $size A size range string or an integer * * @return $this * * @see SizeRangeFilterIterator * @see NumberComparator */ public function size($size) { $this->sizes[] = new Comparator\NumberComparator($size); return $this; } /** * Excludes directories. * * @param string|array $dirs A directory path or an array of directories * * @return $this * * @see ExcludeDirectoryFilterIterator */ public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); return $this; } /** * Excludes "hidden" directories and files (starting with a dot). * * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not * * @return $this * * @see ExcludeDirectoryFilterIterator */ public function ignoreDotFiles($ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; } else { $this->ignore &= ~static::IGNORE_DOT_FILES; } return $this; } /** * Forces the finder to ignore version control directories. * * @param bool $ignoreVCS Whether to exclude VCS files or not * * @return $this * * @see ExcludeDirectoryFilterIterator */ public function ignoreVCS($ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_FILES; } return $this; } /** * Adds VCS patterns. * * @see ignoreVCS() * * @param string|string[] $pattern VCS patterns to ignore */ public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; } self::$vcsPatterns = array_unique(self::$vcsPatterns); } /** * Sorts files and directories by an anonymous function. * * The anonymous function receives two \SplFileInfo instances to compare. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @param \Closure $closure An anonymous function * * @return $this * * @see SortableIterator */ public function sort(\Closure $closure) { $this->sort = $closure; return $this; } /** * Sorts files and directories by name. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByName() { $this->sort = Iterator\SortableIterator::SORT_BY_NAME; return $this; } /** * Sorts files and directories by type (directories before files), then by name. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } /** * Sorts files and directories by the last accessed time. * * This is the time that the file was last accessed, read or written to. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } /** * Sorts files and directories by the last inode changed time. * * This is the time that the inode information was last modified (permissions, owner, group or other metadata). * * On Windows, since inode is not available, changed time is actually the file creation time. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } /** * Sorts files and directories by the last modified time. * * This is the last time the actual contents of the file were last modified. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } /** * Filters the iterator with an anonymous function. * * The anonymous function receives a \SplFileInfo and must return false * to remove files. * * @param \Closure $closure An anonymous function * * @return $this * * @see CustomFilterIterator */ public function filter(\Closure $closure) { $this->filters[] = $closure; return $this; } /** * Forces the following of symlinks. * * @return $this */ public function followLinks() { $this->followLinks = true; return $this; } /** * Tells finder to ignore unreadable directories. * * By default, scanning unreadable directories content throws an AccessDeniedException. * * @param bool $ignore * * @return $this */ public function ignoreUnreadableDirs($ignore = true) { $this->ignoreUnreadableDirs = (bool) $ignore; return $this; } /** * Searches files and directories which match defined rules. * * @param string|array $dirs A directory path or an array of directories * * @return $this * * @throws \InvalidArgumentException if one of the directories does not exist */ 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; } /** * Returns an Iterator for the current Finder configuration. * * This method implements the IteratorAggregate interface. * * @return \Iterator|SplFileInfo[] An iterator * * @throws \LogicException if the in() method has not been called */ 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; } /** * Appends an existing set of files/directories to the finder. * * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. * * @param mixed $iterator * * @return $this * * @throws \InvalidArgumentException when the given argument is not iterable */ 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; } /** * Counts all the results collected by the iterators. * * @return int */ public function count() { return iterator_count($this->getIterator()); } /** * @return $this */ private function sortAdapters() { uasort($this->adapters, function (array $a, array $b) { if ($a['selected'] || $b['selected']) { return $a['selected'] ? -1 : 1; } return $a['priority'] > $b['priority'] ? -1 : 1; }); return $this; } /** * @param $dir * * @return \Iterator */ 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[] = '#(^|/)\..+(/|$)#'; } if ($this->adapters) { foreach ($this->adapters as $adapter) { if ($adapter['adapter']->isSupported()) { try { return $this ->buildAdapter($adapter['adapter']) ->searchInDirectory($dir); } catch (ExceptionInterface $e) { } } } } $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; } /** * @param AdapterInterface $adapter * * @return AdapterInterface */ private function buildAdapter(AdapterInterface $adapter) { return $adapter ->setFollowLinks($this->followLinks) ->setDepths($this->depths) ->setMode($this->mode) ->setExclude($this->exclude) ->setNames($this->names) ->setNotNames($this->notNames) ->setContains($this->contains) ->setNotContains($this->notContains) ->setSizes($this->sizes) ->setDates($this->dates) ->setFilters($this->filters) ->setSort($this->sort) ->setPath($this->paths) ->setNotPath($this->notPaths) ->ignoreUnreadableDirs($this->ignoreUnreadableDirs); } /** * Unselects all adapters. */ private function resetAdapterSelection() { $this->adapters = array_map(function (array $properties) { $properties['selected'] = false; return $properties; }, $this->adapters); } private function initDefaultAdapters() { if (null === $this->adapters) { $this->adapters = array(); $this ->addAdapter(new GnuFindAdapter()) ->addAdapter(new BsdFindAdapter()) ->addAdapter(new PhpAdapter(), -50) ->setAdapter('php') ; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Glob; /** * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). * * @author Fabien Potencier */ class FilenameFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { return $this->isAccepted($this->current()->getFilename()); } /** * Converts glob to regexp. * * PCRE patterns are left unchanged. * Glob strings are transformed with Glob::toRegex(). * * @param string $str Pattern: glob or regexp * * @return string regexp corresponding to a given glob or regexp */ protected function toRegex($str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * This iterator just overrides the rewind method in order to correct a PHP bug, * which existed before version 5.5.23/5.6.7. * * @see https://bugs.php.net/68557 * * @author Alex Bogomazov */ abstract class FilterIterator extends \FilterIterator { /** * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after * rewind in some cases. * * @see FilterIterator::rewind() */ public function rewind() { if (\PHP_VERSION_ID > 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) { // this condition is necessary for iterators to work properly with non-local filesystems like ftp if ($innerIterator->isRewindable()) { $innerIterator->next(); $innerIterator->rewind(); } } elseif ($innerIterator instanceof \FilesystemIterator) { $innerIterator->next(); $innerIterator->rewind(); } $iterator = $innerIterator; } parent::rewind(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Comparator\NumberComparator; /** * SizeRangeFilterIterator filters out files that are not in the given size range. * * @author Fabien Potencier */ class SizeRangeFilterIterator extends FilterIterator { private $comparators = array(); /** * @param \Iterator $iterator The Iterator to filter * @param NumberComparator[] $comparators An array of NumberComparator instances */ public function __construct(\Iterator $iterator, array $comparators) { $this->comparators = $comparators; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ 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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * CustomFilterIterator filters files by applying anonymous functions. * * The anonymous function receives a \SplFileInfo and must return false * to remove files. * * @author Fabien Potencier */ class CustomFilterIterator extends FilterIterator { private $filters = array(); /** * @param \Iterator $iterator The Iterator to filter * @param callable[] $filters An array of PHP callbacks * * @throws \InvalidArgumentException */ public function __construct(\Iterator $iterator, array $filters) { foreach ($filters as $filter) { if (!is_callable($filter)) { throw new \InvalidArgumentException('Invalid PHP callback.'); } } $this->filters = $filters; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { if (false === call_user_func($filter, $fileinfo)) { return false; } } return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier * @author Włodzimierz Gajda */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { if (!$this->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); } /** * Converts string to regexp if necessary. * * @param string $str Pattern: string or regexp * * @return string regexp corresponding to a given string or regexp */ protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). * * @author Fabien Potencier */ abstract class MultiplePcreFilterIterator extends FilterIterator { protected $matchRegexps = array(); protected $noMatchRegexps = array(); /** * @param \Iterator $iterator The Iterator to filter * @param array $matchPatterns An array of patterns that need to match * @param array $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { foreach ($matchPatterns as $pattern) { $this->matchRegexps[] = $this->toRegex($pattern); } foreach ($noMatchPatterns as $pattern) { $this->noMatchRegexps[] = $this->toRegex($pattern); } parent::__construct($iterator); } /** * Checks whether the string is accepted by the regex filters. * * If there is no regexps defined in the class, this method will accept the string. * Such case can be handled by child classes before calling the method if they want to * apply a different behavior. * * @param string $string The string to be matched against filters * * @return bool */ protected function isAccepted($string) { // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $string)) { return false; } } // should at least match one rule if ($this->matchRegexps) { foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $string)) { return true; } } return false; } // If there is no match rules, the file is accepted return true; } /** * Checks whether the string is a regex. * * @param string $str * * @return bool Whether the given string is a regex */ 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; } /** * Converts string into regexp. * * @param string $str Pattern * * @return string regexp corresponding to a given string */ abstract protected function toRegex($str); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * ExcludeDirectoryFilterIterator filters out directories. * * @author Fabien Potencier */ class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator { private $iterator; private $isRecursive; private $excludedDirs = array(); private $excludedPattern; /** * @param \Iterator $iterator The Iterator to filter * @param array $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { $this->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); } /** * Filters the iterator values. * * @return bool True if the value should be kept, false otherwise */ 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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * FileTypeFilterIterator only keeps files, directories, or both. * * @author Fabien Potencier */ class FileTypeFilterIterator extends FilterIterator { const ONLY_FILES = 1; const ONLY_DIRECTORIES = 2; private $mode; /** * @param \Iterator $iterator The Iterator to filter * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ public function __construct(\Iterator $iterator, $mode) { $this->mode = $mode; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ 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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier * @author Włodzimierz Gajda */ class PathFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $filename = $this->current()->getRelativePathname(); if ('\\' === DIRECTORY_SEPARATOR) { $filename = str_replace('\\', '/', $filename); } return $this->isAccepted($filename); } /** * Converts strings to regexp. * * PCRE patterns are left unchanged. * * Default conversion: * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' * * Use only / as directory separator (on Windows also). * * @param string $str Pattern: regexp or dirname * * @return string regexp corresponding to a given string or regexp */ protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * DepthRangeFilterIterator limits the directory depth. * * @author Fabien Potencier */ class DepthRangeFilterIterator extends FilterIterator { private $minDepth = 0; /** * @param \RecursiveIteratorIterator $iterator The Iterator to filter * @param int $minDepth The min depth * @param int $maxDepth The max depth */ public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX) { $this->minDepth = $minDepth; $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; @trigger_error('The '.__NAMESPACE__.'\FilePathsIterator class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); use Symfony\Component\Finder\SplFileInfo; /** * Iterate over shell command result. * * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. */ class FilePathsIterator extends \ArrayIterator { /** * @var string */ private $baseDir; /** * @var int */ private $baseDirLength; /** * @var string */ private $subPath; /** * @var string */ private $subPathname; /** * @var SplFileInfo */ private $current; /** * @param array $paths List of paths returned by shell command * @param string $baseDir Base dir for relative path building */ public function __construct(array $paths, $baseDir) { $this->baseDir = $baseDir; $this->baseDirLength = strlen($baseDir); parent::__construct($paths); } /** * @param string $name * @param array $arguments * * @return mixed */ public function __call($name, array $arguments) { return call_user_func_array(array($this->current(), $name), $arguments); } /** * Return an instance of SplFileInfo with support for relative paths. * * @return SplFileInfo File information */ public function current() { return $this->current; } /** * @return string */ public function key() { return $this->current->getPathname(); } public function next() { parent::next(); $this->buildProperties(); } public function rewind() { parent::rewind(); $this->buildProperties(); } /** * @return string */ public function getSubPath() { return $this->subPath; } /** * @return string */ public function getSubPathname() { return $this->subPathname; } private function buildProperties() { $absolutePath = parent::current(); if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) { $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\'); $dir = dirname($this->subPathname); $this->subPath = '.' === $dir ? '' : $dir; } else { $this->subPath = $this->subPathname = ''; } $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Exception\AccessDeniedException; use Symfony\Component\Finder\SplFileInfo; /** * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { /** * @var bool */ private $ignoreUnreadableDirs; /** * @var bool */ private $rewindable; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations private $rootPath; private $subPath; private $directorySeparator = '/'; /** * @param string $path * @param int $flags * @param bool $ignoreUnreadableDirs * * @throws \RuntimeException */ public function __construct($path, $flags, $ignoreUnreadableDirs = false) { if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { throw new \RuntimeException('This iterator only support returning current as fileinfo.'); } parent::__construct($path, $flags); $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->rootPath = (string) $path; if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { $this->directorySeparator = DIRECTORY_SEPARATOR; } } /** * Return an instance of SplFileInfo with support for relative paths. * * @return SplFileInfo File information */ public function current() { // the logic here avoids redoing the same work in all iterations 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); } /** * @return \RecursiveIterator * * @throws AccessDeniedException */ public function getChildren() { try { $children = parent::getChildren(); if ($children instanceof self) { // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; // performance optimization to avoid redoing the same work in all children $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } return $children; } catch (\UnexpectedValueException $e) { if ($this->ignoreUnreadableDirs) { // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. return new \RecursiveArrayIterator(array()); } else { throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } } /** * Do nothing for non rewindable stream. */ public function rewind() { if (false === $this->isRewindable()) { return; } // @see https://bugs.php.net/68557 if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) { parent::next(); } parent::rewind(); } /** * Checks if the stream is rewindable. * * @return bool true when the stream is rewindable, false otherwise */ public function isRewindable() { if (null !== $this->rewindable) { return $this->rewindable; } // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed 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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * SortableIterator applies a sort on a given Iterator. * * @author Fabien Potencier */ class SortableIterator implements \IteratorAggregate { const SORT_BY_NAME = 1; const SORT_BY_TYPE = 2; const SORT_BY_ACCESSED_TIME = 3; const SORT_BY_CHANGED_TIME = 4; const SORT_BY_MODIFIED_TIME = 5; private $iterator; private $sort; /** * @param \Traversable $iterator The Iterator to filter * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) * * @throws \InvalidArgumentException */ public function __construct(\Traversable $iterator, $sort) { $this->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); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Comparator\DateComparator; /** * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). * * @author Fabien Potencier */ class DateRangeFilterIterator extends FilterIterator { private $comparators = array(); /** * @param \Iterator $iterator The Iterator to filter * @param DateComparator[] $comparators An array of DateComparator instances */ public function __construct(\Iterator $iterator, array $comparators) { $this->comparators = $comparators; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ 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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; @trigger_error('The '.__NAMESPACE__.'\Regex class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); /** * @author Jean-François Simon */ class Regex implements ValueInterface { const START_FLAG = '^'; const END_FLAG = '$'; const BOUNDARY = '~'; const JOKER = '.*'; const ESCAPING = '\\'; /** * @var string */ private $pattern; /** * @var array */ private $options; /** * @var bool */ private $startFlag; /** * @var bool */ private $endFlag; /** * @var bool */ private $startJoker; /** * @var bool */ private $endJoker; /** * @param string $expr * * @return self * * @throws \InvalidArgumentException */ public static function create($expr) { if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) { $start = substr($m[1], 0, 1); $end = substr($m[1], -1); if ( ($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ('{' === $start && '}' === $end) || ('(' === $start && ')' === $end) ) { return new self(substr($m[1], 1, -1), $m[2], $end); } } throw new \InvalidArgumentException('Given expression is not a regex.'); } /** * @param string $pattern * @param string $options * @param string $delimiter */ public function __construct($pattern, $options = '', $delimiter = null) { if (null !== $delimiter) { // removes delimiter escaping $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern); } $this->parsePattern($pattern); $this->options = $options; } /** * @return string */ public function __toString() { return $this->render(); } /** * {@inheritdoc} */ public function render() { return self::BOUNDARY .$this->renderPattern() .self::BOUNDARY .$this->options; } /** * {@inheritdoc} */ public function renderPattern() { return ($this->startFlag ? self::START_FLAG : '') .($this->startJoker ? self::JOKER : '') .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern) .($this->endJoker ? self::JOKER : '') .($this->endFlag ? self::END_FLAG : ''); } /** * {@inheritdoc} */ public function isCaseSensitive() { return !$this->hasOption('i'); } /** * {@inheritdoc} */ public function getType() { return Expression::TYPE_REGEX; } /** * {@inheritdoc} */ public function prepend($expr) { $this->pattern = $expr.$this->pattern; return $this; } /** * {@inheritdoc} */ public function append($expr) { $this->pattern .= $expr; return $this; } /** * @param string $option * * @return bool */ public function hasOption($option) { return false !== strpos($this->options, $option); } /** * @param string $option * * @return $this */ public function addOption($option) { if (!$this->hasOption($option)) { $this->options .= $option; } return $this; } /** * @param string $option * * @return $this */ public function removeOption($option) { $this->options = str_replace($option, '', $this->options); return $this; } /** * @param bool $startFlag * * @return $this */ public function setStartFlag($startFlag) { $this->startFlag = $startFlag; return $this; } /** * @return bool */ public function hasStartFlag() { return $this->startFlag; } /** * @param bool $endFlag * * @return $this */ public function setEndFlag($endFlag) { $this->endFlag = (bool) $endFlag; return $this; } /** * @return bool */ public function hasEndFlag() { return $this->endFlag; } /** * @param bool $startJoker * * @return $this */ public function setStartJoker($startJoker) { $this->startJoker = $startJoker; return $this; } /** * @return bool */ public function hasStartJoker() { return $this->startJoker; } /** * @param bool $endJoker * * @return $this */ public function setEndJoker($endJoker) { $this->endJoker = (bool) $endJoker; return $this; } /** * @return bool */ public function hasEndJoker() { return $this->endJoker; } /** * @param array $replacement * * @return $this */ public function replaceJokers($replacement) { $replace = function ($subject) use ($replacement) { $subject = $subject[0]; $replace = 0 === substr_count($subject, '\\') % 2; return $replace ? str_replace('.', $replacement, $subject) : $subject; }; $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern); return $this; } /** * @param string $pattern */ private function parsePattern($pattern) { if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) { $pattern = substr($pattern, 1); } if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) { $pattern = substr($pattern, 2); } if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) { $pattern = substr($pattern, 0, -1); } if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) { $pattern = substr($pattern, 0, -2); } $this->pattern = $pattern; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; @trigger_error('The '.__NAMESPACE__.'\ValueInterface interface is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); /** * @author Jean-François Simon */ interface ValueInterface { /** * Renders string representation of expression. * * @return string */ public function render(); /** * Renders string representation of pattern. * * @return string */ public function renderPattern(); /** * Returns value case sensitivity. * * @return bool */ public function isCaseSensitive(); /** * Returns expression type. * * @return int */ public function getType(); /** * @param string $expr * * @return $this */ public function prepend($expr); /** * @param string $expr * * @return $this */ public function append($expr); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; @trigger_error('The '.__NAMESPACE__.'\Expression class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); /** * @author Jean-François Simon */ class Expression implements ValueInterface { const TYPE_REGEX = 1; const TYPE_GLOB = 2; /** * @var ValueInterface */ private $value; /** * @param string $expr * * @return self */ public static function create($expr) { return new self($expr); } /** * @param string $expr */ public function __construct($expr) { try { $this->value = Regex::create($expr); } catch (\InvalidArgumentException $e) { $this->value = new Glob($expr); } } /** * @return string */ public function __toString() { return $this->render(); } /** * {@inheritdoc} */ public function render() { return $this->value->render(); } /** * {@inheritdoc} */ public function renderPattern() { return $this->value->renderPattern(); } /** * @return bool */ public function isCaseSensitive() { return $this->value->isCaseSensitive(); } /** * @return int */ public function getType() { return $this->value->getType(); } /** * {@inheritdoc} */ public function prepend($expr) { $this->value->prepend($expr); return $this; } /** * {@inheritdoc} */ public function append($expr) { $this->value->append($expr); return $this; } /** * @return bool */ public function isRegex() { return self::TYPE_REGEX === $this->value->getType(); } /** * @return bool */ public function isGlob() { return self::TYPE_GLOB === $this->value->getType(); } /** * @return Glob * * @throws \LogicException */ public function getGlob() { if (self::TYPE_GLOB !== $this->value->getType()) { throw new \LogicException('Regex can\'t be transformed to glob.'); } return $this->value; } /** * @return Regex */ public function getRegex() { return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; @trigger_error('The '.__NAMESPACE__.'\Glob class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); use Symfony\Component\Finder\Glob as FinderGlob; /** * @author Jean-François Simon */ class Glob implements ValueInterface { /** * @var string */ private $pattern; /** * @param string $pattern */ public function __construct($pattern) { $this->pattern = $pattern; } /** * {@inheritdoc} */ public function render() { return $this->pattern; } /** * {@inheritdoc} */ public function renderPattern() { return $this->pattern; } /** * {@inheritdoc} */ public function getType() { return Expression::TYPE_GLOB; } /** * {@inheritdoc} */ public function isCaseSensitive() { return true; } /** * {@inheritdoc} */ public function prepend($expr) { $this->pattern = $expr.$this->pattern; return $this; } /** * {@inheritdoc} */ public function append($expr) { $this->pattern .= $expr; return $this; } /** * Tests if glob is expandable ("*.{a,b}" syntax). * * @return bool */ public function isExpandable() { return false !== strpos($this->pattern, '{') && false !== strpos($this->pattern, '}'); } /** * @param bool $strictLeadingDot * @param bool $strictWildcardSlash * * @return Regex */ public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true) { $regex = FinderGlob::toRegex($this->pattern, $strictLeadingDot, $strictWildcardSlash, ''); return new Regex($regex); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder; /** * Glob matches globbing patterns against text. * * if match_glob("foo.*", "foo.bar") echo "matched\n"; * * // prints foo.bar and foo.baz * $regex = glob_to_regex("foo.*"); * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t) * { * if (/$regex/) echo "matched: $car\n"; * } * * Glob implements glob(3) style matching that can be used to match * against text, rather than fetching names from a filesystem. * * Based on the Perl Text::Glob module. * * @author Fabien Potencier PHP port * @author Richard Clamp Perl version * @copyright 2004-2005 Fabien Potencier * @copyright 2002 Richard Clamp */ class Glob { /** * Returns a regexp which is the equivalent of the glob pattern. * * @param string $glob The glob pattern * @param bool $strictLeadingDot * @param bool $strictWildcardSlash * @param string $delimiter Optional delimiter * * @return string regex The regexp */ public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#') { $firstByte = true; $escaping = false; $inCurlies = 0; $regex = ''; $sizeGlob = strlen($glob); for ($i = 0; $i < $sizeGlob; ++$i) { $car = $glob[$i]; if ($firstByte) { if ($strictLeadingDot && '.' !== $car) { $regex .= '(?=[^\.])'; } $firstByte = false; } if ('/' === $car) { $firstByte = true; } if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { $regex .= "\\$car"; } elseif ('*' === $car) { $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); } elseif ('?' === $car) { $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); } elseif ('{' === $car) { $regex .= $escaping ? '\\{' : '('; if (!$escaping) { ++$inCurlies; } } elseif ('}' === $car && $inCurlies) { $regex .= $escaping ? '}' : ')'; if (!$escaping) { --$inCurlies; } } elseif (',' === $car && $inCurlies) { $regex .= $escaping ? ',' : '|'; } elseif ('\\' === $car) { if ($escaping) { $regex .= '\\\\'; $escaping = false; } else { $escaping = true; } continue; } else { $regex .= $car; } $escaping = false; } return $delimiter.'^'.$regex.'$'.$delimiter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Comparator; /** * NumberComparator compiles a simple comparison to an anonymous * subroutine, which you can call with a value to be tested again. * * Now this would be very pointless, if NumberCompare didn't understand * magnitudes. * * The target value may use magnitudes of kilobytes (k, ki), * megabytes (m, mi), or gigabytes (g, gi). Those suffixed * with an i use the appropriate 2**n version in accordance with the * IEC standard: http://physics.nist.gov/cuu/Units/binary.html * * Based on the Perl Number::Compare module. * * @author Fabien Potencier PHP port * @author Richard Clamp Perl version * @copyright 2004-2005 Fabien Potencier * @copyright 2002 Richard Clamp * * @see http://physics.nist.gov/cuu/Units/binary.html */ class NumberComparator extends Comparator { /** * @param string|int $test A comparison string or an integer * * @throws \InvalidArgumentException If the test is not understood */ public function __construct($test) { if (!preg_match('#^\s*(==|!=|[<>]=?)?\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])) { // magnitude 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] : '=='); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Comparator; /** * DateCompare compiles date comparisons. * * @author Fabien Potencier */ class DateComparator extends Comparator { /** * @param string $test A comparison string * * @throws \InvalidArgumentException If the test is not understood */ public function __construct($test) { if (!preg_match('#^\s*(==|!=|[<>]=?|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); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Comparator; /** * Comparator. * * @author Fabien Potencier */ class Comparator { private $target; private $operator = '=='; /** * Gets the target value. * * @return string The target value */ public function getTarget() { return $this->target; } /** * Sets the target value. * * @param string $target The target value */ public function setTarget($target) { $this->target = $target; } /** * Gets the comparison operator. * * @return string The operator */ public function getOperator() { return $this->operator; } /** * Sets the comparison operator. * * @param string $operator A valid operator * * @throws \InvalidArgumentException */ public function setOperator($operator) { if (!$operator) { $operator = '=='; } if (!in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } $this->operator = $operator; } /** * Tests against the target. * * @param mixed $test A test value * * @return bool */ 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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Shell; @trigger_error('The '.__NAMESPACE__.'\Command class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); /** * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. */ class Command { /** * @var Command|null */ private $parent; /** * @var array */ private $bits = array(); /** * @var array */ private $labels = array(); /** * @var \Closure|null */ private $errorHandler; /** * @param Command|null $parent Parent command */ public function __construct(Command $parent = null) { $this->parent = $parent; } /** * Returns command as string. * * @return string */ public function __toString() { return $this->join(); } /** * Creates a new Command instance. * * @param Command|null $parent Parent command * * @return self */ public static function create(Command $parent = null) { return new self($parent); } /** * Escapes special chars from input. * * @param string $input A string to escape * * @return string The escaped string */ public static function escape($input) { return escapeshellcmd($input); } /** * Quotes input. * * @param string $input An argument string * * @return string The quoted string */ public static function quote($input) { return escapeshellarg($input); } /** * Appends a string or a Command instance. * * @param string|Command $bit * * @return $this */ public function add($bit) { $this->bits[] = $bit; return $this; } /** * Prepends a string or a command instance. * * @param string|Command $bit * * @return $this */ public function top($bit) { array_unshift($this->bits, $bit); foreach ($this->labels as $label => $index) { $this->labels[$label] += 1; } return $this; } /** * Appends an argument, will be quoted. * * @param string $arg * * @return $this */ public function arg($arg) { $this->bits[] = self::quote($arg); return $this; } /** * Appends escaped special command chars. * * @param string $esc * * @return $this */ public function cmd($esc) { $this->bits[] = self::escape($esc); return $this; } /** * Inserts a labeled command to feed later. * * @param string $label The unique label * * @return self|string * * @throws \RuntimeException If label already exists */ public function ins($label) { if (isset($this->labels[$label])) { throw new \RuntimeException(sprintf('Label "%s" already exists.', $label)); } $this->bits[] = self::create($this); $this->labels[$label] = count($this->bits) - 1; return $this->bits[$this->labels[$label]]; } /** * Retrieves a previously labeled command. * * @param string $label * * @return self|string * * @throws \RuntimeException */ public function get($label) { if (!isset($this->labels[$label])) { throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label)); } return $this->bits[$this->labels[$label]]; } /** * Returns parent command (if any). * * @return self * * @throws \RuntimeException If command has no parent */ public function end() { if (null === $this->parent) { throw new \RuntimeException('Calling end on root command doesn\'t make sense.'); } return $this->parent; } /** * Counts bits stored in command. * * @return int The bits count */ public function length() { return count($this->bits); } /** * @param \Closure $errorHandler * * @return $this */ public function setErrorHandler(\Closure $errorHandler) { $this->errorHandler = $errorHandler; return $this; } /** * @return \Closure|null */ public function getErrorHandler() { return $this->errorHandler; } /** * Executes current command. * * @return array The command result * * @throws \RuntimeException */ public function execute() { if (null === $errorHandler = $this->errorHandler) { exec($this->join(), $output); } else { $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes); $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY); if ($error = stream_get_contents($pipes[2])) { $errorHandler($error); } proc_close($process); } return $output ?: array(); } /** * Joins bits. * * @return string */ public function join() { return implode(' ', array_filter( array_map(function ($bit) { return $bit instanceof Command ? $bit->join() : ($bit ?: null); }, $this->bits), function ($bit) { return null !== $bit; } )); } /** * Insert a string or a Command instance before the bit at given position $index (index starts from 0). * * @param string|Command $bit * @param int $index * * @return $this */ public function addAtIndex($bit, $index) { array_splice($this->bits, $index, 0, $bit instanceof self ? array($bit) : $bit); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Shell; @trigger_error('The '.__NAMESPACE__.'\Shell class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); /** * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. */ class Shell { const TYPE_UNIX = 1; const TYPE_DARWIN = 2; const TYPE_CYGWIN = 3; const TYPE_WINDOWS = 4; const TYPE_BSD = 5; /** * @var string|null */ private $type; /** * Returns guessed OS type. * * @return int */ public function getType() { if (null === $this->type) { $this->type = $this->guessType(); } return $this->type; } /** * Tests if a command is available. * * @param string $command * * @return bool */ public function testCommand($command) { if (!function_exists('exec')) { return false; } // todo: find a better way (command could not be available) $testCommand = 'which '; if (self::TYPE_WINDOWS === $this->type) { $testCommand = 'where '; } $command = escapeshellcmd($command); exec($testCommand.$command, $output, $code); return 0 === $code && count($output) > 0; } /** * Guesses OS type. * * @return int */ private function guessType() { $os = strtolower(PHP_OS); if (false !== strpos($os, 'cygwin')) { return self::TYPE_CYGWIN; } if (false !== strpos($os, 'darwin')) { return self::TYPE_DARWIN; } if (false !== strpos($os, 'bsd')) { return self::TYPE_BSD; } if (0 === strpos($os, 'win')) { return self::TYPE_WINDOWS; } return self::TYPE_UNIX; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder; /** * Extends \SplFileInfo to support relative paths. * * @author Fabien Potencier */ class SplFileInfo extends \SplFileInfo { private $relativePath; private $relativePathname; /** * @param string $file The file name * @param string $relativePath The relative path * @param string $relativePathname The relative path name */ public function __construct($file, $relativePath, $relativePathname) { parent::__construct($file); $this->relativePath = $relativePath; $this->relativePathname = $relativePathname; } /** * Returns the relative path. * * This path does not contain the file name. * * @return string the relative path */ public function getRelativePath() { return $this->relativePath; } /** * Returns the relative path name. * * This path contains the file name. * * @return string the relative path name */ public function getRelativePathname() { return $this->relativePathname; } /** * Returns the contents of the file. * * @return string the contents of the file * * @throws \RuntimeException */ 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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; @trigger_error('The '.__NAMESPACE__.'\OperationNotPermitedException class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); /** * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. */ class OperationNotPermitedException extends AdapterFailureException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; /** * @author Jean-François Simon */ interface ExceptionInterface { /** * @return \Symfony\Component\Finder\Adapter\AdapterInterface */ public function getAdapter(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; @trigger_error('The '.__NAMESPACE__.'\ShellCommandFailureException class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); use Symfony\Component\Finder\Adapter\AdapterInterface; use Symfony\Component\Finder\Shell\Command; /** * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. */ class ShellCommandFailureException extends AdapterFailureException { /** * @var Command */ private $command; /** * @param AdapterInterface $adapter * @param Command $command * @param \Exception|null $previous */ public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null) { $this->command = $command; parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous); } /** * @return Command */ public function getCommand() { return $this->command; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; /** * @author Jean-François Simon */ class AccessDeniedException extends \UnexpectedValueException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; @trigger_error('The '.__NAMESPACE__.'\AdapterFailureException class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); use Symfony\Component\Finder\Adapter\AdapterInterface; /** * Base exception for all adapter failures. * * @author Jean-François Simon * * @deprecated since 2.8, to be removed in 3.0. */ class AdapterFailureException extends \RuntimeException implements ExceptionInterface { /** * @var \Symfony\Component\Finder\Adapter\AdapterInterface */ private $adapter; /** * @param AdapterInterface $adapter * @param string|null $message * @param \Exception|null $previous */ public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null) { $this->adapter = $adapter; parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous); } /** * {@inheritdoc} */ public function getAdapter() { return $this->adapter; } } '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; '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; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Mbstring; /** * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. * * Implemented: * - mb_chr - Returns a specific character from its Unicode code point * - mb_convert_encoding - Convert character encoding * - mb_convert_variables - Convert character code in variable(s) * - mb_decode_mimeheader - Decode string in MIME header field * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED * - mb_convert_case - Perform case folding on a string * - mb_get_info - Get internal settings of mbstring * - mb_http_input - Detect HTTP input character encoding * - mb_http_output - Set/Get HTTP output character encoding * - mb_internal_encoding - Set/Get internal character encoding * - mb_list_encodings - Returns an array of all supported encodings * - mb_ord - Returns the Unicode code point of a character * - mb_output_handler - Callback function converts character encoding in output buffer * - mb_scrub - Replaces ill-formed byte sequences with substitute characters * - mb_strlen - Get string length * - mb_strpos - Find position of first occurrence of string in a string * - mb_strrpos - Find position of last occurrence of a string in a string * - mb_strtolower - Make a string lowercase * - mb_strtoupper - Make a string uppercase * - mb_substitute_character - Set/Get substitution character * - mb_substr - Get part of string * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive * - mb_stristr - Finds first occurrence of a string within another, case insensitive * - mb_strrchr - Finds the last occurrence of a character in a string within another * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive * - mb_strstr - Finds first occurrence of a string within anothers * - mb_strwidth - Return width of string * - mb_substr_count - Count the number of substring occurrences * * Not implemented: * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) * - mb_decode_numericentity - Decode HTML numeric string reference to character * - mb_encode_numericentity - Encode character to HTML numeric string reference * - mb_ereg_* - Regular expression with multibyte support * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable * - mb_preferred_mime_name - Get MIME charset string * - mb_regex_encoding - Returns current encoding for multibyte regex as string * - mb_regex_set_options - Set/Get the default options for mbregex functions * - mb_send_mail - Send encoded mail * - mb_split - Split multibyte string using regular expression * - mb_strcut - Get part of string * - mb_strimwidth - Get truncated string with specified width * * @author Nicolas Grekas * * @internal */ final class Mbstring { const MB_CASE_FOLD = PHP_INT_MAX; private static $encodingList = array('ASCII', 'UTF-8'); private static $language = 'neutral'; private static $internalEncoding = 'UTF-8'; private static $caseFold = array( array('µ','ſ',"\xCD\x85",'ς',"\xCF\x90","\xCF\x91","\xCF\x95","\xCF\x96","\xCF\xB0","\xCF\xB1","\xCF\xB5","\xE1\xBA\x9B","\xE1\xBE\xBE"), array('μ','s','ι', 'σ','β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1",'ι'), ); public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) { if (is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); } else { $fromEncoding = self::getEncoding($fromEncoding); } $toEncoding = self::getEncoding($toEncoding); if ('BASE64' === $fromEncoding) { $s = base64_decode($s); $fromEncoding = $toEncoding; } if ('BASE64' === $toEncoding) { return base64_encode($s); } if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { $fromEncoding = 'Windows-1252'; } if ('UTF-8' !== $fromEncoding) { $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); } return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); } if ('HTML-ENTITIES' === $fromEncoding) { $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); $fromEncoding = 'UTF-8'; } return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); } public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); $ok = true; array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { $ok = false; } }); return $ok ? $fromEncoding : false; } public static function mb_decode_mimeheader($s) { return iconv_mime_decode($s, 2, self::$internalEncoding); } public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) { trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); } public static function mb_convert_case($s, $mode, $encoding = null) { if ('' === $s .= '') { return ''; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { $s = iconv($encoding, 'UTF-8//IGNORE', $s); } if (MB_CASE_TITLE == $mode) { $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s); $s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $s); } else { if (MB_CASE_UPPER == $mode) { static $upper = null; if (null === $upper) { $upper = self::getData('upperCase'); } $map = $upper; } else { if (self::MB_CASE_FOLD === $mode) { $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); } static $lower = null; if (null === $lower) { $lower = self::getData('lowerCase'); } $map = $lower; } static $ulenMask = array("\xC0" => 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) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return strlen($s); } return @iconv_strlen($s, $encoding); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return strpos($haystack, $needle, $offset); } 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 ('CP850' === $encoding || 'ASCII' === $encoding) { return strrpos($haystack, $needle, $offset); } 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 ('CP850' === $encoding || 'ASCII' === $encoding) { return substr($s, $start, null === $length ? 2147483647 : $length); } 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); if ('CP850' === $encoding || 'ASCII' === $encoding) { return strrchr($haystack, $needle, $part); } $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; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Mbstring as p; if (!function_exists('mb_strlen')) { define('MB_CASE_UPPER', 0); define('MB_CASE_LOWER', 1); define('MB_CASE_TITLE', 2); function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } } if (!function_exists('mb_chr')) { function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); } function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } } * : The path to the PHP file to execute. * * [...] * : One or more arguments to pass to the file. They are placed in the $args variable. * * [--skip-wordpress] * : Load and execute file without loading WordPress. * * @when before_wp_load * * ## EXAMPLES * * wp eval-file my-code.php value1 value2 */ public function __invoke( $args, $assoc_args ) { $file = array_shift( $args ); if ( !file_exists( $file ) ) { WP_CLI::error( "'$file' does not exist." ); } if ( null === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-wordpress' ) ) { WP_CLI::get_runner()->load_wordpress(); } self::_eval( $file, $args ); } private static function _eval( $file, $args ) { include( $file ); } } * : The code to execute, as a string. * * [--skip-wordpress] * : Execute code without loading WordPress. * * ## EXAMPLES * * # Display WordPress content directory. * $ wp eval 'echo WP_CONTENT_DIR;' * /var/www/wordpress/wp-content * * # Generate a random number. * $ wp eval 'echo rand();' --skip-wordpress * 479620423 * * @when before_wp_load */ public function __invoke( $args, $assoc_args ) { if ( null === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-wordpress' ) ) { WP_CLI::get_runner()->load_wordpress(); } eval( $args[0] ); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; use cli\arguments\Argument; use cli\arguments\HelpScreen; use cli\arguments\InvalidArguments; use cli\arguments\Lexer; /** * Parses command line arguments. */ class Arguments implements \ArrayAccess { protected $_flags = array(); protected $_options = array(); protected $_strict = false; protected $_input = array(); protected $_invalid = array(); protected $_parsed; protected $_lexer; /** * Initializes the argument parser. If you wish to change the default behaviour * you may pass an array of options as the first argument. Valid options are * `'help'` and `'strict'`, each a boolean. * * `'help'` is `true` by default, `'strict'` is false by default. * * @param array $options An array of options for this parser. */ public function __construct($options = array()) { $options += array( 'strict' => false, 'input' => array_slice($_SERVER['argv'], 1) ); $this->_input = $options['input']; $this->setStrict($options['strict']); if (isset($options['flags'])) { $this->addFlags($options['flags']); } if (isset($options['options'])) { $this->addOptions($options['options']); } } /** * Get the list of arguments found by the defined definitions. * * @return array */ public function getArguments() { if (!isset($this->_parsed)) { $this->parse(); } return $this->_parsed; } public function getHelpScreen() { return new HelpScreen($this); } /** * Encodes the parsed arguments as JSON. * * @return string */ public function asJSON() { return json_encode($this->_parsed); } /** * Returns true if a given argument was parsed. * * @param mixed $offset An Argument object or the name of the argument. * @return bool */ public function offsetExists($offset) { if ($offset instanceOf Argument) { $offset = $offset->key; } return array_key_exists($offset, $this->_parsed); } /** * Get the parsed argument's value. * * @param mixed $offset An Argument object or the name of the argument. * @return mixed */ public function offsetGet($offset) { if ($offset instanceOf Argument) { $offset = $offset->key; } if (isset($this->_parsed[$offset])) { return $this->_parsed[$offset]; } } /** * Sets the value of a parsed argument. * * @param mixed $offset An Argument object or the name of the argument. * @param mixed $value The value to set */ public function offsetSet($offset, $value) { if ($offset instanceOf Argument) { $offset = $offset->key; } $this->_parsed[$offset] = $value; } /** * Unset a parsed argument. * * @param mixed $offset An Argument object or the name of the argument. */ public function offsetUnset($offset) { if ($offset instanceOf Argument) { $offset = $offset->key; } unset($this->_parsed[$offset]); } /** * Adds a flag (boolean argument) to the argument list. * * @param mixed $flag A string representing the flag, or an array of strings. * @param array $settings An array of settings for this flag. * @setting string description A description to be shown in --help. * @setting bool default The default value for this flag. * @setting bool stackable Whether the flag is repeatable to increase the value. * @setting array aliases Other ways to trigger this flag. * @return $this */ public function addFlag($flag, $settings = array()) { if (is_string($settings)) { $settings = array('description' => $settings); } if (is_array($flag)) { $settings['aliases'] = $flag; $flag = array_shift($settings['aliases']); } if (isset($this->_flags[$flag])) { $this->_warn('flag already exists: ' . $flag); return $this; } $settings += array( 'default' => false, 'stackable' => false, 'description' => null, 'aliases' => array() ); $this->_flags[$flag] = $settings; return $this; } /** * Add multiple flags at once. The input array should be keyed with the * primary flag character, and the values should be the settings array * used by {addFlag}. * * @param array $flags An array of flags to add * @return $this */ public function addFlags($flags) { foreach ($flags as $flag => $settings) { if (is_numeric($flag)) { $this->_warn('No flag character given'); continue; } $this->addFlag($flag, $settings); } return $this; } /** * Adds an option (string argument) to the argument list. * * @param mixed $option A string representing the option, or an array of strings. * @param array $settings An array of settings for this option. * @setting string description A description to be shown in --help. * @setting bool default The default value for this option. * @setting array aliases Other ways to trigger this option. * @return $this */ public function addOption($option, $settings = array()) { if (is_string($settings)) { $settings = array('description' => $settings); } if (is_array($option)) { $settings['aliases'] = $option; $option = array_shift($settings['aliases']); } if (isset($this->_options[$option])) { $this->_warn('option already exists: ' . $option); return $this; } $settings += array( 'default' => null, 'description' => null, 'aliases' => array() ); $this->_options[$option] = $settings; return $this; } /** * Add multiple options at once. The input array should be keyed with the * primary option string, and the values should be the settings array * used by {addOption}. * * @param array $options An array of options to add * @return $this */ public function addOptions($options) { foreach ($options as $option => $settings) { if (is_numeric($option)) { $this->_warn('No option string given'); continue; } $this->addOption($option, $settings); } return $this; } /** * Enable or disable strict mode. If strict mode is active any invalid * arguments found by the parser will throw `cli\arguments\InvalidArguments`. * * Even if strict is disabled, invalid arguments are logged and can be * retrieved with `cli\Arguments::getInvalidArguments()`. * * @param bool $strict True to enable, false to disable. * @return $this */ public function setStrict($strict) { $this->_strict = (bool)$strict; return $this; } /** * Get the list of invalid arguments the parser found. * * @return array */ public function getInvalidArguments() { return $this->_invalid; } /** * Get a flag by primary matcher or any defined aliases. * * @param mixed $flag Either a string representing the flag or an * cli\arguments\Argument object. * @return array */ public function getFlag($flag) { if ($flag instanceOf Argument) { $obj = $flag; $flag = $flag->value; } if (isset($this->_flags[$flag])) { return $this->_flags[$flag]; } foreach ($this->_flags as $master => $settings) { if (in_array($flag, (array)$settings['aliases'])) { if (isset($obj)) { $obj->key = $master; } $cache[$flag] =& $settings; return $settings; } } } public function getFlags() { return $this->_flags; } public function hasFlags() { return !empty($this->_flags); } /** * Returns true if the given argument is defined as a flag. * * @param mixed $argument Either a string representing the flag or an * cli\arguments\Argument object. * @return bool */ public function isFlag($argument) { return (null !== $this->getFlag($argument)); } /** * Returns true if the given flag is stackable. * * @param mixed $flag Either a string representing the flag or an * cli\arguments\Argument object. * @return bool */ public function isStackable($flag) { $settings = $this->getFlag($flag); return isset($settings) && (true === $settings['stackable']); } /** * Get an option by primary matcher or any defined aliases. * * @param mixed $option Either a string representing the option or an * cli\arguments\Argument object. * @return array */ public function getOption($option) { if ($option instanceOf Argument) { $obj = $option; $option = $option->value; } if (isset($this->_options[$option])) { return $this->_options[$option]; } foreach ($this->_options as $master => $settings) { if (in_array($option, (array)$settings['aliases'])) { if (isset($obj)) { $obj->key = $master; } return $settings; } } } public function getOptions() { return $this->_options; } public function hasOptions() { return !empty($this->_options); } /** * Returns true if the given argument is defined as an option. * * @param mixed $argument Either a string representing the option or an * cli\arguments\Argument object. * @return bool */ public function isOption($argument) { return (null != $this->getOption($argument)); } /** * Parses the argument list with the given options. The returned argument list * will use either the first long name given or the first name in the list * if a long name is not given. * * @return array * @throws arguments\InvalidArguments */ public function parse() { $this->_invalid = array(); $this->_parsed = array(); $this->_lexer = new Lexer($this->_input); $this->_applyDefaults(); foreach ($this->_lexer as $argument) { if ($this->_parseFlag($argument)) { continue; } if ($this->_parseOption($argument)) { continue; } array_push($this->_invalid, $argument->raw); } if ($this->_strict && !empty($this->_invalid)) { throw new InvalidArguments($this->_invalid); } } /** * This applies the default values, if any, of all of the * flags and options, so that if there is a default value * it will be available. */ private function _applyDefaults() { foreach($this->_flags as $flag => $settings) { $this[$flag] = $settings['default']; } foreach($this->_options as $option => $settings) { // If the default is 0 we should still let it be set. if (!empty($settings['default']) || $settings['default'] === 0) { $this[$option] = $settings['default']; } } } private function _warn($message) { trigger_error('[' . __CLASS__ .'] ' . $message, E_USER_WARNING); } private function _parseFlag($argument) { if (!$this->isFlag($argument)) { return false; } if ($this->isStackable($argument)) { if (!isset($this[$argument])) { $this[$argument->key] = 0; } $this[$argument->key] += 1; } else { $this[$argument->key] = true; } return true; } private function _parseOption($option) { if (!$this->isOption($option)) { return false; } // Peak ahead to make sure we get a value. if ($this->_lexer->end() || !$this->_lexer->peek->isValue) { $optionSettings = $this->getOption($option->key); if (empty($optionSettings['default']) && $optionSettings !== 0) { // Oops! Got no value and no default , throw a warning and continue. $this->_warn('no value given for ' . $option->raw); $this[$option->key] = null; } else { // No value and we have a default, so we set to the default $this[$option->key] = $optionSettings['default']; } return true; } // Store as array and join to string after looping for values $values = array(); // Loop until we find a flag in peak-ahead foreach ($this->_lexer as $value) { array_push($values, $value->raw); if (!$this->_lexer->end() && !$this->_lexer->peek->isValue) { break; } } $this[$option->key] = join($values, ' '); return true; } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\table; /** * Table renderers are used to change how a table is displayed. */ abstract class Renderer { protected $_widths = array(); public function __construct(array $widths = array()) { $this->setWidths($widths); } /** * Set the widths of each column in the table. * * @param array $widths The widths of the columns. */ public function setWidths(array $widths) { $this->_widths = $widths; } /** * Render a border for the top and bottom and separating the headers from the * table rows. * * @return string The table border. */ public function border() { return null; } /** * Renders a row for output. * * @param array $row The table row. * @return string The formatted table row. */ abstract public function row(array $row); } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\table; /** * The tabular renderer is used for displaying data in a tabular format. */ class Tabular extends Renderer { /** * Renders a row for output. * * @param array $row The table row. * @return string The formatted table row. */ public function row(array $row) { return implode("\t", array_values($row)); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\table; use cli\Colors; use cli\Shell; /** * The ASCII renderer renders tables with ASCII borders. */ class Ascii extends Renderer { protected $_characters = array( 'corner' => '+', 'line' => '-', 'border' => '|', 'padding' => ' ', ); protected $_border = null; protected $_constraintWidth = null; protected $_pre_colorized = false; /** * Set the widths of each column in the table. * * @param array $widths The widths of the columns. */ public function setWidths(array $widths) { if ( is_null( $this->_constraintWidth ) ) { $this->_constraintWidth = (int) Shell::columns(); } $col_count = count( $widths ); $col_borders_count = $col_count ? ( ( $col_count - 1 ) * strlen( $this->_characters['border'] ) ) : 0; $table_borders_count = strlen( $this->_characters['border'] ) * 2; $col_padding_count = $col_count * strlen( $this->_characters['padding'] ) * 2; $max_width = $this->_constraintWidth - $col_borders_count - $table_borders_count - $col_padding_count; if ( $widths && $max_width && array_sum( $widths ) > $max_width ) { $avg = floor( $max_width / count( $widths ) ); $resize_widths = array(); $extra_width = 0; foreach( $widths as $width ) { if ( $width > $avg ) { $resize_widths[] = $width; } else { $extra_width = $extra_width + ( $avg - $width ); } } if ( ! empty( $resize_widths ) && $extra_width ) { $avg_extra_width = floor( $extra_width / count( $resize_widths ) ); foreach( $widths as &$width ) { if ( in_array( $width, $resize_widths ) ) { $width = $avg + $avg_extra_width; array_shift( $resize_widths ); // Last item gets the cake if ( empty( $resize_widths ) ) { $width = 0; // Zero it so not in sum. $width = $max_width - array_sum( $widths ); } } } } } $this->_widths = $widths; } /** * Set the constraint width for the table * * @param int $constraintWidth */ public function setConstraintWidth( $constraintWidth ) { $this->_constraintWidth = $constraintWidth; } /** * Set the characters used for rendering the Ascii table. * * The keys `corner`, `line` and `border` are used in rendering. * * @param $characters array Characters used in rendering. */ public function setCharacters(array $characters) { $this->_characters = array_merge($this->_characters, $characters); } /** * Render a border for the top and bottom and separating the headers from the * table rows. * * @return string The table border. */ public function border() { if (!isset($this->_border)) { $this->_border = $this->_characters['corner']; foreach ($this->_widths as $width) { $this->_border .= str_repeat($this->_characters['line'], $width + 2); $this->_border .= $this->_characters['corner']; } } return $this->_border; } /** * Renders a row for output. * * @param array $row The table row. * @return string The formatted table row. */ public function row( array $row ) { $extra_row_count = 0; if ( count( $row ) > 0 ) { $extra_rows = array_fill( 0, count( $row ), array() ); foreach( $row as $col => $value ) { $value = str_replace( array( "\r\n", "\n" ), ' ', $value ); $col_width = $this->_widths[ $col ]; $encoding = function_exists( 'mb_detect_encoding' ) ? mb_detect_encoding( $value, null, true /*strict*/ ) : false; $original_val_width = Colors::width( $value, self::isPreColorized( $col ), $encoding ); if ( $col_width && $original_val_width > $col_width ) { $row[ $col ] = \cli\safe_substr( $value, 0, $col_width, true /*is_width*/, $encoding ); $value = \cli\safe_substr( $value, \cli\safe_strlen( $row[ $col ], $encoding ), null /*length*/, false /*is_width*/, $encoding ); $i = 0; do { $extra_value = \cli\safe_substr( $value, 0, $col_width, true /*is_width*/, $encoding ); $val_width = Colors::width( $extra_value, self::isPreColorized( $col ), $encoding ); if ( $val_width ) { $extra_rows[ $col ][] = $extra_value; $value = \cli\safe_substr( $value, \cli\safe_strlen( $extra_value, $encoding ), null /*length*/, false /*is_width*/, $encoding ); $i++; if ( $i > $extra_row_count ) { $extra_row_count = $i; } } } while( $value ); } } } $row = array_map(array($this, 'padColumn'), $row, array_keys($row)); array_unshift($row, ''); // First border array_push($row, ''); // Last border $ret = join($this->_characters['border'], $row); if ( $extra_row_count ) { foreach( $extra_rows as $col => $col_values ) { while( count( $col_values ) < $extra_row_count ) { $col_values[] = ''; } } do { $row_values = array(); $has_more = false; foreach( $extra_rows as $col => &$col_values ) { $row_values[ $col ] = array_shift( $col_values ); if ( count( $col_values ) ) { $has_more = true; } } $row_values = array_map(array($this, 'padColumn'), $row_values, array_keys($row_values)); array_unshift($row_values, ''); // First border array_push($row_values, ''); // Last border $ret .= PHP_EOL . join($this->_characters['border'], $row_values); } while( $has_more ); } return $ret; } private function padColumn($content, $column) { return $this->_characters['padding'] . Colors::pad( $content, $this->_widths[ $column ], $this->isPreColorized( $column ) ) . $this->_characters['padding']; } /** * Set whether items are pre-colorized. * * @param bool|array $colorized A boolean to set all columns in the table as pre-colorized, or an array of booleans keyed by column index (number) to set individual columns as pre-colorized. */ public function setPreColorized( $pre_colorized ) { $this->_pre_colorized = $pre_colorized; } /** * Is a column pre-colorized? * * @param int $column Column index to check. * @return bool True if whole table is marked as pre-colorized, or if the individual column is pre-colorized; else false. */ public function isPreColorized( $column ) { if ( is_bool( $this->_pre_colorized ) ) { return $this->_pre_colorized; } if ( is_array( $this->_pre_colorized ) && isset( $this->_pre_colorized[ $column ] ) ) { return $this->_pre_colorized[ $column ]; } return false; } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; abstract class Memoize { protected $_memoCache = array(); public function __get($name) { if (isset($this->_memoCache[$name])) { return $this->_memoCache[$name]; } // Hide probable private methods if (0 == strncmp($name, '_', 1)) { return ($this->_memoCache[$name] = null); } if (!method_exists($this, $name)) { return ($this->_memoCache[$name] = null); } $method = array($this, $name); ($this->_memoCache[$name] = call_user_func($method)); return $this->_memoCache[$name]; } protected function _unmemo($name) { if ($name === true) { $this->_memoCache = array(); } else { unset($this->_memoCache[$name]); } } } $value ) { $msg = str_replace( '{:' . $key . '}', $value, $msg ); } return Colors::shouldColorize() ? Colors::colorize( $msg ) : $msg; } /** * Shortcut for printing to `STDOUT`. The message and parameters are passed * through `sprintf` before output. * * @param string $msg The message to output in `printf` format. * @param mixed ... Either scalar arguments or a single array argument. * @return void * @see \cli\render() */ public static function out( $msg ) { fwrite( static::$out, self::_call( 'render', func_get_args() ) ); } /** * Pads `$msg` to the width of the shell before passing to `cli\out`. * * @param string $msg The message to pad and pass on. * @param mixed ... Either scalar arguments or a single array argument. * @return void * @see cli\out() */ public static function out_padded( $msg ) { $msg = self::_call( 'render', func_get_args() ); self::out( str_pad( $msg, \cli\Shell::columns() ) ); } /** * Prints a message to `STDOUT` with a newline appended. See `\cli\out` for * more documentation. * * @see cli\out() */ public static function line( $msg = '' ) { // func_get_args is empty if no args are passed even with the default above. $args = array_merge( func_get_args(), array( '' ) ); $args[0] .= "\n"; self::_call( 'out', $args ); } /** * Shortcut for printing to `STDERR`. The message and parameters are passed * through `sprintf` before output. * * @param string $msg The message to output in `printf` format. With no string, * a newline is printed. * @param mixed ... Either scalar arguments or a single array argument. * @return void */ public static function err( $msg = '' ) { // func_get_args is empty if no args are passed even with the default above. $args = array_merge( func_get_args(), array( '' ) ); $args[0] .= "\n"; fwrite( static::$err, self::_call( 'render', $args ) ); } /** * Takes input from `STDIN` in the given format. If an end of transmission * character is sent (^D), an exception is thrown. * * @param string $format A valid input format. See `fscanf` for documentation. * If none is given, all input up to the first newline * is accepted. * @param boolean $hide If true will hide what the user types in. * @return string The input with whitespace trimmed. * @throws \Exception Thrown if ctrl-D (EOT) is sent as input. */ public static function input( $format = null, $hide = false ) { if ( $hide ) Shell::hide(); if( $format ) { fscanf( static::$in, $format . "\n", $line ); } else { $line = fgets( static::$in ); } if ( $hide ) { Shell::hide( false ); echo "\n"; } if( $line === false ) { throw new \Exception( 'Caught ^D during input' ); } return trim( $line ); } /** * Displays an input prompt. If no default value is provided the prompt will * continue displaying until input is received. * * @param string $question The question to ask the user. * @param bool|string $default A default value if the user provides no input. * @param string $marker A string to append to the question and default value * on display. * @param boolean $hide Optionally hides what the user types in. * @return string The users input. * @see cli\input() */ public static function prompt( $question, $default = null, $marker = ': ', $hide = false ) { if( $default && strpos( $question, '[' ) === false ) { $question .= ' [' . $default . ']'; } while( true ) { self::out( $question . $marker ); $line = self::input( null, $hide ); if( !empty( $line ) ) return $line; if( $default !== false ) return $default; } } /** * Presents a user with a multiple choice question, useful for 'yes/no' type * questions (which this public static function defaults too). * * @param string $question The question to ask the user. * @param string $choice A string of characters allowed as a response. Case is ignored. * @param string $default The default choice. NULL if a default is not allowed. * @return string The users choice. * @see cli\prompt() */ public static function choose( $question, $choice = 'yn', $default = 'n' ) { if( !is_string( $choice ) ) { $choice = join( '', $choice ); } // Make every choice character lowercase except the default $choice = str_ireplace( $default, strtoupper( $default ), strtolower( $choice ) ); // Seperate each choice with a forward-slash $choices = trim( join( '/', preg_split( '//', $choice ) ), '/' ); while( true ) { $line = self::prompt( sprintf( '%s? [%s]', $question, $choices ), $default, '' ); if( stripos( $choice, $line ) !== false ) { return strtolower( $line ); } if( !empty( $default ) ) { return strtolower( $default ); } } } /** * Displays an array of strings as a menu where a user can enter a number to * choose an option. The array must be a single dimension with either strings * or objects with a `__toString()` method. * * @param array $items The list of items the user can choose from. * @param string $default The index of the default item. * @param string $title The message displayed to the user when prompted. * @return string The index of the chosen item. * @see cli\line() * @see cli\input() * @see cli\err() */ public static function menu( $items, $default = null, $title = 'Choose an item' ) { $map = array_values( $items ); if( $default && strpos( $title, '[' ) === false && isset( $items[$default] ) ) { $title .= ' [' . $items[$default] . ']'; } foreach( $map as $idx => $item ) { self::line( ' %d. %s', $idx + 1, (string)$item ); } self::line(); while( true ) { fwrite( static::$out, sprintf( '%s: ', $title ) ); $line = self::input(); if( is_numeric( $line ) ) { $line--; if( isset( $map[$line] ) ) { return array_search( $map[$line], $items ); } if( $line < 0 || $line >= count( $map ) ) { self::err( 'Invalid menu selection: out of range' ); } } else if( isset( $default ) ) { return $default; } } } /** * Sets one of the streams (input, output, or error) to a `stream` type resource. * * Valid $whichStream values are: * - 'in' (default: STDIN) * - 'out' (default: STDOUT) * - 'err' (default: STDERR) * * Any custom streams will be closed for you on shutdown, so please don't close stream * resources used with this method. * * @param string $whichStream The stream property to update * @param resource $stream The new stream resource to use * @return void * @throws \Exception Thrown if $stream is not a resource of the 'stream' type. */ public static function setStream( $whichStream, $stream ) { if( !is_resource( $stream ) || get_resource_type( $stream ) !== 'stream' ) { throw new \Exception( 'Invalid resource type!' ); } if( property_exists( __CLASS__, $whichStream ) ) { static::${$whichStream} = $stream; } register_shutdown_function( function() use ($stream) { fclose( $stream ); } ); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\arguments; /** * Thrown when undefined arguments are detected in strict mode. */ class InvalidArguments extends \InvalidArgumentException { protected $arguments; /** * @param array $arguments A list of arguments that do not fit the profile. */ public function __construct(array $arguments) { $this->arguments = $arguments; $this->message = $this->_generateMessage(); } /** * Get the arguments that caused the exception. * * @return array */ public function getArguments() { return $this->arguments; } private function _generateMessage() { return 'unknown argument' . (count($this->arguments) > 1 ? 's' : '') . ': ' . join($this->arguments, ', '); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\arguments; use cli\Arguments; /** * Arguments help screen renderer */ class HelpScreen { protected $_flags = array(); protected $_maxFlag = 0; protected $_options = array(); protected $_maxOption = 0; public function __construct(Arguments $arguments) { $this->setArguments($arguments); } public function __toString() { return $this->render(); } public function setArguments(Arguments $arguments) { $this->consumeArgumentFlags($arguments); $this->consumeArgumentOptions($arguments); } public function consumeArgumentFlags(Arguments $arguments) { $data = $this->_consume($arguments->getFlags()); $this->_flags = $data[0]; $this->_flagMax = $data[1]; } public function consumeArgumentOptions(Arguments $arguments) { $data = $this->_consume($arguments->getOptions()); $this->_options = $data[0]; $this->_optionMax = $data[1]; } public function render() { $help = array(); array_push($help, $this->_renderFlags()); array_push($help, $this->_renderOptions()); return join($help, "\n\n"); } private function _renderFlags() { if (empty($this->_flags)) { return null; } return "Flags\n" . $this->_renderScreen($this->_flags, $this->_flagMax); } private function _renderOptions() { if (empty($this->_options)) { return null; } return "Options\n" . $this->_renderScreen($this->_options, $this->_optionMax); } private function _renderScreen($options, $max) { $help = array(); foreach ($options as $option => $settings) { $formatted = ' ' . str_pad($option, $max); $dlen = 80 - 4 - $max; $description = str_split($settings['description'], $dlen); $formatted.= ' ' . array_shift($description); if ($settings['default']) { $formatted .= ' [default: ' . $settings['default'] . ']'; } $pad = str_repeat(' ', $max + 3); while ($desc = array_shift($description)) { $formatted .= "\n${pad}${desc}"; } array_push($help, $formatted); } return join($help, "\n"); } private function _consume($options) { $max = 0; $out = array(); foreach ($options as $option => $settings) { $names = array('--' . $option); foreach ($settings['aliases'] as $alias) { array_push($names, '-' . $alias); } $names = join($names, ', '); $max = max(strlen($names), $max); $out[$names] = $settings; } return array($out, $max); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\arguments; use cli\Memoize; class Lexer extends Memoize implements \Iterator { private $_items = array(); private $_index = 0; private $_length = 0; private $_first = true; /** * @param array $items A list of strings to process as tokens. */ public function __construct(array $items) { $this->_items = $items; $this->_length = count($items); } /** * The current token. * * @return string */ public function current() { return $this->_item; } /** * Peek ahead to the next token without moving the cursor. * * @return Argument */ public function peek() { return new Argument($this->_items[0]); } /** * Move the cursor forward 1 element if it is valid. */ public function next() { if ($this->valid()) { $this->_shift(); } } /** * Return the current position of the cursor. * * @return int */ public function key() { return $this->_index; } /** * Move forward 1 element and, if the method hasn't been called before, reset * the cursor's position to 0. */ public function rewind() { $this->_shift(); if ($this->_first) { $this->_index = 0; $this->_first = false; } } /** * Returns true if the cursor has not reached the end of the list. * * @return bool */ public function valid() { return ($this->_index < $this->_length); } /** * Push an element to the front of the stack. * * @param mixed $item The value to set */ public function unshift($item) { array_unshift($this->_items, $item); $this->_length += 1; } /** * Returns true if the cursor is at the end of the list. * * @return bool */ public function end() { return ($this->_index + 1) == $this->_length; } private function _shift() { $this->_item = new Argument(array_shift($this->_items)); $this->_index += 1; $this->_explode(); $this->_unmemo('peek'); } private function _explode() { if (!$this->_item->canExplode) { return false; } foreach ($this->_item->exploded as $piece) { $this->unshift('-' . $piece); } } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\arguments; use cli\Memoize; /** * Represents an Argument or a value and provides several helpers related to parsing an argument list. */ class Argument extends Memoize { /** * The canonical name of this argument, used for aliasing. * * @param string */ public $key; private $_argument; private $_raw; /** * @param string $argument The raw argument, leading dashes included. */ public function __construct($argument) { $this->_raw = $argument; $this->key =& $this->_argument; if ($this->isLong) { $this->_argument = substr($this->_raw, 2); } else if ($this->isShort) { $this->_argument = substr($this->_raw, 1); } else { $this->_argument = $this->_raw; } } /** * Returns the raw input as a string. * * @return string */ public function __toString() { return (string)$this->_raw; } /** * Returns the formatted argument string. * * @return string */ public function value() { return $this->_argument; } /** * Returns the raw input. * * @return mixed */ public function raw() { return $this->_raw; } /** * Returns true if the string matches the pattern for long arguments. * * @return bool */ public function isLong() { return (0 == strncmp($this->_raw, '--', 2)); } /** * Returns true if the string matches the pattern for short arguments. * * @return bool */ public function isShort() { return !$this->isLong && (0 == strncmp($this->_raw, '-', 1)); } /** * Returns true if the string matches the pattern for arguments. * * @return bool */ public function isArgument() { return $this->isShort() || $this->isLong(); } /** * Returns true if the string matches the pattern for values. * * @return bool */ public function isValue() { return !$this->isArgument; } /** * Returns true if the argument is short but contains several characters. Each * character is considered a separate argument. * * @return bool */ public function canExplode() { return $this->isShort && strlen($this->_argument) > 1; } /** * Returns all but the first character of the argument, removing them from the * objects representation at the same time. * * @return array */ public function exploded() { $exploded = array(); for ($i = strlen($this->_argument); $i > 0; $i--) { array_push($exploded, $this->_argument[$i - 1]); } $this->_argument = array_pop($exploded); $this->_raw = '-' . $this->_argument; return $exploded; } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; /** * Handles rendering strings. If extra scalar arguments are given after the `$msg` * the string will be rendered with `sprintf`. If the second argument is an `array` * then each key in the array will be the placeholder name. Placeholders are of the * format {:key}. * * @param string $msg The message to render. * @param mixed ... Either scalar arguments or a single array argument. * @return string The rendered string. */ function render( $msg ) { return Streams::_call( 'render', func_get_args() ); } /** * Shortcut for printing to `STDOUT`. The message and parameters are passed * through `sprintf` before output. * * @param string $msg The message to output in `printf` format. * @param mixed ... Either scalar arguments or a single array argument. * @return void * @see \cli\render() */ function out( $msg ) { Streams::_call( 'out', func_get_args() ); } /** * Pads `$msg` to the width of the shell before passing to `cli\out`. * * @param string $msg The message to pad and pass on. * @param mixed ... Either scalar arguments or a single array argument. * @return void * @see cli\out() */ function out_padded( $msg ) { Streams::_call( 'out_padded', func_get_args() ); } /** * Prints a message to `STDOUT` with a newline appended. See `\cli\out` for * more documentation. * * @see cli\out() */ function line( $msg = '' ) { Streams::_call( 'line', func_get_args() ); } /** * Shortcut for printing to `STDERR`. The message and parameters are passed * through `sprintf` before output. * * @param string $msg The message to output in `printf` format. With no string, * a newline is printed. * @param mixed ... Either scalar arguments or a single array argument. * @return void */ function err( $msg = '' ) { Streams::_call( 'err', func_get_args() ); } /** * Takes input from `STDIN` in the given format. If an end of transmission * character is sent (^D), an exception is thrown. * * @param string $format A valid input format. See `fscanf` for documentation. * If none is given, all input up to the first newline * is accepted. * @return string The input with whitespace trimmed. * @throws \Exception Thrown if ctrl-D (EOT) is sent as input. */ function input( $format = null ) { return Streams::input( $format ); } /** * Displays an input prompt. If no default value is provided the prompt will * continue displaying until input is received. * * @param string $question The question to ask the user. * @param string $default A default value if the user provides no input. * @param string $marker A string to append to the question and default value on display. * @param boolean $hide If the user input should be hidden * @return string The users input. * @see cli\input() */ function prompt( $question, $default = false, $marker = ': ', $hide = false ) { return Streams::prompt( $question, $default, $marker, $hide ); } /** * Presents a user with a multiple choice question, useful for 'yes/no' type * questions (which this function defaults too). * * @param string $question The question to ask the user. * @param string $choice * @param string|null $default The default choice. NULL if a default is not allowed. * @internal param string $valid A string of characters allowed as a response. Case * is ignored. * @return string The users choice. * @see cli\prompt() */ function choose( $question, $choice = 'yn', $default = 'n' ) { return Streams::choose( $question, $choice, $default ); } /** * Does the same as {@see choose()}, but always asks yes/no and returns a boolean * * @param string $question The question to ask the user. * @param bool|null $default The default choice, in a boolean format. * @return bool */ function confirm( $question, $default = false ) { if ( is_bool( $default ) ) { $default = $default? 'y' : 'n'; } $result = choose( $question, 'yn', $default ); return $result == 'y'; } /** * Displays an array of strings as a menu where a user can enter a number to * choose an option. The array must be a single dimension with either strings * or objects with a `__toString()` method. * * @param array $items The list of items the user can choose from. * @param string $default The index of the default item. * @param string $title The message displayed to the user when prompted. * @return string The index of the chosen item. * @see cli\line() * @see cli\input() * @see cli\err() */ function menu( $items, $default = null, $title = 'Choose an item' ) { return Streams::menu( $items, $default, $title ); } /** * Attempts an encoding-safe way of getting string length. If intl extension or PCRE with '\X' or mb_string extension aren't * available, falls back to basic strlen. * * @param string $str The string to check. * @param string|bool $encoding Optional. The encoding of the string. Default false. * @return int Numeric value that represents the string's length */ function safe_strlen( $str, $encoding = false ) { // Allow for selective testings - "1" bit set tests grapheme_strlen(), "2" preg_match_all( '/\X/u' ), "4" mb_strlen(), "other" strlen(). $test_safe_strlen = getenv( 'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' ); // Assume UTF-8 if no encoding given - `grapheme_strlen()` will return null if given non-UTF-8 string. if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && null !== ( $length = grapheme_strlen( $str ) ) ) { if ( ! $test_safe_strlen || ( $test_safe_strlen & 1 ) ) { return $length; } } // Assume UTF-8 if no encoding given - `preg_match_all()` will return false if given non-UTF-8 string. if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() && false !== ( $length = preg_match_all( '/\X/u', $str, $dummy /*needed for PHP 5.3*/ ) ) ) { if ( ! $test_safe_strlen || ( $test_safe_strlen & 2 ) ) { return $length; } } // Legacy encodings and old PHPs will reach here. if ( function_exists( 'mb_strlen' ) && ( $encoding || function_exists( 'mb_detect_encoding' ) ) ) { if ( ! $encoding ) { $encoding = mb_detect_encoding( $str, null, true /*strict*/ ); } $length = $encoding ? mb_strlen( $str, $encoding ) : mb_strlen( $str ); // mbstring funcs can fail if given `$encoding` arg that evals to false. if ( 'UTF-8' === $encoding ) { // Subtract combining characters. $length -= preg_match_all( get_unicode_regexs( 'm' ), $str, $dummy /*needed for PHP 5.3*/ ); } if ( ! $test_safe_strlen || ( $test_safe_strlen & 4 ) ) { return $length; } } return strlen( $str ); } /** * Attempts an encoding-safe way of getting a substring. If intl extension or PCRE with '\X' or mb_string extension aren't * available, falls back to substr(). * * @param string $str The input string. * @param int $start The starting position of the substring. * @param int|bool|null $length Optional, unless $is_width is set. Maximum length of the substring. Default false. Negative not supported. * @param int|bool $is_width Optional. If set and encoding is UTF-8, $length (which must be specified) is interpreted as spacing width. Default false. * @param string|bool $encoding Optional. The encoding of the string. Default false. * @return bool|string False if given unsupported args, otherwise substring of string specified by start and length parameters */ function safe_substr( $str, $start, $length = false, $is_width = false, $encoding = false ) { // Negative $length or $is_width and $length not specified not supported. if ( $length < 0 || ( $is_width && ( null === $length || false === $length ) ) ) { return false; } // Need this for normalization below and other uses. $safe_strlen = safe_strlen( $str, $encoding ); // Normalize `$length` when not specified - PHP 5.3 substr takes false as full length, PHP > 5.3 takes null. if ( null === $length || false === $length ) { $length = $safe_strlen; } // Normalize `$start` - various methods treat this differently. if ( $start > $safe_strlen ) { return ''; } if ( $start < 0 && -$start > $safe_strlen ) { $start = 0; } // Allow for selective testings - "1" bit set tests grapheme_substr(), "2" preg_split( '/\X/' ), "4" mb_substr(), "8" substr(). $test_safe_substr = getenv( 'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR' ); // Assume UTF-8 if no encoding given - `grapheme_substr()` will return false (not null like `grapheme_strlen()`) if given non-UTF-8 string. if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && false !== ( $try = grapheme_substr( $str, $start, $length ) ) ) { if ( ! $test_safe_substr || ( $test_safe_substr & 1 ) ) { return $is_width ? _safe_substr_eaw( $try, $length ) : $try; } } // Assume UTF-8 if no encoding given - `preg_split()` returns a one element array if given non-UTF-8 string (PHP bug) so need to check `preg_last_error()`. if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() ) { if ( false !== ( $try = preg_split( '/(\X)/u', $str, $safe_strlen + 1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ) ) && ! preg_last_error() ) { $try = implode( '', array_slice( $try, $start, $length ) ); if ( ! $test_safe_substr || ( $test_safe_substr & 2 ) ) { return $is_width ? _safe_substr_eaw( $try, $length ) : $try; } } } // Legacy encodings and old PHPs will reach here. if ( function_exists( 'mb_substr' ) && ( $encoding || function_exists( 'mb_detect_encoding' ) ) ) { if ( ! $encoding ) { $encoding = mb_detect_encoding( $str, null, true /*strict*/ ); } // Bug: not adjusting for combining chars. $try = $encoding ? mb_substr( $str, $start, $length, $encoding ) : mb_substr( $str, $start, $length ); // mbstring funcs can fail if given `$encoding` arg that evals to false. if ( 'UTF-8' === $encoding && $is_width ) { $try = _safe_substr_eaw( $try, $length ); } if ( ! $test_safe_substr || ( $test_safe_substr & 4 ) ) { return $try; } } return substr( $str, $start, $length ); } /** * Internal function used by `safe_substr()` to adjust for East Asian double-width chars. * * @return string */ function _safe_substr_eaw( $str, $length ) { // Set the East Asian Width regex. $eaw_regex = get_unicode_regexs( 'eaw' ); // If there's any East Asian double-width chars... if ( preg_match( $eaw_regex, $str ) ) { // Note that if the length ends in the middle of a double-width char, the char is excluded, not included. // See if it's all EAW. if ( function_exists( 'mb_substr' ) && preg_match_all( $eaw_regex, $str, $dummy /*needed for PHP 5.3*/ ) === $length ) { // Just halve the length so (rounded down to a minimum of 1). $str = mb_substr( $str, 0, max( (int) ( $length / 2 ), 1 ), 'UTF-8' ); } else { // Explode string into an array of UTF-8 chars. Based on core `_mb_substr()` in "wp-includes/compat.php". $chars = preg_split( '/([\x00-\x7f\xc2-\xf4][^\x00-\x7f\xc2-\xf4]*)/', $str, $length + 1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); $cnt = min( count( $chars ), $length ); $width = $length; for ( $length = 0; $length < $cnt && $width > 0; $length++ ) { $width -= preg_match( $eaw_regex, $chars[ $length ] ) ? 2 : 1; } // Round down to a minimum of 1. if ( $width < 0 && $length > 1 ) { $length--; } return join( '', array_slice( $chars, 0, $length ) ); } } return $str; } /** * An encoding-safe way of padding string length for display * * @param string $string The string to pad. * @param int $length The length to pad it to. * @param string|bool $encoding Optional. The encoding of the string. Default false. * @return string */ function safe_str_pad( $string, $length, $encoding = false ) { $real_length = strwidth( $string, $encoding ); $diff = strlen( $string ) - $real_length; $length += $diff; return str_pad( $string, $length ); } /** * Get width of string, ie length in characters, taking into account multi-byte and mark characters for UTF-8, and multi-byte for non-UTF-8. * * @param string $string The string to check. * @param string|bool $encoding Optional. The encoding of the string. Default false. * @return int The string's width. */ function strwidth( $string, $encoding = false ) { // Set the East Asian Width and Mark regexs. list( $eaw_regex, $m_regex ) = get_unicode_regexs(); // Allow for selective testings - "1" bit set tests grapheme_strlen(), "2" preg_match_all( '/\X/u' ), "4" mb_strwidth(), "other" safe_strlen(). $test_strwidth = getenv( 'PHP_CLI_TOOLS_TEST_STRWIDTH' ); // Assume UTF-8 if no encoding given - `grapheme_strlen()` will return null if given non-UTF-8 string. if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && null !== ( $width = grapheme_strlen( $string ) ) ) { if ( ! $test_strwidth || ( $test_strwidth & 1 ) ) { return $width + preg_match_all( $eaw_regex, $string, $dummy /*needed for PHP 5.3*/ ); } } // Assume UTF-8 if no encoding given - `preg_match_all()` will return false if given non-UTF-8 string. if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() && false !== ( $width = preg_match_all( '/\X/u', $string, $dummy /*needed for PHP 5.3*/ ) ) ) { if ( ! $test_strwidth || ( $test_strwidth & 2 ) ) { return $width + preg_match_all( $eaw_regex, $string, $dummy /*needed for PHP 5.3*/ ); } } // Legacy encodings and old PHPs will reach here. if ( function_exists( 'mb_strwidth' ) && ( $encoding || function_exists( 'mb_detect_encoding' ) ) ) { if ( ! $encoding ) { $encoding = mb_detect_encoding( $string, null, true /*strict*/ ); } $width = $encoding ? mb_strwidth( $string, $encoding ) : mb_strwidth( $string ); // mbstring funcs can fail if given `$encoding` arg that evals to false. if ( 'UTF-8' === $encoding ) { // Subtract combining characters. $width -= preg_match_all( $m_regex, $string, $dummy /*needed for PHP 5.3*/ ); } if ( ! $test_strwidth || ( $test_strwidth & 4 ) ) { return $width; } } return safe_strlen( $string, $encoding ); } /** * Returns whether ICU is modern enough not to flake out. * * @return bool */ function can_use_icu() { static $can_use_icu = null; if ( null === $can_use_icu ) { // Choosing ICU 54, Unicode 7.0. $can_use_icu = defined( 'INTL_ICU_VERSION' ) && version_compare( INTL_ICU_VERSION, '54.1', '>=' ) && function_exists( 'grapheme_strlen' ) && function_exists( 'grapheme_substr' ); } return $can_use_icu; } /** * Returns whether PCRE Unicode extended grapheme cluster '\X' is available for use. * * @return bool */ function can_use_pcre_x() { static $can_use_pcre_x = null; if ( null === $can_use_pcre_x ) { // '\X' introduced (as Unicde extended grapheme cluster) in PCRE 8.32 - see https://vcs.pcre.org/pcre/code/tags/pcre-8.32/ChangeLog?view=markup line 53. // Older versions of PCRE were bundled with PHP <= 5.3.23 & <= 5.4.13. $pcre_version = substr( PCRE_VERSION, 0, strspn( PCRE_VERSION, '0123456789.' ) ); // Remove any trailing date stuff. $can_use_pcre_x = version_compare( $pcre_version, '8.32', '>=' ) && false !== @preg_match( '/\X/u', '' ); } return $can_use_pcre_x; } /** * Get the regexs generated from Unicode data. * * @param string $idx Optional. Return a specific regex only. Default null. * @return array|string Returns keyed array if not given $idx or $idx doesn't exist, otherwise the specific regex string. */ function get_unicode_regexs( $idx = null ) { static $eaw_regex; // East Asian Width regex. Characters that count as 2 characters as they're "wide" or "fullwidth". See http://www.unicode.org/reports/tr11/tr11-19.html static $m_regex; // Mark characters regex (Unicode property "M") - mark combining "Mc", mark enclosing "Me" and mark non-spacing "Mn" chars that should be ignored for spacing purposes. if ( null === $eaw_regex ) { // Load both regexs generated from Unicode data. require __DIR__ . '/unicode/regex.php'; } if ( null !== $idx ) { if ( 'eaw' === $idx ) { return $eaw_regex; } if ( 'm' === $idx ) { return $m_regex; } } return array( $eaw_regex, $m_regex, ); } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\progress; use cli; use cli\Progress; use cli\Shell; use cli\Streams; /** * Displays a progress bar spanning the entire shell. * * Basic format: * * ^MSG PER% [======================= ] 00:00 / 00:00$ */ class Bar extends Progress { protected $_bars = '=>'; protected $_formatMessage = '{:msg} {:percent}% ['; protected $_formatTiming = '] {:elapsed} / {:estimated}'; protected $_format = '{:msg}{:bar}{:timing}'; /** * Prints the progress bar to the screen with percent complete, elapsed time * and estimated total time. * * @param boolean $finish `true` if this was called from * `cli\Notify::finish()`, `false` otherwise. * @see cli\out() * @see cli\Notify::formatTime() * @see cli\Notify::elapsed() * @see cli\Progress::estimated(); * @see cli\Progress::percent() * @see cli\Shell::columns() */ public function display($finish = false) { $_percent = $this->percent(); $percent = str_pad(floor($_percent * 100), 3);; $msg = $this->_message; $msg = Streams::render($this->_formatMessage, compact('msg', 'percent')); $estimated = $this->formatTime($this->estimated()); $elapsed = str_pad($this->formatTime($this->elapsed()), strlen($estimated)); $timing = Streams::render($this->_formatTiming, compact('elapsed', 'estimated')); $size = Shell::columns(); $size -= strlen($msg . $timing); if ( $size < 0 ) { $size = 0; } $bar = str_repeat($this->_bars[0], floor($_percent * $size)) . $this->_bars[1]; // substr is needed to trim off the bar cap at 100% $bar = substr(str_pad($bar, $size, ' '), 0, $size); Streams::out($this->_format, compact('msg', 'bar', 'timing')); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\notify; use cli\Notify; use cli\Streams; /** * The `Spinner` Notifier displays an ASCII spinner. */ class Spinner extends Notify { protected $_chars = '-\|/'; protected $_format = '{:msg} {:char} ({:elapsed}, {:speed}/s)'; protected $_iteration = 0; /** * Prints the current spinner position to `STDOUT` with the time elapsed * and tick speed. * * @param boolean $finish `true` if this was called from * `cli\Notify::finish()`, `false` otherwise. * @see cli\out_padded() * @see cli\Notify::formatTime() * @see cli\Notify::speed() */ public function display($finish = false) { $msg = $this->_message; $idx = $this->_iteration++ % strlen($this->_chars); $char = $this->_chars[$idx]; $speed = number_format(round($this->speed())); $elapsed = $this->formatTime($this->elapsed()); Streams::out_padded($this->_format, compact('msg', 'char', 'elapsed', 'speed')); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\notify; use cli\Notify; use cli\Streams; /** * A Notifer that displays a string of periods. */ class Dots extends Notify { protected $_dots; protected $_format = '{:msg}{:dots} ({:elapsed}, {:speed}/s)'; protected $_iteration; /** * Instatiates a Notification object. * * @param string $msg The text to display next to the Notifier. * @param int $dots The number of dots to iterate through. * @param int $interval The interval in milliseconds between updates. * @throws \InvalidArgumentException */ public function __construct($msg, $dots = 3, $interval = 100) { parent::__construct($msg, $interval); $this->_dots = (int)$dots; if ($this->_dots <= 0) { throw new \InvalidArgumentException('Dot count out of range, must be positive.'); } } /** * Prints the correct number of dots to `STDOUT` with the time elapsed and * tick speed. * * @param boolean $finish `true` if this was called from * `cli\Notify::finish()`, `false` otherwise. * @see cli\out_padded() * @see cli\Notify::formatTime() * @see cli\Notify::speed() */ public function display($finish = false) { $repeat = $this->_dots; if (!$finish) { $repeat = $this->_iteration++ % $repeat; } $msg = $this->_message; $dots = str_pad(str_repeat('.', $repeat), $this->_dots); $speed = number_format(round($this->speed())); $elapsed = $this->formatTime($this->elapsed()); Streams::out_padded($this->_format, compact('msg', 'dots', 'speed', 'elapsed')); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; use cli\Streams; /** * The `Notify` class is the basis of all feedback classes, such as Indicators * and Progress meters. The default behaviour is to refresh output after 100ms * have passed. This is done to preventing the screen from flickering and keep * slowdowns from output to a minimum. * * The most basic form of Notifier has no maxim, and simply displays a series * of characters to indicate progress is being made. */ abstract class Notify { protected $_current = 0; protected $_first = true; protected $_interval; protected $_message; protected $_start; protected $_timer; /** * Instatiates a Notification object. * * @param string $msg The text to display next to the Notifier. * @param int $interval The interval in milliseconds between updates. */ public function __construct($msg, $interval = 100) { $this->_message = $msg; $this->_interval = (int)$interval; } /** * This method should be used to print out the Notifier. This method is * called from `cli\Notify::tick()` after `cli\Notify::$_interval` has passed. * * @abstract * @param boolean $finish * @see cli\Notify::tick() */ abstract public function display($finish = false); /** * Reset the notifier state so the same instance can be used in multiple loops. */ public function reset() { $this->_current = 0; $this->_first = true; $this->_start = null; $this->_timer = null; } /** * Returns the formatted tick count. * * @return string The formatted tick count. */ public function current() { return number_format($this->_current); } /** * Calculates the time elapsed since the Notifier was first ticked. * * @return int The elapsed time in seconds. */ public function elapsed() { if (!$this->_start) { return 0; } $elapsed = time() - $this->_start; return $elapsed; } /** * Calculates the speed (number of ticks per second) at which the Notifier * is being updated. * * @return int The number of ticks performed in 1 second. */ public function speed() { static $tick, $iteration = 0, $speed = 0; if (!$this->_start) { return 0; } else if (!$tick) { $tick = $this->_start; } $now = microtime(true); $span = $now - $tick; if ($span > 1) { $iteration++; $tick = $now; $speed = ($this->_current / $iteration) / $span; } return $speed; } /** * Takes a time span given in seconds and formats it for display. The * returned string will be in MM:SS form. * * @param int $time The time span in seconds to format. * @return string The formatted time span. */ public function formatTime($time) { return floor($time / 60) . ':' . str_pad($time % 60, 2, 0, STR_PAD_LEFT); } /** * Finish our Notification display. Should be called after the Notifier is * no longer needed. * * @see cli\Notify::display() */ public function finish() { Streams::out("\r"); $this->display(true); Streams::line(); } /** * Increments are tick counter by the given amount. If no amount is provided, * the ticker is incremented by 1. * * @param int $increment The amount to increment by. */ public function increment($increment = 1) { $this->_current += $increment; } /** * Determines whether the display should be updated or not according to * our interval setting. * * @return boolean `true` if the display should be updated, `false` otherwise. */ public function shouldUpdate() { $now = microtime(true) * 1000; if (empty($this->_timer)) { $this->_start = (int)(($this->_timer = $now) / 1000); return true; } if (($now - $this->_timer) > $this->_interval) { $this->_timer = $now; return true; } return false; } /** * This method is the meat of all Notifiers. First we increment the ticker * and then update the display if enough time has passed since our last tick. * * @param int $increment The amount to increment by. * @see cli\Notify::increment() * @see cli\Notify::shouldUpdate() * @see cli\Notify::display() */ public function tick($increment = 1) { $this->increment($increment); if ($this->shouldUpdate()) { Streams::out("\r"); $this->display(); } } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; use cli\Shell; use cli\Streams; use cli\table\Ascii; use cli\table\Renderer; use cli\table\Tabular; /** * The `Table` class is used to display data in a tabular format. */ class Table { protected $_renderer; protected $_headers = array(); protected $_footers = array(); protected $_width = array(); protected $_rows = array(); /** * Initializes the `Table` class. * * There are 3 ways to instantiate this class: * * 1. Pass an array of strings as the first parameter for the column headers * and a 2-dimensional array as the second parameter for the data rows. * 2. Pass an array of hash tables (string indexes instead of numerical) * where each hash table is a row and the indexes of the *first* hash * table are used as the header values. * 3. Pass nothing and use `setHeaders()` and `addRow()` or `setRows()`. * * @param array $headers Headers used in this table. Optional. * @param array $rows The rows of data for this table. Optional. * @param array $footers Footers used in this table. Optional. */ public function __construct(array $headers = null, array $rows = null, array $footers = null) { if (!empty($headers)) { // If all the rows is given in $headers we use the keys from the // first row for the header values if ($rows === null) { $rows = $headers; $keys = array_keys(array_shift($headers)); $headers = array(); foreach ($keys as $header) { $headers[$header] = $header; } } $this->setHeaders($headers); $this->setRows($rows); } if (!empty($footers)) { $this->setFooters($footers); } if (Shell::isPiped()) { $this->setRenderer(new Tabular()); } else { $this->setRenderer(new Ascii()); } } public function resetTable() { $this->_headers = array(); $this->_width = array(); $this->_rows = array(); $this->_footers = array(); return $this; } /** * Sets the renderer used by this table. * * @param table\Renderer $renderer The renderer to use for output. * @see table\Renderer * @see table\Ascii * @see table\Tabular */ public function setRenderer(Renderer $renderer) { $this->_renderer = $renderer; } /** * Loops through the row and sets the maximum width for each column. * * @param array $row The table row. * @return array $row */ protected function checkRow(array $row) { foreach ($row as $column => $str) { $width = Colors::width( $str, $this->isAsciiPreColorized( $column ) ); if (!isset($this->_width[$column]) || $width > $this->_width[$column]) { $this->_width[$column] = $width; } } return $row; } /** * Output the table to `STDOUT` using `cli\line()`. * * If STDOUT is a pipe or redirected to a file, should output simple * tab-separated text. Otherwise, renders table with ASCII table borders * * @uses cli\Shell::isPiped() Determine what format to output * * @see cli\Table::renderRow() */ public function display() { foreach( $this->getDisplayLines() as $line ) { Streams::line( $line ); } } /** * Get the table lines to output. * * @see cli\Table::display() * @see cli\Table::renderRow() * * @return array */ public function getDisplayLines() { $this->_renderer->setWidths($this->_width); $border = $this->_renderer->border(); $out = array(); if (isset($border)) { $out[] = $border; } $out[] = $this->_renderer->row($this->_headers); if (isset($border)) { $out[] = $border; } foreach ($this->_rows as $row) { $row = $this->_renderer->row($row); $row = explode( PHP_EOL, $row ); $out = array_merge( $out, $row ); } if (isset($border)) { $out[] = $border; } if ($this->_footers) { $out[] = $this->_renderer->row($this->_footers); if (isset($border)) { $out[] = $border; } } return $out; } /** * Sort the table by a column. Must be called before `cli\Table::display()`. * * @param int $column The index of the column to sort by. */ public function sort($column) { if (!isset($this->_headers[$column])) { trigger_error('No column with index ' . $column, E_USER_NOTICE); return; } usort($this->_rows, function($a, $b) use ($column) { return strcmp($a[$column], $b[$column]); }); } /** * Set the headers of the table. * * @param array $headers An array of strings containing column header names. */ public function setHeaders(array $headers) { $this->_headers = $this->checkRow($headers); } /** * Set the footers of the table. * * @param array $footers An array of strings containing column footers names. */ public function setFooters(array $footers) { $this->_footers = $this->checkRow($footers); } /** * Add a row to the table. * * @param array $row The row data. * @see cli\Table::checkRow() */ public function addRow(array $row) { $this->_rows[] = $this->checkRow($row); } /** * Clears all previous rows and adds the given rows. * * @param array $rows A 2-dimensional array of row data. * @see cli\Table::addRow() */ public function setRows(array $rows) { $this->_rows = array(); foreach ($rows as $row) { $this->addRow($row); } } public function countRows() { return count($this->_rows); } /** * Set whether items in an Ascii table are pre-colorized. * * @param bool|array $precolorized A boolean to set all columns in the table as pre-colorized, or an array of booleans keyed by column index (number) to set individual columns as pre-colorized. * @see cli\Ascii::setPreColorized() */ public function setAsciiPreColorized( $pre_colorized ) { if ( $this->_renderer instanceof Ascii ) { $this->_renderer->setPreColorized( $pre_colorized ); } } /** * Is a column in an Ascii table pre-colorized? * * @param int $column Column index to check. * @return bool True if whole Ascii table is marked as pre-colorized, or if the individual column is pre-colorized; else false. * @see cli\Ascii::isPreColorized() */ private function isAsciiPreColorized( $column ) { if ( $this->_renderer instanceof Ascii ) { return $this->_renderer->isPreColorized( $column ); } return false; } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; /** * The `Tree` class is used to display data in a tree-like format. */ class Tree { protected $_renderer; protected $_data = array(); /** * Sets the renderer used by this tree. * * @param tree\Renderer $renderer The renderer to use for output. * @see tree\Renderer * @see tree\Ascii * @see tree\Markdown */ public function setRenderer(tree\Renderer $renderer) { $this->_renderer = $renderer; } /** * Set the data. * Format: * [ * 'Label' => [ * 'Thing' => ['Thing'], * ], * 'Thing', * ] * @param array $data */ public function setData(array $data) { $this->_data = $data; } /** * Render the tree and return it as a string. * * @return string|null */ public function render() { return $this->_renderer->render($this->_data); } /** * Display the rendered tree */ public function display() { echo $this->render(); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; /** * A more complex type of Notifier, `Progress` Notifiers always have a maxim * value and generally show some form of percent complete or estimated time * to completion along with the standard Notifier displays. * * @see cli\Notify */ abstract class Progress extends \cli\Notify { protected $_total = 0; /** * Instantiates a Progress Notifier. * * @param string $msg The text to display next to the Notifier. * @param int $total The total number of ticks we will be performing. * @param int $interval The interval in milliseconds between updates. * @see cli\Progress::setTotal() */ public function __construct($msg, $total, $interval = 100) { parent::__construct($msg, $interval); $this->setTotal($total); } /** * Set the max increments for this progress notifier. * * @param int $total The total number of times this indicator should be `tick`ed. * @throws \InvalidArgumentException Thrown if the `$total` is less than 0. */ public function setTotal($total) { $this->_total = (int)$total; if ($this->_total < 0) { throw new \InvalidArgumentException('Maximum value out of range, must be positive.'); } } /** * Reset the progress state so the same instance can be used in multiple loops. */ public function reset($total = null) { parent::reset(); if ($total) { $this->setTotal($total); } } /** * Behaves in a similar manner to `cli\Notify::current()`, but the output * is padded to match the length of `cli\Progress::total()`. * * @return string The formatted and padded tick count. * @see cli\Progress::total() */ public function current() { $size = strlen($this->total()); return str_pad(parent::current(), $size); } /** * Returns the formatted total expected ticks. * * @return string The formatted total ticks. */ public function total() { return number_format($this->_total); } /** * Calculates the estimated total time for the tick count to reach the * total ticks given. * * @return int The estimated total number of seconds for all ticks to be * completed. This is not the estimated time left, but total. * @see cli\Notify::speed() * @see cli\Notify::elapsed() */ public function estimated() { $speed = $this->speed(); if (!$speed || !$this->elapsed()) { return 0; } $estimated = round($this->_total / $speed); return $estimated; } /** * Forces the current tick count to the total ticks given at instatiation * time before passing on to `cli\Notify::finish()`. */ public function finish() { $this->_current = $this->_total; parent::finish(); } /** * Increments are tick counter by the given amount. If no amount is provided, * the ticker is incremented by 1. * * @param int $increment The amount to increment by. */ public function increment($increment = 1) { $this->_current = min($this->_total, $this->_current + $increment); } /** * Calculate the percentage completed. * * @return float The percent completed. */ public function percent() { if ($this->_total == 0) { return 1; } return ($this->_current / $this->_total); } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; /** * The `Shell` class is a utility class for shell related tasks such as * information on width. */ class Shell { /** * Returns the number of columns the current shell has for display. * * @return int The number of columns. * @todo Test on more systems. */ static public function columns() { static $columns; if ( getenv( 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET' ) ) { $columns = null; } if ( null === $columns ) { if ( function_exists( 'exec' ) ) { if ( self::is_windows() ) { // Cater for shells such as Cygwin and Git bash where `mode CON` returns an incorrect value for columns. if ( ( $shell = getenv( 'SHELL' ) ) && preg_match( '/(?:bash|zsh)(?:\.exe)?$/', $shell ) && getenv( 'TERM' ) ) { $columns = (int) exec( 'tput cols' ); } if ( ! $columns ) { $return_var = -1; $output = array(); exec( 'mode CON', $output, $return_var ); if ( 0 === $return_var && $output ) { // Look for second line ending in ": " (searching for "Columns:" will fail on non-English locales). if ( preg_match( '/:\s*[0-9]+\n[^:]+:\s*([0-9]+)\n/', implode( "\n", $output ), $matches ) ) { $columns = (int) $matches[1]; } } } } else { if ( ! ( $columns = (int) getenv( 'COLUMNS' ) ) ) { $size = exec( '/usr/bin/env stty size 2>/dev/null' ); if ( '' !== $size && preg_match( '/[0-9]+ ([0-9]+)/', $size, $matches ) ) { $columns = (int) $matches[1]; } if ( ! $columns ) { if ( getenv( 'TERM' ) ) { $columns = (int) exec( '/usr/bin/env tput cols 2>/dev/null' ); } } } } } if ( ! $columns ) { $columns = 80; // default width of cmd window on Windows OS } } return $columns; } /** * Checks whether the output of the current script is a TTY or a pipe / redirect * * Returns true if STDOUT output is being redirected to a pipe or a file; false is * output is being sent directly to the terminal. * * If an env variable SHELL_PIPE exists, returned result depends it's * value. Strings like 1, 0, yes, no, that validate to booleans are accepted. * * To enable ASCII formatting even when shell is piped, use the * ENV variable SHELL_PIPE=0 * * @return bool */ static public function isPiped() { $shellPipe = getenv('SHELL_PIPE'); if ($shellPipe !== false) { return filter_var($shellPipe, FILTER_VALIDATE_BOOLEAN); } else { return (function_exists('posix_isatty') && !posix_isatty(STDOUT)); } } /** * Uses `stty` to hide input/output completely. * @param boolean $hidden Will hide/show the next data. Defaults to true. */ static public function hide($hidden = true) { system( 'stty ' . ( $hidden? '-echo' : 'echo' ) ); } /** * Is this shell in Windows? * * @return bool */ static private function is_windows() { return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; } } ?> * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\tree; /** * Tree renderers are used to change how a tree is displayed. */ abstract class Renderer { /** * @param array $tree * @return string|null */ abstract public function render(array $tree); } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\tree; /** * The ASCII renderer renders trees with ASCII lines. */ class Markdown extends Renderer { /** * How many spaces to indent by * @var int */ protected $_padding = 2; /** * @param int $padding Optional. Default 2. */ function __construct($padding = null) { if ($padding) { $this->_padding = $padding; } } /** * Renders the tree * * @param array $tree * @param int $level Optional * @return string */ public function render(array $tree, $level = 0) { $output = ''; foreach ($tree as $label => $next) { if (is_string($next)) { $label = $next; } // Output the label $output .= sprintf("%s- %s\n", str_repeat(' ', $level * $this->_padding), $label); // Next level if (is_array($next)) { $output .= $this->render($next, $level + 1); } } return $output; } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli\tree; /** * The ASCII renderer renders trees with ASCII lines. */ class Ascii extends Renderer { /** * @param array $tree * @return string */ public function render(array $tree) { $output = ''; $treeIterator = new \RecursiveTreeIterator( new \RecursiveArrayIterator($tree), \RecursiveTreeIterator::SELF_FIRST ); foreach ($treeIterator as $val) { $output .= $val . "\n"; } return $output; } } * @copyright 2010 James Logsdom (http://girsbrain.org) * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ namespace cli; /** * Change the color of text. * * Reference: http://graphcomp.com/info/specs/ansi_col.html#colors */ class Colors { static protected $_colors = array( 'color' => array( 'black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'white' => 37 ), 'style' => array( 'bright' => 1, 'dim' => 2, 'underline' => 4, 'blink' => 5, 'reverse' => 7, 'hidden' => 8 ), 'background' => array( 'black' => 40, 'red' => 41, 'green' => 42, 'yellow' => 43, 'blue' => 44, 'magenta' => 45, 'cyan' => 46, 'white' => 47 ) ); static protected $_enabled = null; static protected $_string_cache = array(); static public function enable($force = true) { self::$_enabled = $force === true ? true : null; } static public function disable($force = true) { self::$_enabled = $force === true ? false : null; } /** * Check if we should colorize output based on local flags and shell type. * * Only check the shell type if `Colors::$_enabled` is null and `$colored` is null. */ static public function shouldColorize($colored = null) { return self::$_enabled === true || (self::$_enabled !== false && ($colored === true || ($colored !== false && Streams::isTty()))); } /** * Set the color. * * @param string $color The name of the color or style to set. * @return string */ static public function color($color) { if (!is_array($color)) { $color = compact('color'); } $color += array('color' => null, 'style' => null, 'background' => null); if ($color['color'] == 'reset') { return "\033[0m"; } $colors = array(); foreach (array('color', 'style', 'background') as $type) { $code = $color[$type]; if (isset(self::$_colors[$type][$code])) { $colors[] = self::$_colors[$type][$code]; } } if (empty($colors)) { $colors[] = 0; } return "\033[" . join(';', $colors) . "m"; } /** * Colorize a string using helpful string formatters. If the `Streams::$out` points to a TTY coloring will be enabled, * otherwise disabled. You can control this check with the `$colored` parameter. * * @param string $string * @param boolean $colored Force enable or disable the colorized output. If left as `null` the TTY will control coloring. * @return string */ static public function colorize($string, $colored = null) { $passed = $string; if (!self::shouldColorize($colored)) { $return = self::decolorize( $passed, 2 /*keep_encodings*/ ); self::cacheString($passed, $return); return $return; } $md5 = md5($passed); if (isset(self::$_string_cache[$md5]['colorized'])) { return self::$_string_cache[$md5]['colorized']; } $string = str_replace('%%', '%¾', $string); foreach (self::getColors() as $key => $value) { $string = str_replace($key, self::color($value), $string); } $string = str_replace('%¾', '%', $string); self::cacheString($passed, $string); return $string; } /** * Remove color information from a string. * * @param string $string A string with color information. * @param int $keep Optional. If the 1 bit is set, color tokens (eg "%n") won't be stripped. If the 2 bit is set, color encodings (ANSI escapes) won't be stripped. Default 0. * @return string A string with color information removed. */ static public function decolorize( $string, $keep = 0 ) { if ( ! ( $keep & 1 ) ) { // Get rid of color tokens if they exist $string = str_replace('%%', '%¾', $string); $string = str_replace(array_keys(self::getColors()), '', $string); $string = str_replace('%¾', '%', $string); } if ( ! ( $keep & 2 ) ) { // Remove color encoding if it exists foreach (self::getColors() as $key => $value) { $string = str_replace(self::color($value), '', $string); } } return $string; } /** * Cache the original, colorized, and decolorized versions of a string. * * @param string $passed The original string before colorization. * @param string $colorized The string after running through self::colorize. * @param string $deprecated Optional. Not used. Default null. */ static public function cacheString( $passed, $colorized, $deprecated = null ) { self::$_string_cache[md5($passed)] = array( 'passed' => $passed, 'colorized' => $colorized, 'decolorized' => self::decolorize($passed), // Not very useful but keep for BC. ); } /** * Return the length of the string without color codes. * * @param string $string the string to measure * @return int */ static public function length($string) { return safe_strlen( self::decolorize( $string ) ); } /** * Return the width (length in characters) of the string without color codes if enabled. * * @param string $string The string to measure. * @param bool $pre_colorized Optional. Set if the string is pre-colorized. Default false. * @param string|bool $encoding Optional. The encoding of the string. Default false. * @return int */ static public function width( $string, $pre_colorized = false, $encoding = false ) { return strwidth( $pre_colorized || self::shouldColorize() ? self::decolorize( $string, $pre_colorized ? 1 /*keep_tokens*/ : 0 ) : $string, $encoding ); } /** * Pad the string to a certain display length. * * @param string $string The string to pad. * @param int $length The display length. * @param bool $pre_colorized Optional. Set if the string is pre-colorized. Default false. * @param string|bool $encoding Optional. The encoding of the string. Default false. * @param int $pad_type Optional. Can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type is not specified it is assumed to be STR_PAD_RIGHT. * @return string */ static public function pad( $string, $length, $pre_colorized = false, $encoding = false, $pad_type = STR_PAD_RIGHT ) { $real_length = self::width( $string, $pre_colorized, $encoding ); $diff = strlen( $string ) - $real_length; $length += $diff; return str_pad( $string, $length, ' ', $pad_type ); } /** * Get the color mapping array. * * @return array Array of color tokens mapped to colors and styles. */ static public function getColors() { return array( '%y' => array('color' => 'yellow'), '%g' => array('color' => 'green'), '%b' => array('color' => 'blue'), '%r' => array('color' => 'red'), '%p' => array('color' => 'magenta'), '%m' => array('color' => 'magenta'), '%c' => array('color' => 'cyan'), '%w' => array('color' => 'white'), '%k' => array('color' => 'black'), '%n' => array('color' => 'reset'), '%Y' => array('color' => 'yellow', 'style' => 'bright'), '%G' => array('color' => 'green', 'style' => 'bright'), '%B' => array('color' => 'blue', 'style' => 'bright'), '%R' => array('color' => 'red', 'style' => 'bright'), '%P' => array('color' => 'magenta', 'style' => 'bright'), '%M' => array('color' => 'magenta', 'style' => 'bright'), '%C' => array('color' => 'cyan', 'style' => 'bright'), '%W' => array('color' => 'white', 'style' => 'bright'), '%K' => array('color' => 'black', 'style' => 'bright'), '%N' => array('color' => 'reset', 'style' => 'bright'), '%3' => array('background' => 'yellow'), '%2' => array('background' => 'green'), '%4' => array('background' => 'blue'), '%1' => array('background' => 'red'), '%5' => array('background' => 'magenta'), '%6' => array('background' => 'cyan'), '%7' => array('background' => 'white'), '%0' => array('background' => 'black'), '%F' => array('style' => 'blink'), '%U' => array('style' => 'underline'), '%8' => array('style' => 'reverse'), '%9' => array('style' => 'bright'), '%_' => array('style' => 'bright') ); } /** * Get the cached string values. * * @return array The cached string values. */ static public function getStringCache() { return self::$_string_cache; } /** * Clear the string cache. */ static public function clearStringCache() { self::$_string_cache = array(); } } array( 'verbose' => array( 'description' => 'Turn on verbose mode', 'aliases' => array('v') ), 'c' => array( 'description' => 'A counter to test stackable', 'stackable' => true ) ), 'options' => array( 'user' => array( 'description' => 'Username for authentication', 'aliases' => array('u') ) ), 'strict' => true )); try { $args->parse(); } catch (cli\InvalidArguments $e) { echo $e->getMessage() . "\n\n"; } print_r($args->getArguments()); _host = 'http://' . $host; $this->_prompt = '%K' . $this->_host . '%n/%K>%n '; } public function handleRequest($type, $path) { $request = new Buzz\Message\Request($type, $path, $this->_host); $response = new Buzz\Message\Response; $client = new Buzz\Client\FileGetContents(); $client->send($request, $response); // Display headers foreach ($response->getHeaders() as $i => $header) { if ($i == 0) { \cli\line('%G{:header}%n', compact('header')); continue; } list($key, $value) = explode(': ', $header, 2); \cli\line('%W{:key}%n: {:value}', compact('key', 'value')); } \cli\line("\n"); print $response->getContent() . "\n"; switch ($type) { } } public function run() { while (true) { $cmd = \cli\prompt($this->_prompt, false, null); if (preg_match('/^(HEAD|GET|POST|PUT|DELETE) (\S+)$/', $cmd, $matches)) { $this->handleRequest($matches[1], $matches[2]); continue; } if ($cmd == '\q') { break; } } } } try { $console = new HttpConsole(array_shift($argv) ?: '127.0.0.1:80'); $console->run(); } catch (\Exception $e) { \cli\err("\n\n%R" . $e->getMessage() . "%n\n"); } ?> * : The new permalink structure to apply. * * [--category-base=] * : Set the base for category permalinks, i.e. '/category/'. * * [--tag-base=] * : Set the base for tag permalinks, i.e. '/tag/'. * * [--hard] * : Perform a hard flush - update `.htaccess` rules as well as rewrite rules in database. * * ## EXAMPLES * * $ wp rewrite structure '/%year%/%monthnum%/%postname%/' * Success: Rewrite structure set. */ public function structure( $args, $assoc_args ) { global $wp_rewrite; // copypasta from /wp-admin/options-permalink.php $prefix = $blog_prefix = ''; if ( is_multisite() && !is_subdomain_install() && is_main_site() ) $blog_prefix = '/blog'; $permalink_structure = ( $args[0] == 'default' ) ? '' : $args[0]; if ( ! empty( $permalink_structure ) ) { $permalink_structure = preg_replace( '#/+#', '/', '/' . str_replace( '#', '', $permalink_structure ) ); if ( $prefix && $blog_prefix ) $permalink_structure = $prefix . preg_replace( '#^/?index\.php#', '', $permalink_structure ); else $permalink_structure = $blog_prefix . $permalink_structure; } $wp_rewrite->set_permalink_structure( $permalink_structure ); // Update category or tag bases if ( isset( $assoc_args['category-base'] ) ) { $category_base = $assoc_args['category-base']; if ( ! empty( $category_base ) ) $category_base = $blog_prefix . preg_replace('#/+#', '/', '/' . str_replace( '#', '', $category_base ) ); $wp_rewrite->set_category_base( $category_base ); } if ( isset( $assoc_args['tag-base'] ) ) { $tag_base = $assoc_args['tag-base']; if ( ! empty( $tag_base ) ) $tag_base = $blog_prefix . preg_replace('#/+#', '/', '/' . str_replace( '#', '', $tag_base ) ); $wp_rewrite->set_tag_base( $tag_base ); } // make sure we detect mod_rewrite if configured in apache_modules in config self::apache_modules(); // Launch a new process to flush rewrites because core expects flush // to happen after rewrites are set $new_assoc_args = array(); $cmd = 'rewrite flush'; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'hard' ) ) { $cmd .= ' --hard'; $new_assoc_args['hard'] = true; if ( ! in_array( 'mod_rewrite', (array) WP_CLI::get_config( 'apache_modules' ) ) ) { WP_CLI::warning( "Regenerating a .htaccess file requires special configuration. See usage docs." ); } } $process_run = WP_CLI::runcommand( $cmd ); if ( ! empty( $process_run->stderr ) ) { // Strip "Warning: " WP_CLI::warning( substr( $process_run->stderr, 9 ) ); } WP_CLI::success( "Rewrite structure set." ); } /** * Get a list of the current rewrite rules. * * ## OPTIONS * * [--match=] * : Show rewrite rules matching a particular URL. * * [--source=] * : Show rewrite rules from a particular source. * * [--fields=] * : Limit the output to specific fields. Defaults to match,query,source. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * ## EXAMPLES * * $ wp rewrite list --format=csv * match,query,source * ^wp-json/?$,index.php?rest_route=/,other * ^wp-json/(.*)?,index.php?rest_route=/$matches[1],other * category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$,index.php?category_name=$matches[1]&feed=$matches[2],category * category/(.+?)/(feed|rdf|rss|rss2|atom)/?$,index.php?category_name=$matches[1]&feed=$matches[2],category * category/(.+?)/embed/?$,index.php?category_name=$matches[1]&embed=true,category * * @subcommand list */ public function list_( $args, $assoc_args ) { global $wp_rewrite; $rules = get_option( 'rewrite_rules' ); if ( ! $rules ) { $rules = array(); WP_CLI::warning( 'No rewrite rules.' ); } self::check_skip_plugins_themes(); $defaults = array( 'source' => '', 'match' => '', 'format' => 'table', 'fields' => 'match,query,source', ); $assoc_args = array_merge( $defaults, $assoc_args ); $rewrite_rules_by_source = array(); $rewrite_rules_by_source['post'] = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->permalink_structure, EP_PERMALINK ); $rewrite_rules_by_source['date'] = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->get_date_permastruct(), EP_DATE ); $rewrite_rules_by_source['root'] = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->root . '/', EP_ROOT ); $rewrite_rules_by_source['comments'] = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->root . $wp_rewrite->comments_base, EP_COMMENTS, true, true, true, false ); $rewrite_rules_by_source['search'] = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->get_search_permastruct(), EP_SEARCH ); $rewrite_rules_by_source['author'] = $wp_rewrite->generate_rewrite_rules($wp_rewrite->get_author_permastruct(), EP_AUTHORS ); $rewrite_rules_by_source['page'] = $wp_rewrite->page_rewrite_rules(); // Extra permastructs including tags, categories, etc. foreach ( $wp_rewrite->extra_permastructs as $permastructname => $permastruct ) { if ( is_array( $permastruct ) ) { $rewrite_rules_by_source[$permastructname] = $wp_rewrite->generate_rewrite_rules( $permastruct['struct'], $permastruct['ep_mask'], $permastruct['paged'], $permastruct['feed'], $permastruct['forcomments'], $permastruct['walk_dirs'], $permastruct['endpoints'] ); } else { $rewrite_rules_by_source[$permastructname] = $wp_rewrite->generate_rewrite_rules( $permastruct, EP_NONE ); } } // Apply the filters used in core just in case foreach( $rewrite_rules_by_source as $source => $source_rules ) { $rewrite_rules_by_source[$source] = apply_filters( $source . '_rewrite_rules', $source_rules ); if ( 'post_tag' == $source ) $rewrite_rules_by_source[$source] = apply_filters( 'tag_rewrite_rules', $source_rules ); } $rule_list = array(); foreach ( $rules as $match => $query ) { if ( ! empty( $assoc_args['match'] ) && ! preg_match( "!^$match!", trim( $assoc_args['match'], '/' ) ) ) continue; $source = 'other'; foreach( $rewrite_rules_by_source as $rules_source => $source_rules ) { if ( array_key_exists( $match, $source_rules ) ) { $source = $rules_source; } } if ( ! empty( $assoc_args['source'] ) && $source != $assoc_args['source'] ) continue; $rule_list[] = compact( 'match', 'query', 'source' ); } WP_CLI\Utils\format_items( $assoc_args['format'], $rule_list, explode( ',', $assoc_args['fields'] ) ); } /** * Expose apache modules if present in config * * Implementation Notes: This function exposes a global function * apache_get_modules and also sets the $is_apache global variable. * * This is so that flush_rewrite_rules will actually write out the * .htaccess file for apache wordpress installations. There is a check * to see: * * 1. if the $is_apache variable is set. * 2. if the mod_rewrite module is returned from the apache_get_modules * function. * * To get this to work with wp-cli you'll need to add the mod_rewrite module * to your config.yml. For example * * ``` * apache_modules: * - mod_rewrite * ``` * * If this isn't done then the .htaccess rewrite rules won't be flushed out * to disk. */ private static function apache_modules() { $mods = WP_CLI::get_config('apache_modules'); if ( !empty( $mods ) && !function_exists( 'apache_get_modules' ) ) { global $is_apache; $is_apache = true; // needed for get_home_path() and .htaccess location $_SERVER['SCRIPT_FILENAME'] = ABSPATH; function apache_get_modules() { return WP_CLI::get_config( 'apache_modules' ); } } } /** * Display a warning if --skip-plugins or --skip-themes are in use. * * Skipping the loading of plugins or themes can mean some rewrite rules * are unregistered, which may cause erroneous behavior. */ private static function check_skip_plugins_themes() { $skipped = array(); if ( WP_CLI::get_config( 'skip-plugins' ) ) { $skipped[] = 'plugins'; } if ( WP_CLI::get_config( 'skip-themes' ) ) { $skipped[] = 'themes'; } if ( empty( $skipped ) ) { return; } $skipped = implode( ' and ', $skipped ); WP_CLI::warning( sprintf( "Some rewrite rules may be missing because %s weren't loaded.", $skipped ) ); } } WP_CLI::add_command( 'rewrite', 'Rewrite_Command' ); obj_fields[] = 'enabled'; } parent::__construct(); $this->fetcher = new \WP_CLI\Fetchers\Theme; } protected function get_upgrader_class( $force ) { return $force ? '\\WP_CLI\\DestructiveThemeUpgrader' : 'Theme_Upgrader'; } /** * See the status of one or all themes. * * ## OPTIONS * * [] * : A particular theme to show the status for. * * ## EXAMPLES * * $ wp theme status twentysixteen * Theme twentysixteen details: * Name: Twenty Sixteen * Status: Inactive * Version: 1.2 * Author: the WordPress team */ public function status( $args ) { if ( isset( $args[0] ) ) { $theme = $this->fetcher->get_check( $args[0] ); $errors = $theme->errors(); if ( is_wp_error( $errors ) ) { $message = $errors->get_error_message(); WP_CLI::error( $message ); } } parent::status( $args ); } /** * Search the WordPress.org theme directory. * * Displays themes in the WordPress.org theme directory matching a given * search query. * * ## OPTIONS * * * : The string to search for. * * [--per-page=] * : Optional number of results to display. Defaults to 10. * * [--field=] * : Prints the value of a single field for each theme. * * [--fields=] * : Ask for specific fields from the API. Defaults to name,slug,author,rating. Acceptable values: * * **name**: Theme Name * **slug**: Theme Slug * **version**: Current Version Number * **author**: Theme Author * **preview_url**: Theme Preview URL * **screenshot_url**: Theme Screenshot URL * **rating**: Theme Rating * **num_ratings**: Number of Theme Ratings * **homepage**: Theme Author's Homepage * **description**: Theme Description * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * ## EXAMPLES * * $ wp theme search photo --per-page=6 * Success: Showing 6 of 203 themes. * +----------------------+----------------------+--------+ * | name | slug | rating | * +----------------------+----------------------+--------+ * | Photos | photos | 100 | * | Infinite Photography | infinite-photography | 100 | * | PhotoBook | photobook | 100 | * | BG Photo Frame | bg-photo-frame | 0 | * | fPhotography | fphotography | 0 | * | Photo Perfect | photo-perfect | 98 | * +----------------------+----------------------+--------+ */ public function search( $args, $assoc_args ) { parent::_search( $args, $assoc_args ); } protected function status_single( $args ) { $theme = $this->fetcher->get_check( $args[0] ); $status = $this->format_status( $this->get_status( $theme ), 'long' ); $version = $theme->get('Version'); if ( $this->has_update( $theme->get_stylesheet() ) ) $version .= ' (%gUpdate available%n)'; echo WP_CLI::colorize( \WP_CLI\Utils\mustache_render( 'theme-status.mustache', array( 'slug' => $theme->get_stylesheet(), 'status' => $status, 'version' => $version, 'name' => $theme->get('Name'), 'author' => $theme->get('Author'), ) ) ); } protected function get_all_items() { return $this->get_item_list(); } protected function get_status( $theme ) { if ( $this->is_active_theme( $theme ) ) { return 'active'; } else if ( $theme->get_stylesheet_directory() === get_template_directory() ) { return 'parent'; } else { return 'inactive'; } } /** * Activate a theme. * * ## OPTIONS * * * : The theme to activate. * * ## EXAMPLES * * $ wp theme activate twentysixteen * Success: Switched to 'Twenty Sixteen' theme. */ public function activate( $args = array() ) { $theme = $this->fetcher->get_check( $args[0] ); $errors = $theme->errors(); if ( is_wp_error( $errors ) ) { $message = $errors->get_error_message(); WP_CLI::error( $message ); } $name = $theme->get('Name'); if ( 'active' === $this->get_status( $theme ) ) { WP_CLI::warning( "The '$name' theme is already active." ); return; } if ( $theme->get_stylesheet() != $theme->get_template() && ! $this->fetcher->get( $theme->get_template() ) ) { WP_CLI::error( "The '{$theme->get_stylesheet()}' theme cannot be activated without its parent, '{$theme->get_template()}'." ); } switch_theme( $theme->get_template(), $theme->get_stylesheet() ); if ( $this->is_active_theme( $theme ) ) { WP_CLI::success( "Switched to '$name' theme." ); } else { WP_CLI::error( "Could not switch to '$name' theme." ); } } /** * Enable a theme on a WordPress multisite install. * * Permits theme to be activated from the dashboard of a site on a WordPress * multisite install. * * ## OPTIONS * * * : The theme to enable. * * [--network] * : If set, the theme is enabled for the entire network * * [--activate] * : If set, the theme is activated for the current site. Note that * the "network" flag has no influence on this. * * ## EXAMPLES * * # Enable theme * $ wp theme enable twentysixteen * Success: Enabled the 'Twenty Sixteen' theme. * * # Network enable theme * $ wp theme enable twentysixteen --network * Success: Network enabled the 'Twenty Sixteen' theme. * * # Network enable and activate theme for current site * $ wp theme enable twentysixteen --activate * Success: Enabled the 'Twenty Sixteen' theme. * Success: Switched to 'Twenty Sixteen' theme. */ public function enable( $args, $assoc_args ) { if ( ! is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } $theme = $this->fetcher->get_check( $args[0] ); $name = $theme->get( 'Name' ); # If the --network flag is set, we'll be calling the (get|update)_site_option functions $_site = ! empty( $assoc_args['network'] ) ? '_site' : ''; # Add the current theme to the allowed themes option or site option $allowed_themes = call_user_func( "get{$_site}_option", 'allowedthemes' ); if ( empty( $allowed_themes ) ) $allowed_themes = array(); $allowed_themes[ $theme->get_stylesheet() ] = true; call_user_func( "update{$_site}_option", 'allowedthemes', $allowed_themes ); if ( ! empty( $assoc_args['network'] ) ) WP_CLI::success( "Network enabled the '$name' theme." ); else WP_CLI::success( "Enabled the '$name' theme." ); # If the --activate flag is set, activate the theme for the current site if ( ! empty( $assoc_args['activate'] ) ) { $this->activate( $args ); } } /** * Disable a theme on a WordPress multisite install. * * Removes ability for a theme to be activated from the dashboard of a site * on a WordPress multisite install. * * ## OPTIONS * * * : The theme to disable. * * [--network] * : If set, the theme is disabled on the network level. Note that * individual sites may still have this theme enabled if it was * enabled for them independently. * * ## EXAMPLES * * # Disable theme * $ wp theme disable twentysixteen * Success: Disabled the 'Twenty Sixteen' theme. * * # Disable theme in network level * $ wp theme disable twentysixteen --network * Success: Network disabled the 'Twenty Sixteen' theme. */ public function disable( $args, $assoc_args ) { if ( ! is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } $theme = $this->fetcher->get_check( $args[0] ); $name = $theme->get( 'Name' ); # If the --network flag is set, we'll be calling the (get|update)_site_option functions $_site = ! empty( $assoc_args['network'] ) ? '_site' : ''; # Add the current theme to the allowed themes option or site option $allowed_themes = call_user_func( "get{$_site}_option", 'allowedthemes' ); if ( ! empty( $allowed_themes[ $theme->get_stylesheet() ] ) ) unset( $allowed_themes[ $theme->get_stylesheet() ] ); call_user_func( "update{$_site}_option", 'allowedthemes', $allowed_themes ); if ( ! empty( $assoc_args['network'] ) ) WP_CLI::success( "Network disabled the '$name' theme." ); else WP_CLI::success( "Disabled the '$name' theme." ); } private function is_active_theme( $theme ) { return $theme->get_stylesheet_directory() == get_stylesheet_directory(); } /** * Get the path to a theme or to the theme directory. * * ## OPTIONS * * [] * : The theme to get the path to. Path includes "style.css" file. * If not set, will return the path to the themes directory. * * [--dir] * : If set, get the path to the closest parent directory, instead of the * theme's "style.css" file. * * ## EXAMPLES * * # Get theme path * $ wp theme path * /var/www/example.com/public_html/wp-content/themes * * # Change directory to theme path * $ cd $(wp theme path) */ public function path( $args, $assoc_args ) { if ( empty( $args ) ) { $path = WP_CONTENT_DIR . '/themes'; } else { $theme = $this->fetcher->get_check( $args[0] ); $path = $theme->get_stylesheet_directory(); if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'dir' ) ) $path .= '/style.css'; } WP_CLI::line( $path ); } protected function install_from_repo( $slug, $assoc_args ) { $api = themes_api( 'theme_information', array( 'slug' => $slug ) ); if ( is_wp_error( $api ) ) { return $api; } if ( isset( $assoc_args['version'] ) ) { self::alter_api_response( $api, $assoc_args['version'] ); } if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) ) { $theme = wp_get_theme( $slug ); if ( $theme->exists() ) { // We know this will fail, so avoid a needless download of the package. return new WP_Error( 'already_installed', 'Theme already installed.' ); } // Clear cache so WP_Theme doesn't create a "missing theme" object. $cache_hash = md5( $theme->theme_root . '/' . $theme->stylesheet ); foreach( array( 'theme', 'screenshot', 'headers', 'page_templates' ) as $key ) { wp_cache_delete( $key . '-' . $cache_hash, 'themes' ); } } WP_CLI::log( sprintf( 'Installing %s (%s)', html_entity_decode( $api->name, ENT_QUOTES ), $api->version ) ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'version' ) != 'dev' ) { WP_CLI::get_http_cache_manager()->whitelist_package( $api->download_link, $this->item_type, $api->slug, $api->version ); } $result = $this->get_upgrader( $assoc_args )->install( $api->download_link ); return $result; } protected function get_item_list() { $items = array(); if ( is_multisite() ) { $site_enabled = get_option( 'allowedthemes' ); if ( empty( $site_enabled ) ) $site_enabled = array(); $network_enabled = get_site_option( 'allowedthemes' ); if ( empty( $network_enabled ) ) $network_enabled = array(); } foreach ( wp_get_themes() as $key => $theme ) { $file = $theme->get_stylesheet_directory(); $update_info = $this->get_update_info( $theme->get_stylesheet() ); $items[ $file ] = array( 'name' => $key, 'status' => $this->get_status( $theme ), 'update' => (bool) $update_info, 'update_version' => $update_info['new_version'], 'update_package' => $update_info['package'], 'version' => $theme->get('Version'), 'update_id' => $theme->get_stylesheet(), 'title' => $theme->get('Name'), 'description' => $theme->get('Description'), 'author' => $theme->get('Author'), ); if ( is_multisite() ) { if ( ! empty( $site_enabled[ $key ] ) && ! empty( $network_enabled[ $key ] ) ) $items[ $file ]['enabled'] = 'network,site'; elseif ( ! empty( $network_enabled[ $key ] ) ) $items[ $file ]['enabled'] = 'network'; elseif ( ! empty( $site_enabled[ $key ] ) ) $items[ $file ]['enabled'] = 'site'; else $items[ $file ]['enabled'] = 'no'; } } return $items; } protected function filter_item_list( $items, $args ) { $theme_files = array(); foreach ( $args as $arg ) { $theme_files[] = $this->fetcher->get_check( $arg )->get_stylesheet_directory(); } return \WP_CLI\Utils\pick_fields( $items, $theme_files ); } /** * Install a theme. * * ## OPTIONS * * ... * : A theme slug, the path to a local zip file, or URL to a remote zip file. * * [--version=] * : If set, get that particular version from wordpress.org, instead of the * stable version. * * [--force] * : If set, the command will overwrite any installed version of the theme, without prompting * for confirmation. * * [--activate] * : If set, the theme will be activated immediately after install. * * ## EXAMPLES * * # Install the latest version from wordpress.org and activate * $ wp theme install twentysixteen --activate * Installing Twenty Sixteen (1.2) * Downloading install package from http://downloads.wordpress.org/theme/twentysixteen.1.2.zip... * Unpacking the package... * Installing the theme... * Theme installed successfully. * Activating 'twentysixteen'... * Success: Switched to 'Twenty Sixteen' theme. * * # Install from a local zip file * $ wp theme install ../my-theme.zip * * # Install from a remote zip file * $ wp theme install http://s3.amazonaws.com/bucketname/my-theme.zip?AWSAccessKeyId=123&Expires=456&Signature=abcdef */ public function install( $args, $assoc_args ) { $theme_root = get_theme_root(); if ( $theme_root && ! is_dir( $theme_root ) ) { wp_mkdir_p( $theme_root ); register_theme_directory( $theme_root ); } parent::install( $args, $assoc_args ); } /** * Get details about a theme. * * ## OPTIONS * * * : The theme to get. * * [--field=] * : Instead of returning the whole theme, returns the value of a single field. * * [--fields=] * : Limit the output to specific fields. Defaults to all fields. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * $ wp theme get twentysixteen --fields=name,title,version * +---------+----------------+ * | Field | Value | * +---------+----------------+ * | name | Twenty Sixteen | * | title | Twenty Sixteen | * | version | 1.2 | * +---------+----------------+ */ public function get( $args, $assoc_args ) { $theme = $this->fetcher->get_check( $args[0] ); $errors = $theme->errors(); if ( is_wp_error( $errors ) ) { $message = $errors->get_error_message(); WP_CLI::error( $message ); } // WP_Theme object employs magic getter, unfortunately $theme_vars = array( 'name', 'title', 'version', 'parent_theme', 'template_dir', 'stylesheet_dir', 'template', 'stylesheet', 'screenshot', 'description', 'author', 'tags', 'theme_root', 'theme_root_uri', ); $theme_obj = new stdClass; foreach ( $theme_vars as $var ) { $theme_obj->$var = $theme->$var; } $theme_obj->description = wordwrap( $theme_obj->description ); if ( empty( $assoc_args['fields'] ) ) { $assoc_args['fields'] = $theme_vars; } $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $theme_obj ); } /** * Update one or more themes. * * ## OPTIONS * * [...] * : One or more themes to update. * * [--all] * : If set, all themes that have updates will be updated. * * [--exclude=] * : Comma separated list of theme names that should be excluded from updating. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - summary * --- * * [--version=] * : If set, the theme will be updated to the specified version. * * [--dry-run] * : Preview which themes would be updated. * * ## EXAMPLES * * # Update multiple themes * $ wp theme update twentyfifteen twentysixteen * Downloading update from https://downloads.wordpress.org/theme/twentyfifteen.1.5.zip... * Unpacking the update... * Installing the latest version... * Removing the old version of the theme... * Theme updated successfully. * Downloading update from https://downloads.wordpress.org/theme/twentysixteen.1.2.zip... * Unpacking the update... * Installing the latest version... * Removing the old version of the theme... * Theme updated successfully. * +---------------+-------------+-------------+---------+ * | name | old_version | new_version | status | * +---------------+-------------+-------------+---------+ * | twentyfifteen | 1.4 | 1.5 | Updated | * | twentysixteen | 1.1 | 1.2 | Updated | * +---------------+-------------+-------------+---------+ * Success: Updated 2 of 2 themes. * * # Exclude themes updates when bulk updating the themes * $ wp theme update --all --exclude=twentyfifteen * Downloading update from https://downloads.wordpress.org/theme/astra.1.0.5.1.zip... * Unpacking the update... * Installing the latest version... * Removing the old version of the theme... * Theme updated successfully. * Downloading update from https://downloads.wordpress.org/theme/twentyseventeen.1.2.zip... * Unpacking the update... * Installing the latest version... * Removing the old version of the theme... * Theme updated successfully. * +-----------------+----------+---------+----------------+ * | name | status | version | update_version | * +-----------------+----------+---------+----------------+ * | astra | inactive | 1.0.1 | 1.0.5.1 | * | twentyseventeen | inactive | 1.1 | 1.2 | * +-----------------+----------+---------+----------------+ * Success: Updated 2 of 2 themes. * * # Update all themes * $ wp theme update --all * * @alias upgrade */ public function update( $args, $assoc_args ) { if ( isset( $assoc_args['version'] ) ) { foreach ( $this->fetcher->get_many( $args ) as $theme ) { $r = delete_theme( $theme->stylesheet ); if ( is_wp_error( $r ) ) { WP_CLI::warning( $r ); } else { $assoc_args['force'] = true; $this->install( array( $theme->stylesheet ), $assoc_args ); } } } else { parent::update_many( $args, $assoc_args ); } } /** * Check if the theme is installed. * * Returns exit code 0 when installed, 1 when uninstalled. * * ## OPTIONS * * * : The theme to check. * * ## EXAMPLES * * # Check whether theme is installed; exit status 0 if installed, otherwise 1 * $ wp theme is-installed hello * $ echo $? * 1 * * @subcommand is-installed */ public function is_installed( $args, $assoc_args = array() ) { $theme = wp_get_theme( $args[0] ); if ( $theme->exists() ) { WP_CLI::halt( 0 ); } else { WP_CLI::halt( 1 ); } } /** * Delete a theme. * * Removes the theme from the filesystem. * * ## OPTIONS * * ... * : One or more themes to delete. * * ## EXAMPLES * * $ wp theme delete twentytwelve * Deleted 'twentytwelve' theme. * Success: Deleted 1 of 1 themes. * * @alias uninstall */ public function delete( $args ) { $successes = $errors = 0; foreach ( $this->fetcher->get_many( $args ) as $theme ) { $theme_slug = $theme->get_stylesheet(); if ( $this->is_active_theme( $theme ) ) { WP_CLI::warning( "Can't delete the currently active theme: $theme_slug" ); $errors++; continue; } $r = delete_theme( $theme_slug ); if ( is_wp_error( $r ) ) { WP_CLI::warning( $r ); $errors++; } else { WP_CLI::log( "Deleted '$theme_slug' theme." ); $successes++; } } if ( ! $this->chained_command ) { Utils\report_batch_operation_results( 'theme', 'delete', count( $args ), $successes, $errors ); } } /** * Get a list of themes. * * ## OPTIONS * * [--=] * : Filter results based on the value of a field. * * [--field=] * : Prints the value of a single field for each theme. * * [--fields=] * : Limit the output to specific object fields. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each theme: * * * name * * status * * update * * version * * These fields are optionally available: * * * update_version * * update_package * * update_id * * title * * description * * ## EXAMPLES * * # List themes * $ wp theme list --status=inactive --format=csv * name,status,update,version * twentyfourteen,inactive,none,1.7 * twentysixteen,inactive,available,1.1 * * @subcommand list */ public function list_( $_, $assoc_args ) { parent::_list( $_, $assoc_args ); } } exists() ) { return false; } return $theme; } } $_ ) { if ( $file === "$name.php" || ( $name && $file === $name ) || ( dirname( $file ) === $name && $name !== '.' ) ) { return (object) compact( 'name', 'file' ); } } return false; } } fetcher = new \WP_CLI\Fetchers\Plugin; } abstract protected function get_upgrader_class( $force ); abstract protected function get_item_list(); /** * @param array List of update candidates * @param array List of item names * @return array List of update candidates */ abstract protected function filter_item_list( $items, $args ); abstract protected function get_all_items(); abstract protected function get_status( $file ); abstract protected function status_single( $args ); abstract protected function install_from_repo( $slug, $assoc_args ); public function status( $args ) { // Force WordPress to check for updates call_user_func( $this->upgrade_refresh ); if ( empty( $args ) ) { $this->status_all(); } else { $this->status_single( $args ); } } private function status_all() { $items = $this->get_all_items(); $n = count( $items ); // Not interested in the translation, just the number logic \WP_CLI::log( sprintf( _n( "%d installed {$this->item_type}:", "%d installed {$this->item_type}s:", $n ), $n ) ); $padding = $this->get_padding($items); foreach ( $items as $file => $details ) { if ( $details['update'] ) { $line = ' %yU%n'; } else { $line = ' '; } $line .= $this->format_status( $details['status'], 'short' ); $line .= " " . str_pad( $details['name'], $padding ). "%n"; if ( !empty( $details['version'] ) ) { $line .= " " . $details['version']; } \WP_CLI::line( \WP_CLI::colorize( $line ) ); } \WP_CLI::line(); $this->show_legend( $items ); } private function get_padding( $items ) { $max_len = 0; foreach ( $items as $details ) { $len = strlen( $details['name'] ); if ( $len > $max_len ) { $max_len = $len; } } return $max_len; } private function show_legend( $items ) { $statuses = array_unique( wp_list_pluck( $items, 'status' ) ); $legend_line = array(); foreach ( $statuses as $status ) { $legend_line[] = sprintf( '%s%s = %s%%n', $this->get_color( $status ), $this->map['short'][ $status ], $this->map['long'][ $status ] ); } if ( in_array( true, wp_list_pluck( $items, 'update' ) ) ) $legend_line[] = '%yU = Update Available%n'; \WP_CLI::line( 'Legend: ' . \WP_CLI::colorize( implode( ', ', $legend_line ) ) ); } public function install( $args, $assoc_args ) { $successes = $errors = 0; foreach ( $args as $slug ) { if ( empty( $slug ) ) { WP_CLI::warning( "Ignoring ambigious empty slug value." ); continue; } $local_or_remote_zip_file = false; $result = false; // Check if a URL to a remote or local zip has been specified if ( strpos( $slug, '://' ) !== false || ( pathinfo( $slug, PATHINFO_EXTENSION ) === 'zip' && is_file( $slug ) ) ) { $local_or_remote_zip_file = $slug; } if ( $local_or_remote_zip_file ) { // Install from local or remote zip file $file_upgrader = $this->get_upgrader( $assoc_args ); $filter = false; if ( strpos( $local_or_remote_zip_file, '://' ) !== false && 'github.com' === parse_url( $local_or_remote_zip_file, PHP_URL_HOST ) ) { $filter = function( $source, $remote_source, $upgrader ) use ( $local_or_remote_zip_file ) { // Don't attempt to rename ZIPs uploaded to the releases page if ( preg_match( '#github\.com/([^/]+)/([^/]+)/releases/download/#', $local_or_remote_zip_file ) || preg_match( '#github\.com/([^/]+)/([^/]+)/raw/#', $local_or_remote_zip_file ) ) { return $source; } $branch_name = pathinfo( $local_or_remote_zip_file, PATHINFO_FILENAME ); // Always be attached "archive" into release tag and tag on github. if ( preg_match( '#github\.com/([^/]+)/([^/]+)/archive/#', $local_or_remote_zip_file ) ) { $path_array = explode( "/", pathinfo( $local_or_remote_zip_file, PATHINFO_DIRNAME) ); array_pop( $path_array ); $branch_name = end( $path_array ); } $new_path = str_replace( pathinfo( $source, PATHINFO_BASENAME ), $branch_name, $source ); if ( $GLOBALS['wp_filesystem']->move( $source, $new_path ) ) { WP_CLI::log( "Renamed Github-based project from '" . Utils\basename( $source ) . "' to '" . Utils\basename( $new_path ) . "'." ); return $new_path; } else { return new \WP_Error( 'wpcli_install_github', "Couldn't move Github-based project to appropriate directory." ); } return $source; }; add_filter( 'upgrader_source_selection', $filter, 10, 3 ); } if ( $file_upgrader->install( $local_or_remote_zip_file ) ) { $slug = $file_upgrader->result['destination_name']; $result = true; if ( $filter ) { remove_filter( 'upgrader_source_selection', $filter, 10 ); } $successes++; } else { $errors++; } } else { // Assume a plugin/theme slug from the WordPress.org repository has been specified $result = $this->install_from_repo( $slug, $assoc_args ); if ( is_null( $result ) ) { $errors++; } elseif ( is_wp_error( $result ) ) { $key = $result->get_error_code(); if ( in_array( $key, array( 'plugins_api_failed', 'themes_api_failed' ) ) && ! empty( $result->error_data[ $key ] ) && in_array( $result->error_data[ $key ], array( 'N;', 'b:0;' ) ) ) { \WP_CLI::warning( "Couldn't find '$slug' in the WordPress.org {$this->item_type} directory." ); $errors++; } else { \WP_CLI::warning( "$slug: " . $result->get_error_message() ); if ( 'already_installed' !== $key ) { $errors++; } } } else { $successes++; } } if ( $result ) { $this->chained_command = true; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate-network' ) ) { \WP_CLI::log( "Network-activating '$slug'..." ); $this->activate( array( $slug ), array( 'network' => true ) ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { \WP_CLI::log( "Activating '$slug'..." ); $this->activate( array( $slug ) ); } $this->chained_command = false; } } Utils\report_batch_operation_results( $this->item_type, 'install', count( $args ), $successes, $errors ); } /** * Prepare an API response for downloading a particular version of an item. * * @param object $response wordpress.org API response * @param string $version The desired version of the package */ protected static function alter_api_response( $response, $version ) { if ( $response->version == $version ) return; // WordPress.org forces https, but still sometimes returns http // See https://twitter.com/nacin/status/512362694205140992 $response->download_link = str_replace( 'http://', 'https://', $response->download_link ); list( $link ) = explode( $response->slug, $response->download_link ); if ( false !== strpos( $response->download_link, '/theme/' ) ) $download_type = 'theme'; else if ( false !== strpos( $response->download_link, '/plugin/' ) ) $download_type = 'plugin'; else $download_type = 'plugin/theme'; if ( 'dev' == $version ) { $response->download_link = $link . $response->slug . '.zip'; $response->version = 'Development Version'; } else { $response->download_link = $link . $response->slug . '.' . $version .'.zip'; $response->version = $version; // check if the requested version exists $response = wp_remote_head( $response->download_link ); $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== (int) $response_code ) { if ( is_wp_error( $response ) ) { $error_msg = $response->get_error_message(); } else { $error_msg = sprintf( 'HTTP code %d', $response_code ); } \WP_CLI::error( sprintf( "Can't find the requested %s's version %s in the WordPress.org %s repository (%s).", $download_type, $version, $download_type, $error_msg ) ); } } } protected function get_upgrader( $assoc_args ) { $upgrader_class = $this->get_upgrader_class( \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) ); return \WP_CLI\Utils\get_upgrader( $upgrader_class ); } protected function update_many( $args, $assoc_args ) { call_user_func( $this->upgrade_refresh ); if ( ! empty( $assoc_args['format'] ) && in_array( $assoc_args['format'], array( 'json', 'csv' ) ) ) { $logger = new \WP_CLI\Loggers\Quiet; \WP_CLI::set_logger( $logger ); } if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) { \WP_CLI::error( "Please specify one or more {$this->item_type}s, or use --all." ); } if ( Utils\get_flag_value( $assoc_args, 'minor' ) && Utils\get_flag_value( $assoc_args, 'patch' ) ) { WP_CLI::error( '--minor and --patch cannot be used together.' ); } $items = $this->get_item_list(); $errors = 0; if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) { $items = $this->filter_item_list( $items, $args ); $errors = count( $args ) - count( $items ); } $items_to_update = wp_list_filter( $items, array( 'update' => true ) ); if ( 'plugin' === $this->item_type && ( Utils\get_flag_value( $assoc_args, 'minor' ) || Utils\get_flag_value( $assoc_args, 'patch' ) ) ) { $type = Utils\get_flag_value( $assoc_args, 'minor' ) ? 'minor' : 'patch'; $items_to_update = self::get_minor_or_patch_updates( $items_to_update, $type ); } $exclude = WP_CLI\Utils\get_flag_value( $assoc_args, 'exclude' ); if ( isset( $exclude ) ) { $exclude_items = explode( ',', trim( $assoc_args['exclude'], ',' ) ); unset( $assoc_args['exclude'] ); foreach ( $exclude_items as $item ) { if ( 'plugin' === $this->item_type ) { $plugin = $this->fetcher->get( $item ); unset( $items_to_update[ $plugin->file ] ); } elseif ( 'theme' === $this->item_type ) { $theme_root = get_theme_root() . '/' . $item; unset( $items_to_update[ $theme_root ] ); } } } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run' ) ) { if ( empty( $items_to_update ) ) { \WP_CLI::log( "No {$this->item_type} updates available." ); if ( NULL !== $exclude ) { \WP_CLI::log( "Skipped updates for: $exclude" ); } return; } if ( ! empty( $assoc_args['format'] ) && in_array( $assoc_args['format'], array( 'json', 'csv' ) ) ) { \WP_CLI\Utils\format_items( $assoc_args['format'], $items_to_update, array( 'name', 'status', 'version', 'update_version' ) ); } else if ( ! empty( $assoc_args['format'] ) && 'summary' === $assoc_args['format'] ) { \WP_CLI::log( "Available {$this->item_type} updates:" ); foreach( $items_to_update as $item_to_update => $info ) { \WP_CLI::log( "{$info['title']} update from version {$info['version']} to version {$info['update_version']}" ); } } else { \WP_CLI::log( "Available {$this->item_type} updates:" ); \WP_CLI\Utils\format_items( 'table', $items_to_update, array( 'name', 'status', 'version', 'update_version' ) ); } if ( NULL !== $exclude ) { \WP_CLI::log( "Skipped updates for: $exclude" ); } return; } $result = array(); // Only attempt to update if there is something to update if ( !empty( $items_to_update ) ) { $cache_manager = \WP_CLI::get_http_cache_manager(); foreach ($items_to_update as $item) { $cache_manager->whitelist_package($item['update_package'], $this->item_type, $item['name'], $item['update_version']); } $upgrader = $this->get_upgrader( $assoc_args ); // Ensure the upgrader uses the download offer present in each item $transient_filter = function( $transient ) use ( $items_to_update ) { foreach( $items_to_update as $name => $item_data ) { if ( isset( $transient->response[ $name ] ) ) { $transient->response[ $name ]->new_version = $item_data['update_version']; $transient->response[ $name ]->package = $item_data['update_package']; } } return $transient; }; add_filter( 'site_transient_' . $this->upgrade_transient, $transient_filter, 999 ); $result = $upgrader->bulk_upgrade( wp_list_pluck( $items_to_update, 'update_id' ) ); remove_filter( 'site_transient_' . $this->upgrade_transient, $transient_filter, 999 ); } // Let the user know the results. $num_to_update = count( $items_to_update ); $num_updated = count( array_filter( $result ) ); if ( $num_to_update > 0 ) { if ( ! empty( $assoc_args['format'] ) && 'summary' === $assoc_args['format'] ) { foreach( $items_to_update as $item_to_update => $info ) { $message = $result[ $info['update_id'] ] !== null ? 'updated successfully' : 'did not update'; \WP_CLI::log( "{$info['title']} {$message} from version {$info['version']} to version {$info['update_version']}" ); } } else { $status = array(); foreach($items_to_update as $item_to_update => $info) { $status[$item_to_update] = array( 'name' => $info['name'], 'old_version' => $info['version'], 'new_version' => $info['update_version'], 'status' => $result[ $info['update_id'] ] !== null ? 'Updated' : 'Error', ); } $format = 'table'; if ( ! empty( $assoc_args['format'] ) && in_array( $assoc_args['format'], array( 'json', 'csv' ) ) ) { $format = $assoc_args['format']; } \WP_CLI\Utils\format_items( $format, $status, array( 'name', 'old_version', 'new_version', 'status' ) ); } } $total_updated = Utils\get_flag_value( $assoc_args, 'all' ) ? $num_to_update : count( $args ); Utils\report_batch_operation_results( $this->item_type, 'update', $total_updated, $num_updated, $errors ); if ( NULL !== $exclude ) { \WP_CLI::log( "Skipped updates for: $exclude" ); } } protected function _list( $_, $assoc_args ) { // Force WordPress to check for updates call_user_func( $this->upgrade_refresh ); $all_items = $this->get_all_items(); if ( !is_array( $all_items ) ) \WP_CLI::error( "No {$this->item_type}s found." ); foreach ( $all_items as $key => &$item ) { if ( empty( $item['version'] ) ) $item['version'] = ''; foreach ( $item as $field => &$value ) { if ( $value === true ) { $value = 'available'; } else if ( $value === false ) { $value = 'none'; } } foreach ( $this->obj_fields as $field ) { if ( isset( $assoc_args[$field] ) && $assoc_args[$field] != $item[$field] ) unset( $all_items[$key] ); } } $formatter = $this->get_formatter( $assoc_args ); $formatter->display_items( $all_items ); } /** * Check whether an item has an update available or not. * * @param string $slug The plugin/theme slug * * @return bool */ protected function has_update( $slug ) { $update_list = get_site_transient( $this->upgrade_transient ); return isset( $update_list->response[ $slug ] ); } /** * Get the available update info * * @param string $slug The plugin/theme slug * * @return array|null */ protected function get_update_info( $slug ) { $update_list = get_site_transient( $this->upgrade_transient ); if ( !isset( $update_list->response[ $slug ] ) ) return null; return (array) $update_list->response[ $slug ]; } private $map = array( 'short' => array( 'inactive' => 'I', 'active' => 'A', 'active-network' => 'N', 'must-use' => 'M', 'parent' => 'P', 'dropin' => 'D', ), 'long' => array( 'inactive' => 'Inactive', 'active' => 'Active', 'active-network' => 'Network Active', 'must-use' => 'Must Use', 'parent' => 'Parent', 'dropin' => 'Drop-In', ) ); protected function format_status( $status, $format ) { return $this->get_color( $status ) . $this->map[ $format ][ $status ]; } private function get_color( $status ) { static $colors = array( 'inactive' => '', 'active' => '%g', 'active-network' => '%g', 'must-use' => '%c', 'parent' => '%p', 'dropin' => '%B', ); return $colors[ $status ]; } /** * Get the minor or patch version for plugins with available updates * * @param array $items Plugins with updates. * @param string $type Either 'minor' or 'patch' * @return array */ private function get_minor_or_patch_updates( $items, $type ) { foreach ( $items as $i => $item ) { $wporg_url = sprintf( 'https://api.wordpress.org/plugins/info/1.0/%s.json', $item['name'] ); $response = Utils\http_request( 'GET', $wporg_url ); // Must not be hosted on wp.org if ( 20 != substr( $response->status_code, 0, 2 ) ) { unset( $items[ $i ] ); continue; } $data = json_decode( $response->body, true ); // No minor or patch versions to access if ( empty( $data['versions'] ) ) { unset( $items[ $i ] ); continue; } $update_version = $update_package = false; foreach ( $data['versions'] as $version => $download_link ) { $update_type = Utils\get_named_sem_ver( $version, $item['version'] ); // Compared version must be older if ( ! $update_type ) { continue; } // Only permit 'patch' for 'patch' if ( 'patch' === $type && 'patch' !== $update_type ) { continue; } // Permit 'minor' or 'patch' for 'minor' if ( 'minor' === $type && ! in_array( $update_type, array( 'minor', 'patch' ), true ) ) { continue; } if ( $update_version && ! Comparator::greaterThan( $version, $update_version ) ) { continue; } $update_version = $version; $update_package = $download_link; } // If there's not a matching version, bail on updates if ( ! $update_version ) { unset( $items[ $i ] ); continue; } $items[ $i ]['update_version'] = $update_version; $items[ $i ]['update_package'] = $update_package; } return $items; } /** * Search wordpress.org repo. * * @param array $args A arguments array containing the search term in the first element. * @param array $assoc_args Data passed in from command. */ protected function _search( $args, $assoc_args ) { $term = $args[0]; $defaults = array( 'per-page' => 10, 'page' => 1, 'fields' => implode( ',', array( 'name', 'slug', 'rating' ) ), ); $assoc_args = array_merge( $defaults, $assoc_args ); $fields = array(); foreach( explode( ',', $assoc_args['fields'] ) as $field ) { $fields[ $field ] = true; } $format = ! empty( $assoc_args['format'] ) ? $assoc_args['format'] : 'table'; $formatter = $this->get_formatter( $assoc_args ); $api_args = array( 'per_page' => (int) $assoc_args['per-page'], 'page' => (int) $assoc_args['page'], 'search' => $term, 'fields' => $fields, ); if ( 'plugin' == $this->item_type ) { $api = plugins_api( 'query_plugins', $api_args ); } else { $api = themes_api( 'query_themes', $api_args ); } if ( is_wp_error( $api ) ) \WP_CLI::error( $api->get_error_message() . __( ' Try again' ) ); $plural = $this->item_type . 's'; if ( ! isset( $api->$plural ) ) \WP_CLI::error( __( 'API error. Try Again.' ) ); $items = $api->$plural; if ( 'table' === $format ) { $count = \WP_CLI\Utils\get_flag_value( $api->info, 'results', 'unknown' ); \WP_CLI::success( sprintf( 'Showing %s of %s %s.', count( $items ), $count, $plural ) ); } $formatter->display_items( $items ); } protected function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->obj_fields, $this->item_type ); } } ...] * : One or more mods to get. * * [--field=] * : Returns the value of a single field. * * [--all] * : List all theme mods * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - json * - csv * - yaml * --- * * ## EXAMPLES * * # Get all theme mods. * $ wp theme mod get --all * +------------------+---------+ * | key | value | * +------------------+---------+ * | background_color | dd3333 | * | link_color | #dd9933 | * | main_text_color | #8224e3 | * +------------------+---------+ * * # Get single theme mod in JSON format. * $ wp theme mod get background_color --format=json * [{"key":"background_color","value":"dd3333"}] * * # Get value of a single theme mod. * $ wp theme mod get background_color --field=value * dd3333 * * # Get multiple theme mods. * $ wp theme mod get background_color header_textcolor * +------------------+--------+ * | key | value | * +------------------+--------+ * | background_color | dd3333 | * | header_textcolor | | * +------------------+--------+ */ public function get( $args = array(), $assoc_args = array() ) { if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) { WP_CLI::error( "You must specify at least one mod or use --all." ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) { $args = array(); } $list = array(); $mods = get_theme_mods(); if ( ! is_array( $mods ) ) { // if no mods are set (perhaps new theme), make sure foreach still works $mods = array(); } foreach ( $mods as $k => $v ) { // if mods were given, skip the others if ( ! empty( $args ) && ! in_array( $k, $args ) ) continue; if ( is_array( $v ) ) { $list[] = array( 'key' => $k, 'value' => '=>' ); foreach ( $v as $_k => $_v ) { $list[] = array( 'key' => " $_k", 'value' => $_v ); } } else { $list[] = array( 'key' => $k, 'value' => $v ); } } // For unset mods, show blank value foreach ( $args as $mod ) { if ( ! isset( $mods[ $mod ] ) ) { $list[] = array( 'key' => $mod, 'value' => '' ); } } $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields, 'thememods' ); $formatter->display_items( $list ); } /** * Remove one or more theme mods. * * ## OPTIONS * * [...] * : One or more mods to remove. * * [--all] * : Remove all theme mods. * * ## EXAMPLES * * # Remove all theme mods. * $ wp theme mod remove --all * Success: Theme mods removed. * * # Remove single theme mod. * $ wp theme mod remove background_color * Success: 1 mod removed. * * # Remove multiple theme mods. * $ wp theme mod remove background_color header_textcolor * Success: 2 mods removed. */ public function remove( $args = array(), $assoc_args = array() ) { if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) { WP_CLI::error( "You must specify at least one mod or use --all." ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) { remove_theme_mods(); WP_CLI::success( 'Theme mods removed.' ); return; } foreach ( $args as $mod ) { remove_theme_mod( $mod ); } $count = count( $args ); $success_message = ( 1 === $count ) ? '%d mod removed.' : '%d mods removed.'; WP_CLI::success( sprintf( $success_message, $count ) ); } /** * Set the value of a theme mod. * * ## OPTIONS * * * : The name of the theme mod to set or update. * * * : The new value. * * ## EXAMPLES * * # Set theme mod * $ wp theme mod set background_color 000000 * Success: Theme mod background_color set to 000000 */ public function set( $args = array(), $assoc_args = array() ) { list( $mod, $value ) = $args; set_theme_mod( $mod, $value ); if ( $value == get_theme_mod( $mod ) ) { WP_CLI::success( sprintf( "Theme mod %s set to %s.", $mod, $value ) ); } else { WP_CLI::success( sprintf( "Could not update theme mod %s.", $mod ) ); } } } fetcher = new \WP_CLI\Fetchers\Plugin; } protected function get_upgrader_class( $force ) { return $force ? '\\WP_CLI\\DestructivePluginUpgrader' : 'Plugin_Upgrader'; } /** * See the status of one or all plugins. * * ## OPTIONS * * [] * : A particular plugin to show the status for. * * ## EXAMPLES * * # Displays status of all plugins * $ wp plugin status * 5 installed plugins: * I akismet 3.1.11 * I easy-digital-downloads 2.5.16 * A theme-check 20160523.1 * I wen-logo-slider 2.0.3 * M ns-pack 1.0.0 * Legend: I = Inactive, A = Active, M = Must Use * * # Displays status of a plugin * $ wp plugin status theme-check * Plugin theme-check details: * Name: Theme Check * Status: Active * Version: 20160523.1 * Author: Otto42, pross * Description: A simple and easy way to test your theme for all the latest WordPress standards and practices. A great theme development tool! */ public function status( $args ) { parent::status( $args ); } /** * Search the WordPress.org plugin directory. * * Displays plugins in the WordPress.org plugin directory matching a given * search query. * * ## OPTIONS * * * : The string to search for. * * [--page=] * : Optional page to display. * --- * default: 1 * --- * * [--per-page=] * : Optional number of results to display. * --- * default: 10 * --- * * [--field=] * : Prints the value of a single field for each plugin. * * [--fields=] * : Ask for specific fields from the API. Defaults to name,slug,author_profile,rating. Acceptable values: * * **name**: Plugin Name * **slug**: Plugin Slug * **version**: Current Version Number * **author**: Plugin Author * **author_profile**: Plugin Author Profile * **contributors**: Plugin Contributors * **requires**: Plugin Minimum Requirements * **tested**: Plugin Tested Up To * **compatibility**: Plugin Compatible With * **rating**: Plugin Rating * **num_ratings**: Number of Plugin Ratings * **homepage**: Plugin Author's Homepage * **description**: Plugin's Description * **short_description**: Plugin's Short Description * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - count * - json * - yaml * --- * * ## EXAMPLES * * $ wp plugin search dsgnwrks --per-page=20 --format=json * Success: Showing 3 of 3 plugins. * [{"name":"DsgnWrks Instagram Importer Debug","slug":"dsgnwrks-instagram-importer-debug","rating":0},{"name":"DsgnWrks Instagram Importer","slug":"dsgnwrks-instagram-importer","rating":84},{"name":"DsgnWrks Twitter Importer","slug":"dsgnwrks-twitter-importer","rating":80}] * * $ wp plugin search dsgnwrks --fields=name,version,slug,rating,num_ratings * Success: Showing 3 of 3 plugins. * +-----------------------------------+---------+-----------------------------------+--------+-------------+ * | name | version | slug | rating | num_ratings | * +-----------------------------------+---------+-----------------------------------+--------+-------------+ * | DsgnWrks Instagram Importer Debug | 0.1.6 | dsgnwrks-instagram-importer-debug | 0 | 0 | * | DsgnWrks Instagram Importer | 1.3.7 | dsgnwrks-instagram-importer | 84 | 23 | * | DsgnWrks Twitter Importer | 1.1.1 | dsgnwrks-twitter-importer | 80 | 1 | * +-----------------------------------+---------+-----------------------------------+--------+-------------+ */ public function search( $args, $assoc_args ) { parent::_search( $args, $assoc_args ); } protected function status_single( $args ) { $plugin = $this->fetcher->get_check( $args[0] ); $file = $plugin->file; $details = $this->get_details( $file ); $status = $this->format_status( $this->get_status( $file ), 'long' ); $version = $details['Version']; if ( $this->has_update( $file ) ) $version .= ' (%gUpdate available%n)'; echo WP_CLI::colorize( \WP_CLI\Utils\mustache_render( 'plugin-status.mustache', array( 'slug' => Utils\get_plugin_name( $file ), 'status' => $status, 'version' => $version, 'name' => $details['Name'], 'author' => $details['Author'], 'description' => $details['Description'] ) ) ); } protected function get_all_items() { $items = $this->get_item_list(); foreach ( get_mu_plugins() as $file => $mu_plugin ) { $mu_version = ''; if ( ! empty( $mu_plugin['Version'] ) ) { $mu_version = $mu_plugin['Version']; } $items[ $file ] = array( 'name' => Utils\get_plugin_name( $file ), 'status' => 'must-use', 'update' => false, 'update_version' => NULL, 'update_package' => NULL, 'version' => $mu_version, 'update_id' => '', 'title' => '', 'description' => '', ); } $raw_items = get_dropins(); $raw_data = _get_dropins(); foreach( $raw_items as $name => $item_data ) { $description = ! empty( $raw_data[ $name ][0] ) ? $raw_data[ $name ][0] : ''; $items[ $name ] = array( 'name' => $name, 'title' => $item_data['Title'], 'description' => $description, 'status' => 'dropin', 'update' => false, 'update_version' => NULL, 'update_package' => NULL, 'update_id' => '', ); } return $items; } /** * Activate a plugin. * * ## OPTIONS * * [...] * : One or more plugins to activate. * * [--all] * : If set, all plugins will be activated. * * [--network] * : If set, the plugin will be activated for the entire multisite network. * * ## EXAMPLES * * # Activate plugin * $ wp plugin activate hello * Plugin 'hello' activated. * Success: Activated 1 of 1 plugins. * * # Activate plugin in entire multisite network * $ wp plugin activate hello --network * Plugin 'hello' network activated. * Success: Network activated 1 of 1 plugins. */ public function activate( $args, $assoc_args = array() ) { $network_wide = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ); $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); if ( $all ) { $args = array_map( function( $file ){ return Utils\get_plugin_name( $file ); }, array_keys( $this->get_all_plugins() ) ); } $successes = $errors = 0; $plugins = $this->fetcher->get_many( $args ); if ( count( $plugins ) < count( $args ) ) { $errors = count( $args ) - count( $plugins ); } foreach ( $plugins as $plugin ) { $status = $this->get_status( $plugin->file ); if ( $all && in_array( $status, array( 'active', 'active-network' ) ) ) { continue; } // Network-active is the highest level of activation status if ( 'active-network' === $status ) { WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." ); continue; } // Don't reactivate active plugins, but do let them become network-active if ( ! $network_wide && 'active' === $status ) { WP_CLI::warning( "Plugin '{$plugin->name}' is already active." ); continue; } // Plugins need to be deactivated before being network activated if ( $network_wide && 'active' === $status ) { deactivate_plugins( $plugin->file, false, false ); } activate_plugin( $plugin->file, '', $network_wide ); $this->active_output( $plugin->name, $plugin->file, $network_wide, 'activate' ); $successes++; } if ( ! $this->chained_command ) { $verb = $network_wide ? 'network activate' : 'activate'; Utils\report_batch_operation_results( 'plugin', $verb, count( $args ), $successes, $errors ); } } /** * Deactivate a plugin. * * ## OPTIONS * * [...] * : One or more plugins to deactivate. * * [--uninstall] * : Uninstall the plugin after deactivation. * * [--all] * : If set, all plugins will be deactivated. * * [--network] * : If set, the plugin will be deactivated for the entire multisite network. * * ## EXAMPLES * * # Deactivate plugin * $ wp plugin deactivate hello * Plugin 'hello' deactivated. * Success: Deactivated 1 of 1 plugins. */ public function deactivate( $args, $assoc_args = array() ) { $network_wide = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ); $disable_all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ); if ( $disable_all ) { $args = array_map( function( $file ){ return Utils\get_plugin_name( $file ); }, array_keys( $this->get_all_plugins() ) ); } $successes = $errors = 0; $plugins = $this->fetcher->get_many( $args ); if ( count( $plugins ) < count( $args ) ) { $errors = count( $args ) - count( $plugins ); } foreach ( $plugins as $plugin ) { $status = $this->get_status( $plugin->file ); if ( $disable_all && ! in_array( $status, array( 'active', 'active-network' ) ) ) { continue; } // Network active plugins must be explicitly deactivated if ( ! $network_wide && 'active-network' === $status ) { WP_CLI::warning( "Plugin '{$plugin->name}' is network active and must be deactivated with --network flag." ); $errors++; continue; } if ( ! in_array( $status, array( 'active', 'active-network' ) ) ) { WP_CLI::warning( "Plugin '{$plugin->name}' isn't active." ); continue; } deactivate_plugins( $plugin->file, false, $network_wide ); if ( ! is_network_admin() ) { update_option( 'recently_activated', array( $plugin->file => time() ) + (array) get_option( 'recently_activated' ) ); } else { update_site_option( 'recently_activated', array( $plugin->file => time() ) + (array) get_site_option( 'recently_activated' ) ); } $this->active_output( $plugin->name, $plugin->file, $network_wide, 'deactivate' ); $successes++; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'uninstall' ) ) { WP_CLI::log( "Uninstalling '{$plugin->name}'..." ); $this->chained_command = true; $this->uninstall( array( $plugin->name ) ); $this->chained_command = false; } } if ( ! $this->chained_command ) { $verb = $network_wide ? 'network deactivate' : 'deactivate'; Utils\report_batch_operation_results( 'plugin', $verb, count( $args ), $successes, $errors ); } } /** * Toggle a plugin's activation state. * * If the plugin is active, then it will be deactivated. If the plugin is * inactive, then it will be activated. * * ## OPTIONS * * ... * : One or more plugins to toggle. * * [--network] * : If set, the plugin will be toggled for the entire multisite network. * * ## EXAMPLES * * # Akismet is currently activated * $ wp plugin toggle akismet * Plugin 'akismet' deactivated. * Success: Toggled 1 of 1 plugins. * * # Akismet is currently deactivated * $ wp plugin toggle akismet * Plugin 'akismet' activated. * Success: Toggled 1 of 1 plugins. */ public function toggle( $args, $assoc_args = array() ) { $network_wide = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ); $successes = $errors = 0; $plugins = $this->fetcher->get_many( $args ); if ( count( $plugins ) < count( $args ) ) { $errors = count( $args ) - count( $plugins ); } $this->chained_command = true; foreach ( $plugins as $plugin ) { if ( $this->check_active( $plugin->file, $network_wide ) ) { $this->deactivate( array( $plugin->name ), $assoc_args ); } else { $this->activate( array( $plugin->name ), $assoc_args ); } $successes++; } $this->chained_command = false; Utils\report_batch_operation_results( 'plugin', 'toggle', count( $args ), $successes, $errors ); } /** * Get the path to a plugin or to the plugin directory. * * ## OPTIONS * * [] * : The plugin to get the path to. If not set, will return the path to the * plugins directory. * * [--dir] * : If set, get the path to the closest parent directory, instead of the * plugin file. * * ## EXAMPLES * * $ cd $(wp plugin path) && pwd * /var/www/wordpress/wp-content/plugins */ public function path( $args, $assoc_args ) { $path = untrailingslashit( WP_PLUGIN_DIR ); if ( !empty( $args ) ) { $plugin = $this->fetcher->get_check( $args[0] ); $path .= '/' . $plugin->file; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'dir' ) ) $path = dirname( $path ); } WP_CLI::line( $path ); } protected function install_from_repo( $slug, $assoc_args ) { $api = plugins_api( 'plugin_information', array( 'slug' => $slug ) ); if ( is_wp_error( $api ) ) { return $api; } if ( isset( $assoc_args['version'] ) ) { self::alter_api_response( $api, $assoc_args['version'] ); } $status = install_plugin_install_status( $api ); if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) && 'install' != $status['status'] ) { // We know this will fail, so avoid a needless download of the package. return new WP_Error( 'already_installed', 'Plugin already installed.' ); } WP_CLI::log( sprintf( 'Installing %s (%s)', html_entity_decode( $api->name, ENT_QUOTES ), $api->version ) ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'version' ) != 'dev' ) { WP_CLI::get_http_cache_manager()->whitelist_package( $api->download_link, $this->item_type, $api->slug, $api->version ); } $result = $this->get_upgrader( $assoc_args )->install( $api->download_link ); return $result; } /** * Update one or more plugins. * * ## OPTIONS * * [...] * : One or more plugins to update. * * [--all] * : If set, all plugins that have updates will be updated. * * [--exclude=] * : Comma separated list of plugin names that should be excluded from updating. * * [--minor] * : Only perform updates for minor releases (e.g. from 1.3 to 1.4 instead of 2.0) * * [--patch] * : Only perform updates for patch releases (e.g. from 1.3 to 1.3.3 instead of 1.4) * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - summary * --- * * [--version=] * : If set, the plugin will be updated to the specified version. * * [--dry-run] * : Preview which plugins would be updated. * * ## EXAMPLES * * $ wp plugin update bbpress --version=dev * Installing bbPress (Development Version) * Downloading install package from https://downloads.wordpress.org/plugin/bbpress.zip... * Unpacking the package... * Installing the plugin... * Removing the old version of the plugin... * Plugin updated successfully. * Success: Updated 1 of 2 plugins. * * $ wp plugin update --all * Enabling Maintenance mode... * Downloading update from https://downloads.wordpress.org/plugin/akismet.3.1.11.zip... * Unpacking the update... * Installing the latest version... * Removing the old version of the plugin... * Plugin updated successfully. * Downloading update from https://downloads.wordpress.org/plugin/nginx-champuru.3.2.0.zip... * Unpacking the update... * Installing the latest version... * Removing the old version of the plugin... * Plugin updated successfully. * Disabling Maintenance mode... * +------------------------+-------------+-------------+---------+ * | name | old_version | new_version | status | * +------------------------+-------------+-------------+---------+ * | akismet | 3.1.3 | 3.1.11 | Updated | * | nginx-cache-controller | 3.1.1 | 3.2.0 | Updated | * +------------------------+-------------+-------------+---------+ * Success: Updated 2 of 2 plugins. * * $ wp plugin update --all --exclude=akismet * Enabling Maintenance mode... * Downloading update from https://downloads.wordpress.org/plugin/nginx-champuru.3.2.0.zip... * Unpacking the update... * Installing the latest version... * Removing the old version of the plugin... * Plugin updated successfully. * Disabling Maintenance mode... * +------------------------+-------------+-------------+---------+ * | name | old_version | new_version | status | * +------------------------+-------------+-------------+---------+ * | nginx-cache-controller | 3.1.1 | 3.2.0 | Updated | * +------------------------+-------------+-------------+---------+ * * @alias upgrade */ public function update( $args, $assoc_args ) { if ( isset( $assoc_args['version'] ) ) { foreach ( $this->fetcher->get_many( $args ) as $plugin ) { $assoc_args['force'] = 1; $this->install( array( $plugin->name ), $assoc_args ); } } else { parent::update_many( $args, $assoc_args ); } } protected function get_item_list() { $items = $duplicate_names = array(); foreach ( $this->get_all_plugins() as $file => $details ) { $update_info = $this->get_update_info( $file ); $name = Utils\get_plugin_name( $file ); if ( ! isset( $duplicate_names[ $name ] ) ) { $duplicate_names[ $name ] = array(); } $duplicate_names[ $name ][] = $file; $items[ $file ] = array( 'name' => $name, 'status' => $this->get_status( $file ), 'update' => (bool) $update_info, 'update_version' => $update_info['new_version'], 'update_package' => $update_info['package'], 'version' => $details['Version'], 'update_id' => $file, 'title' => $details['Name'], 'description' => $details['Description'], ); } foreach( $duplicate_names as $name => $files ) { if ( count( $files ) <= 1 ) { continue; } foreach( $files as $file ) { $items[ $file ]['name'] = str_replace( '.' . pathinfo( $file, PATHINFO_EXTENSION ), '', $file ); } } return $items; } protected function filter_item_list( $items, $args ) { $basenames = wp_list_pluck( $this->fetcher->get_many( $args ), 'file' ); return \WP_CLI\Utils\pick_fields( $items, $basenames ); } /** * Install a plugin. * * ## OPTIONS * * ... * : A plugin slug, the path to a local zip file, or URL to a remote zip file. * * [--version=] * : If set, get that particular version from wordpress.org, instead of the * stable version. * * [--force] * : If set, the command will overwrite any installed version of the plugin, without prompting * for confirmation. * * [--activate] * : If set, the plugin will be activated immediately after install. * * [--activate-network] * : If set, the plugin will be network activated immediately after install * * ## EXAMPLES * * # Install the latest version from wordpress.org and activate * $ wp plugin install bbpress --activate * Installing bbPress (2.5.9) * Downloading install package from https://downloads.wordpress.org/plugin/bbpress.2.5.9.zip... * Using cached file '/home/vagrant/.wp-cli/cache/plugin/bbpress-2.5.9.zip'... * Unpacking the package... * Installing the plugin... * Plugin installed successfully. * Activating 'bbpress'... * Plugin 'bbpress' activated. * Success: Installed 1 of 1 plugins. * * # Install the development version from wordpress.org * $ wp plugin install bbpress --version=dev * Installing bbPress (Development Version) * Downloading install package from https://downloads.wordpress.org/plugin/bbpress.zip... * Unpacking the package... * Installing the plugin... * Plugin installed successfully. * Success: Installed 1 of 1 plugins. * * # Install from a local zip file * $ wp plugin install ../my-plugin.zip * Unpacking the package... * Installing the plugin... * Plugin installed successfully. * Success: Installed 1 of 1 plugins. * * # Install from a remote zip file * $ wp plugin install http://s3.amazonaws.com/bucketname/my-plugin.zip?AWSAccessKeyId=123&Expires=456&Signature=abcdef * Downloading install package from http://s3.amazonaws.com/bucketname/my-plugin.zip?AWSAccessKeyId=123&Expires=456&Signature=abcdef * Unpacking the package... * Installing the plugin... * Plugin installed successfully. * Success: Installed 1 of 1 plugins. * * # Update from a remote zip file * $ wp plugin install https://github.com/envato/wp-envato-market/archive/master.zip --force * Downloading install package from https://github.com/envato/wp-envato-market/archive/master.zip * Unpacking the package... * Installing the plugin... * Renamed Github-based project from 'wp-envato-market-master' to 'wp-envato-market'. * Plugin updated successfully * Success: Installed 1 of 1 plugins. * * # Forcefully re-install all installed plugins * $ wp plugin install $(wp plugin list --field=name) --force * Installing Akismet (3.1.11) * Downloading install package from https://downloads.wordpress.org/plugin/akismet.3.1.11.zip... * Unpacking the package... * Installing the plugin... * Removing the old version of the plugin... * Plugin updated successfully * Success: Installed 1 of 1 plugins. */ public function install( $args, $assoc_args ) { if ( ! is_dir( WP_PLUGIN_DIR ) ) { wp_mkdir_p( WP_PLUGIN_DIR ); } parent::install( $args, $assoc_args ); } /** * Get details about an installed plugin. * * ## OPTIONS * * * : The plugin to get. * * [--field=] * : Instead of returning the whole plugin, returns the value of a single field. * * [--fields=] * : Limit the output to specific fields. Defaults to all fields. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * $ wp plugin get bbpress --format=json * {"name":"bbpress","title":"bbPress","author":"The bbPress Contributors","version":"2.6-alpha","description":"bbPress is forum software with a twist from the creators of WordPress.","status":"active"} */ public function get( $args, $assoc_args ) { $plugin = $this->fetcher->get_check( $args[0] ); $file = $plugin->file; $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $file, false, false ); $plugin_obj = (object)array( 'name' => Utils\get_plugin_name( $file ), 'title' => $plugin_data['Name'], 'author' => $plugin_data['Author'], 'version' => $plugin_data['Version'], 'description' => wordwrap( $plugin_data['Description'] ), 'status' => $this->get_status( $file ), ); if ( empty( $assoc_args['fields'] ) ) { $plugin_array = get_object_vars( $plugin_obj ); $assoc_args['fields'] = array_keys( $plugin_array ); } $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $plugin_obj ); } /** * Uninstall a plugin. * * ## OPTIONS * * ... * : One or more plugins to uninstall. * * [--deactivate] * : Deactivate the plugin before uninstalling. Default behavior is to warn and skip if the plugin is active. * * [--skip-delete] * : If set, the plugin files will not be deleted. Only the uninstall procedure * will be run. * * ## EXAMPLES * * $ wp plugin uninstall hello * Uninstalled and deleted 'hello' plugin. * Success: Installed 1 of 1 plugins. */ public function uninstall( $args, $assoc_args = array() ) { $successes = $errors = 0; $plugins = $this->fetcher->get_many( $args ); foreach ( $plugins as $plugin ) { if ( is_plugin_active( $plugin->file ) && ! WP_CLI\Utils\get_flag_value( $assoc_args, 'deactivate' ) ) { WP_CLI::warning( "The '{$plugin->name}' plugin is active." ); $errors++; continue; } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'deactivate' ) ) { WP_CLI::log( "Deactivating '{$plugin->name}'..." ); $this->chained_command = true; $this->deactivate( array( $plugin->name ) ); $this->chained_command = false; } uninstall_plugin( $plugin->file ); if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-delete' ) && $this->_delete( $plugin ) ) { WP_CLI::log( "Uninstalled and deleted '$plugin->name' plugin." ); } else { WP_CLI::log( "Ran uninstall procedure for '$plugin->name' plugin without deleting." ); } $successes++; } if ( ! $this->chained_command ) { Utils\report_batch_operation_results( 'plugin', 'uninstall', count( $args ), $successes, $errors ); } } /** * Check if the plugin is installed. * * Returns exit code 0 when installed, 1 when uninstalled. * * ## OPTIONS * * * : The plugin to check. * * ## EXAMPLES * * # Check whether plugin is installed; exit status 0 if installed, otherwise 1 * $ wp plugin is-installed hello * $ echo $? * 1 * * @subcommand is-installed */ public function is_installed( $args, $assoc_args = array() ) { if ( $this->fetcher->get( $args[0] ) ) { WP_CLI::halt( 0 ); } else { WP_CLI::halt( 1 ); } } /** * Delete plugin files without deactivating or uninstalling. * * ## OPTIONS * * ... * : One or more plugins to delete. * * ## EXAMPLES * * # Delete plugin * $ wp plugin delete hello * Deleted 'hello' plugin. * Success: Deleted 1 of 1 plugins. * * # Delete inactive plugins * $ wp plugin delete $(wp plugin list --status=inactive --field=name) * Deleted 'tinymce-templates' plugin. * Success: Deleted 1 of 1 plugins. */ public function delete( $args, $assoc_args = array() ) { $successes = $errors = 0; foreach ( $this->fetcher->get_many( $args ) as $plugin ) { if ( $this->_delete( $plugin ) ) { WP_CLI::log( "Deleted '{$plugin->name}' plugin." ); $successes++; } else { $errors++; } } if ( ! $this->chained_command ) { Utils\report_batch_operation_results( 'plugin', 'delete', count( $args ), $successes, $errors ); } } /** * Get a list of plugins. * * Displays a list of the plugins installed on the site with activation * status, whether or not there's an update available, etc. * * Use `--status=dropin` to list installed dropins (e.g. `object-cache.php`). * * ## OPTIONS * * [--=] * : Filter results based on the value of a field. * * [--field=] * : Prints the value of a single field for each plugin. * * [--fields=] * : Limit the output to specific object fields. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - count * - json * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each plugin: * * * name * * status * * update * * version * * These fields are optionally available: * * * update_version * * update_package * * update_id * * title * * description * * ## EXAMPLES * * # List active plugins on the site. * $ wp plugin list --status=active --format=json * [{"name":"dynamic-hostname","status":"active","update":"none","version":"0.4.2"},{"name":"tinymce-templates","status":"active","update":"none","version":"4.4.3"},{"name":"wp-multibyte-patch","status":"active","update":"none","version":"2.4"},{"name":"wp-total-hacks","status":"active","update":"none","version":"2.0.1"}] * * # List plugins on each site in a network. * $ wp site list --field=url | xargs -I % wp plugin list --url=% * +---------+----------------+--------+---------+ * | name | status | update | version | * +---------+----------------+--------+---------+ * | akismet | active-network | none | 3.1.11 | * | hello | inactive | none | 1.6 | * +---------+----------------+--------+---------+ * +---------+----------------+--------+---------+ * | name | status | update | version | * +---------+----------------+--------+---------+ * | akismet | active-network | none | 3.1.11 | * | hello | inactive | none | 1.6 | * +---------+----------------+--------+---------+ * * @subcommand list */ public function list_( $_, $assoc_args ) { parent::_list( $_, $assoc_args ); } /* PRIVATES */ private function check_active( $file, $network_wide ) { $required = $network_wide ? 'active-network' : 'active'; return $required == $this->get_status( $file ); } private function active_output( $name, $file, $network_wide, $action ) { $network_wide = $network_wide || ( is_multisite() && is_network_only_plugin( $file ) ); $check = $this->check_active( $file, $network_wide ); if ( ( $action == "activate" ) ? $check : ! $check ) { if ( $network_wide ) WP_CLI::log( "Plugin '{$name}' network {$action}d." ); else WP_CLI::log( "Plugin '{$name}' {$action}d." ); } else { WP_CLI::warning( "Could not {$action} the '{$name}' plugin." ); } } protected function get_status( $file ) { if ( is_plugin_active_for_network( $file ) ) return 'active-network'; if ( is_plugin_active( $file ) ) return 'active'; return 'inactive'; } /** * Get the details of a plugin. * * @param object * @return array */ private function get_details( $file ) { $plugin_folder = get_plugins( '/' . plugin_basename( dirname( $file ) ) ); $plugin_file = Utils\basename( $file ); return $plugin_folder[$plugin_file]; } private function _delete( $plugin ) { $plugin_dir = dirname( $plugin->file ); if ( '.' == $plugin_dir ) $plugin_dir = $plugin->file; $path = path_join( WP_PLUGIN_DIR, $plugin_dir ); if ( \WP_CLI\Utils\is_windows() ) { // Handles plugins that are not in own folders // e.g. Hello Dolly -> plugins/hello.php if ( is_file( $path ) ) { $command = 'del /f /q '; } else { $command = 'rd /s /q '; } $path = str_replace( "/", "\\", $path ); } else { $command = 'rm -rf '; } return ! WP_CLI::launch( $command . $path ); } /** * Get all available plugins. * * Uses the same filter core uses in plugins.php to determine which plugins * should be available to manage through the WP_Plugins_List_Table class. * * @return array */ private function get_all_plugins() { return apply_filters( 'all_plugins', get_plugins() ); } } 'application/json'); $response = Utils\http_request( 'GET', $url, null, $headers, array( 'timeout' => 30 ) ); if ( 200 === $response->status_code ) { return $response->body; } else { WP_CLI::error( "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." ); } } private function get_download_offer( $locale ) { $out = unserialize( self::_read( 'https://api.wordpress.org/core/version-check/1.6/?locale=' . $locale ) ); $offer = $out['offers'][0]; if ( $offer['locale'] != $locale ) { return false; } return $offer; } /** * Verify WordPress files against WordPress.org's checksums. * * Downloads md5 checksums for the current version from WordPress.org, and * compares those checksums against the currently installed files. * * For security, avoids loading WordPress when verifying checksums. * * If you experience issues verifying from this command, ensure you are * passing the relevant `--locale` and `--version` arguments according to * the values from the `Dashboard->Updates` menu in the admin area of the * site. * * ## OPTIONS * * [--version=] * : Verify checksums against a specific version of WordPress. * * [--locale=] * : Verify checksums against a specific locale of WordPress. * * ## EXAMPLES * * # Verify checksums * $ wp core verify-checksums * Success: WordPress install verifies against checksums. * * # Verify checksums for given WordPress version * $ wp core verify-checksums --version=4.0 * Success: WordPress install verifies against checksums. * * # Verify checksums for given locale * $ wp core verify-checksums --locale=en_US * Success: WordPress install verifies against checksums. * * # Verify checksums for given locale * $ wp core verify-checksums --locale=ja * Warning: File doesn't verify against checksum: wp-includes/version.php * Warning: File doesn't verify against checksum: readme.html * Warning: File doesn't verify against checksum: wp-config-sample.php * Error: WordPress install doesn't verify against checksums. * * @when before_wp_load */ public function core( $args, $assoc_args ) { global $wp_version, $wp_local_package; if ( ! empty( $assoc_args['version'] ) ) { $wp_version = $assoc_args['version']; } if ( ! empty( $assoc_args['locale'] ) ) { $wp_local_package = $assoc_args['locale']; } if ( empty( $wp_version ) ) { $details = self::get_wp_details(); $wp_version = $details['wp_version']; if ( empty( $wp_local_package ) ) { $wp_local_package = $details['wp_local_package']; } } $checksums = self::get_core_checksums( $wp_version, ! empty( $wp_local_package ) ? $wp_local_package : 'en_US' ); if ( ! is_array( $checksums ) ) { WP_CLI::error( "Couldn't get checksums from WordPress.org." ); } $has_errors = false; foreach ( $checksums as $file => $checksum ) { // Skip files which get updated if ( 'wp-content' == substr( $file, 0, 10 ) ) { continue; } if ( ! file_exists( ABSPATH . $file ) ) { WP_CLI::warning( "File doesn't exist: {$file}" ); $has_errors = true; continue; } $md5_file = md5_file( ABSPATH . $file ); if ( $md5_file !== $checksum ) { WP_CLI::warning( "File doesn't verify against checksum: {$file}" ); $has_errors = true; } } $core_checksums_files = array_filter( array_keys( $checksums ), array( $this, 'only_core_files_filter' ) ); $core_files = $this->get_wp_core_files(); $additional_files = array_diff( $core_files, $core_checksums_files ); if ( ! empty( $additional_files ) ) { foreach ( $additional_files as $additional_file ) { WP_CLI::warning( "File should not exist: {$additional_file}" ); } } if ( ! $has_errors ) { WP_CLI::success( "WordPress install verifies against checksums." ); } else { WP_CLI::error( "WordPress install doesn't verify against checksums." ); } } private function get_wp_core_files() { $core_files = array(); try { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( ABSPATH, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ); foreach ( $files as $file_info ) { $pathname = substr( $file_info->getPathname(), strlen( ABSPATH ) ); if ( $file_info->isFile() && ( 0 === strpos( $pathname, 'wp-admin/' ) || 0 === strpos( $pathname, 'wp-includes/' ) ) ) { $core_files[] = str_replace( ABSPATH, '', $file_info->getPathname() ); } } } catch( Exception $e ) { WP_CLI::error( $e->getMessage() ); } return $core_files; } private function only_core_files_filter( $file ) { return ( 0 === strpos( $file, 'wp-admin/' ) || 0 === strpos( $file, 'wp-includes/' ) ); } /** * Get version information from `wp-includes/version.php`. * * @return array { * @type string $wp_version The WordPress version. * @type int $wp_db_version The WordPress DB revision. * @type string $tinymce_version The TinyMCE version. * @type string $wp_local_package The TinyMCE version. * } */ private static function get_wp_details() { $versions_path = ABSPATH . 'wp-includes/version.php'; if ( ! is_readable( $versions_path ) ) { WP_CLI::error( "This does not seem to be a WordPress install.\n" . "Pass --path=`path/to/wordpress` or run `wp core download`." ); } $version_content = file_get_contents( $versions_path, null, null, 6, 2048 ); $vars = array( 'wp_version', 'wp_db_version', 'tinymce_version', 'wp_local_package' ); $result = array(); foreach ( $vars as $var_name ) { $result[ $var_name ] = self::find_var( $var_name, $version_content ); } return $result; } /** * Search for the value assigned to variable `$var_name` in PHP code `$code`. * * This is equivalent to matching the `\$VAR_NAME = ([^;]+)` regular expression and returning * the first match either as a `string` or as an `integer` (depending if it's surrounded by * quotes or not). * * @param string $var_name Variable name to search for. * @param string $code PHP code to search in. * * @return int|string|null */ private static function find_var( $var_name, $code ) { $start = strpos( $code, '$' . $var_name . ' = ' ); if ( ! $start ) { return null; } $start = $start + strlen( $var_name ) + 3; $end = strpos( $code, ";", $start ); $value = substr( $code, $start, $end - $start ); if ( $value[0] = "'" ) { return trim( $value, "'" ); } else { return intval( $value ); } } /** * Security copy of the core function with Requests - Gets the checksums for the given version of WordPress. * * @param string $version Version string to query. * @param string $locale Locale to query. * @return bool|array False on failure. An array of checksums on success. */ private static function get_core_checksums( $version, $locale ) { $url = 'https://api.wordpress.org/core/checksums/1.0/?' . http_build_query( compact( 'version', 'locale' ), null, '&' ); $options = array( 'timeout' => 30 ); $headers = array( 'Accept' => 'application/json' ); $response = Utils\http_request( 'GET', $url, null, $headers, $options ); if ( ! $response->success || 200 != $response->status_code ) return false; $body = trim( $response->body ); $body = json_decode( $body, true ); if ( ! is_array( $body ) || ! isset( $body['checksums'] ) || ! is_array( $body['checksums'] ) ) return false; return $body['checksums']; } } * @author Chris Wanstrath * @link https://github.com/mustangostang/spyc/ * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen * @license http://www.opensource.org/licenses/mit-license.php MIT License * @package Spyc */ if (!function_exists('spyc_load')) { /** * Parses YAML to array. * @param string $string YAML string. * @return array */ function spyc_load ($string) { return Spyc::YAMLLoadString($string); } } if (!function_exists('spyc_load_file')) { /** * Parses YAML to array. * @param string $file Path to YAML file. * @return array */ function spyc_load_file ($file) { return Spyc::YAMLLoad($file); } } if (!function_exists('spyc_dump')) { /** * Dumps array to YAML. * @param array $data Array. * @return string */ function spyc_dump ($data) { return Spyc::YAMLDump($data, false, false, true); } } * @author Chris Wanstrath * @link https://github.com/mustangostang/spyc/ * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen * @license http://www.opensource.org/licenses/mit-license.php MIT License * @package Spyc */ if (!class_exists('Mustangostang\Spyc')) { require_once dirname(__FILE__) . '/src/Spyc.php'; } class_alias('Mustangostang\Spyc', 'Spyc'); require_once dirname(__FILE__) . '/includes/functions.php'; // Enable use of Spyc from command line // The syntax is the following: php Spyc.php spyc.yaml do { if (PHP_SAPI != 'cli') break; if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; $file = $argv[1]; echo json_encode (spyc_load_file ($file)); } while (0); ', $code); $f = fopen ($dest, 'w'); fwrite($f, $code); fclose ($f); print "Written to $dest.\n"; } * @author Chris Wanstrath * @link https://github.com/mustangostang/spyc/ * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen * @license http://www.opensource.org/licenses/mit-license.php MIT License * @package Spyc */ /** * The Simple PHP YAML Class. * * This class can be used to read a YAML file and convert its contents * into a PHP array. It currently supports a very limited subsection of * the YAML spec. * * Usage: * * $Spyc = new Spyc; * $array = $Spyc->load($file); * * or: * * $array = Spyc::YAMLLoad($file); * * or: * * $array = spyc_load_file($file); * * @package Spyc */ class Spyc { // SETTINGS const REMPTY = "\0\0\0\0\0"; /** * Setting this to true will force YAMLDump to enclose any string value in * quotes. False by default. * * @var bool */ public $setting_dump_force_quotes = false; /** * Setting this to true will forse YAMLLoad to use syck_load function when * possible. False by default. * @var bool */ public $setting_use_syck_is_possible = false; /**#@+ * @access private * @var mixed */ private $_dumpIndent; private $_dumpWordWrap; private $_containsGroupAnchor = false; private $_containsGroupAlias = false; private $path; private $result; private $LiteralPlaceHolder = '___YAML_Literal_Block___'; private $SavedGroups = array(); private $indent; /** * Path modifier that should be applied after adding current element. * @var array */ private $delayedPath = array(); /**#@+ * @access public * @var mixed */ public $_nodeId; /** * Load a valid YAML string to Spyc. * @param string $input * @return array */ public function load ($input) { return $this->_loadString($input); } /** * Load a valid YAML file to Spyc. * @param string $file * @return array */ public function loadFile ($file) { return $this->_load($file); } /** * Load YAML into a PHP array statically * * The load method, when supplied with a YAML stream (string or file), * will do its best to convert YAML in a file into a PHP array. Pretty * simple. * Usage: * * $array = Spyc::YAMLLoad('lucky.yaml'); * print_r($array); * * @access public * @return array * @param string $input Path of YAML file or string containing YAML */ public static function YAMLLoad($input) { $Spyc = new Spyc; return $Spyc->_load($input); } /** * Load a string of YAML into a PHP array statically * * The load method, when supplied with a YAML string, will do its best * to convert YAML in a string into a PHP array. Pretty simple. * * Note: use this function if you don't want files from the file system * loaded and processed as YAML. This is of interest to people concerned * about security whose input is from a string. * * Usage: * * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); * print_r($array); * * @access public * @return array * @param string $input String containing YAML */ public static function YAMLLoadString($input) { $Spyc = new Spyc; return $Spyc->_loadString($input); } /** * Dump YAML from PHP array statically * * The dump method, when supplied with an array, will do its best * to convert the array into friendly YAML. Pretty simple. Feel free to * save the returned string as nothing.yaml and pass it around. * * Oh, and you can decide how big the indent is and what the wordwrap * for folding is. Pretty cool -- just pass in 'false' for either if * you want to use the default. * * Indent's default is 2 spaces, wordwrap's default is 40 characters. And * you can turn off wordwrap by passing in 0. * * @access public * @return string * @param array|\stdClass $array PHP array * @param int $indent Pass in false to use the default, which is 2 * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) * @param bool $no_opening_dashes Do not start YAML file with "---\n" */ public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { $spyc = new Spyc; return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); } /** * Dump PHP array to YAML * * The dump method, when supplied with an array, will do its best * to convert the array into friendly YAML. Pretty simple. Feel free to * save the returned string as tasteful.yaml and pass it around. * * Oh, and you can decide how big the indent is and what the wordwrap * for folding is. Pretty cool -- just pass in 'false' for either if * you want to use the default. * * Indent's default is 2 spaces, wordwrap's default is 40 characters. And * you can turn off wordwrap by passing in 0. * * @access public * @return string * @param array $array PHP array * @param int $indent Pass in false to use the default, which is 2 * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) */ public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { // Dumps to some very clean YAML. We'll have to add some more features // and options soon. And better support for folding. // New features and options. if ($indent === false or !is_numeric($indent)) { $this->_dumpIndent = 2; } else { $this->_dumpIndent = $indent; } if ($wordwrap === false or !is_numeric($wordwrap)) { $this->_dumpWordWrap = 40; } else { $this->_dumpWordWrap = $wordwrap; } // New YAML document $string = ""; if (!$no_opening_dashes) $string = "---\n"; // Start at the base of the array and move through it. if ($array) { $array = (array)$array; $previous_key = -1; foreach ($array as $key => $value) { if (!isset($first_key)) $first_key = $key; $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); $previous_key = $key; } } return $string; } /** * Attempts to convert a key / value array item to YAML * @access private * @return string * @param $key The name of the key * @param $value The value of the item * @param $indent The indent of the current node */ private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { if(is_object($value)) $value = (array)$value; if (is_array($value)) { if (empty ($value)) return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); // It has children. What to do? // Make it the right kind of item $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array); // Add the indent $indent += $this->_dumpIndent; // Yamlize the array $string .= $this->_yamlizeArray($value,$indent); } elseif (!is_array($value)) { // It doesn't have children. Yip. $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); } return $string; } /** * Attempts to convert an array to YAML * @access private * @return string * @param $array The array you want to convert * @param $indent The indent of the current level */ private function _yamlizeArray($array,$indent) { if (is_array($array)) { $string = ''; $previous_key = -1; foreach ($array as $key => $value) { if (!isset($first_key)) $first_key = $key; $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); $previous_key = $key; } return $string; } else { return false; } } /** * Returns YAML from a key and a value * @access private * @return string * @param $key The name of the key * @param $value The value of the item * @param $indent The indent of the current node */ private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { // do some folding here, for blocks if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '%') !== false || strpos ($value, ' ') !== false || strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || substr ($value, -1, 1) == ':') ) { $value = $this->_doLiteralBlock($value,$indent); } else { $value = $this->_doFolding($value,$indent); } if ($value === array()) $value = '[ ]'; if ($value === "") $value = '""'; if (self::isTranslationWord($value)) { $value = $this->_doLiteralBlock($value, $indent); } if (trim ($value) != $value) $value = $this->_doLiteralBlock($value,$indent); if (is_bool($value)) { $value = $value ? "true" : "false"; } if ($value === null) $value = 'null'; if ($value === "'" . self::REMPTY . "'") $value = null; $spaces = str_repeat(' ',$indent); //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { // It's a sequence $string = $spaces.'- '.$value."\n"; } else { // if ($first_key===0) throw new \Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); // It's mapped if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } $string = rtrim ($spaces.$key.': '.$value)."\n"; } return $string; } /** * Creates a literal block for dumping * @access private * @return string * @param $value * @param $indent int The value of the indent */ private function _doLiteralBlock($value,$indent) { if ($value === "\n") return '\n'; if (strpos($value, "\n") === false && strpos($value, "'") === false) { return sprintf ("'%s'", $value); } if (strpos($value, "\n") === false && strpos($value, '"') === false) { return sprintf ('"%s"', $value); } $exploded = explode("\n",$value); $newValue = '|'; if (isset($exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) { $newValue = $exploded[0]; unset($exploded[0]); } $indent += $this->_dumpIndent; $spaces = str_repeat(' ',$indent); foreach ($exploded as $line) { $line = trim($line); if (strpos($line, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) { $line = substr($line, 1, -1); } $newValue .= "\n" . $spaces . ($line); } return $newValue; } /** * Folds a string of text, if necessary * @access private * @return string * @param $value The string you wish to fold */ private function _doFolding($value,$indent) { // Don't do anything if wordwrap is set to 0 if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { $indent += $this->_dumpIndent; $indent = str_repeat(' ',$indent); $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); $value = ">\n".$indent.$wrapped; } else { if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY) $value = '"' . $value . '"'; if (is_numeric($value) && is_string($value)) $value = '"' . $value . '"'; } return $value; } private function isTrueWord($value) { $words = self::getTranslations(array('true', 'on', 'yes', 'y')); return in_array($value, $words, true); } private function isFalseWord($value) { $words = self::getTranslations(array('false', 'off', 'no', 'n')); return in_array($value, $words, true); } private function isNullWord($value) { $words = self::getTranslations(array('null', '~')); return in_array($value, $words, true); } private function isTranslationWord($value) { return ( self::isTrueWord($value) || self::isFalseWord($value) || self::isNullWord($value) ); } /** * Coerce a string into a native type * Reference: http://yaml.org/type/bool.html * TODO: Use only words from the YAML spec. * @access private * @param $value The value to coerce */ private function coerceValue(&$value) { if (self::isTrueWord($value)) { $value = true; } else if (self::isFalseWord($value)) { $value = false; } else if (self::isNullWord($value)) { $value = null; } } /** * Given a set of words, perform the appropriate translations on them to * match the YAML 1.1 specification for type coercing. * @param $words The words to translate * @access private */ private static function getTranslations(array $words) { $result = array(); foreach ($words as $i) { $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i))); } return $result; } // LOADING FUNCTIONS private function _load($input) { $Source = $this->loadFromSource($input); return $this->loadWithSource($Source); } private function _loadString($input) { $Source = $this->loadFromString($input); return $this->loadWithSource($Source); } private function loadWithSource($Source) { if (empty ($Source)) return array(); if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { $array = syck_load (implode ("\n", $Source)); return is_array($array) ? $array : array(); } $this->path = array(); $this->result = array(); $cnt = count($Source); for ($i = 0; $i < $cnt; $i++) { $line = $Source[$i]; $this->indent = strlen($line) - strlen(ltrim($line)); $tempPath = $this->getParentPathByIndent($this->indent); $line = self::stripIndent($line, $this->indent); if (self::isComment($line)) continue; if (self::isEmpty($line)) continue; $this->path = $tempPath; $literalBlockStyle = self::startsLiteralBlock($line); if ($literalBlockStyle) { $line = rtrim ($line, $literalBlockStyle . " \n"); $literalBlock = ''; $line .= ' '.$this->LiteralPlaceHolder; $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); } $i--; } // Strip out comments if (strpos ($line, '#')) { $line = preg_replace('/\s*#([^"\']+)$/','',$line); } while (++$i < $cnt && self::greedilyNeedNextLine($line)) { $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); } $i--; $lineArray = $this->_parseLine($line); if ($literalBlockStyle) $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); $this->addArray($lineArray, $this->indent); foreach ($this->delayedPath as $indent => $delayedPath) $this->path[$indent] = $delayedPath; $this->delayedPath = array(); } return $this->result; } private function loadFromSource ($input) { if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) $input = file_get_contents($input); return $this->loadFromString($input); } private function loadFromString ($input) { $lines = explode("\n",$input); foreach ($lines as $k => $_) { $lines[$k] = rtrim ($_, "\r"); } return $lines; } /** * Parses YAML code and returns an array for a node * @access private * @return array * @param string $line A line from the YAML file */ private function _parseLine($line) { if (!$line) return array(); $line = trim($line); if (!$line) return array(); $array = array(); $group = $this->nodeContainsGroup($line); if ($group) { $this->addGroup($line, $group); $line = $this->stripGroup ($line, $group); } if ($this->startsMappedSequence($line)) return $this->returnMappedSequence($line); if ($this->startsMappedValue($line)) return $this->returnMappedValue($line); if ($this->isArrayElement($line)) return $this->returnArrayElement($line); if ($this->isPlainArray($line)) return $this->returnPlainArray($line); return $this->returnKeyValuePair($line); } /** * Finds the type of the passed value, returns the value as the new type. * @access private * @param string $value * @return mixed */ private function _toType($value) { if ($value === '') return ""; $first_character = $value[0]; $last_character = substr($value, -1, 1); $is_quoted = false; do { if (!$value) break; if ($first_character != '"' && $first_character != "'") break; if ($last_character != '"' && $last_character != "'") break; $is_quoted = true; } while (0); if ($is_quoted) { $value = str_replace('\n', "\n", $value); if ($first_character == "'") return strtr(substr ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\'')); return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\\\''=> '\'')); } if (strpos($value, ' #') !== false && !$is_quoted) $value = preg_replace('/\s+#(.+)$/','',$value); if ($first_character == '[' && $last_character == ']') { // Take out strings sequences and mappings $innerValue = trim(substr ($value, 1, -1)); if ($innerValue === '') return array(); $explode = $this->_inlineEscape($innerValue); // Propagate value array $value = array(); foreach ($explode as $v) { $value[] = $this->_toType($v); } return $value; } if (strpos($value,': ')!==false && $first_character != '{') { $array = explode(': ',$value); $key = trim($array[0]); array_shift($array); $value = trim(implode(': ',$array)); $value = $this->_toType($value); return array($key => $value); } if ($first_character == '{' && $last_character == '}') { $innerValue = trim(substr ($value, 1, -1)); if ($innerValue === '') return array(); // Inline Mapping // Take out strings sequences and mappings $explode = $this->_inlineEscape($innerValue); // Propagate value array $array = array(); foreach ($explode as $v) { $SubArr = $this->_toType($v); if (empty($SubArr)) continue; if (is_array ($SubArr)) { $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; } $array[] = $SubArr; } return $array; } if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { return null; } if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ $intvalue = (int)$value; if ($intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX) $value = $intvalue; return $value; } if ( is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) { // Hexadecimal value. return hexdec($value); } $this->coerceValue($value); if (is_numeric($value)) { if ($value === '0') return 0; if (rtrim ($value, 0) === $value) $value = (float)$value; return $value; } return $value; } /** * Used in inlines to check for more inlines or quoted strings * @access private * @return array */ private function _inlineEscape($inline) { // There's gotta be a cleaner way to do this... // While pure sequences seem to be nesting just fine, // pure mappings and mappings with sequences inside can't go very // deep. This needs to be fixed. $seqs = array(); $maps = array(); $saved_strings = array(); $saved_empties = array(); // Check for empty strings $regex = '/("")|(\'\')/'; if (preg_match_all($regex,$inline,$strings)) { $saved_empties = $strings[0]; $inline = preg_replace($regex,'YAMLEmpty',$inline); } unset($regex); // Check for strings $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; if (preg_match_all($regex,$inline,$strings)) { $saved_strings = $strings[0]; $inline = preg_replace($regex,'YAMLString',$inline); } unset($regex); // echo $inline; $i = 0; do { // Check for sequences while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { $seqs[] = $matchseqs[0]; $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); } // Check for mappings while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { $maps[] = $matchmaps[0]; $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); } if ($i++ >= 10) break; } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); $explode = explode(',',$inline); $explode = array_map('trim', $explode); $stringi = 0; $i = 0; while (1) { // Re-add the sequences if (!empty($seqs)) { foreach ($explode as $key => $value) { if (strpos($value,'YAMLSeq') !== false) { foreach ($seqs as $seqk => $seq) { $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); $value = $explode[$key]; } } } } // Re-add the mappings if (!empty($maps)) { foreach ($explode as $key => $value) { if (strpos($value,'YAMLMap') !== false) { foreach ($maps as $mapk => $map) { $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); $value = $explode[$key]; } } } } // Re-add the strings if (!empty($saved_strings)) { foreach ($explode as $key => $value) { while (strpos($value,'YAMLString') !== false) { $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); unset($saved_strings[$stringi]); ++$stringi; $value = $explode[$key]; } } } // Re-add the empties if (!empty($saved_empties)) { foreach ($explode as $key => $value) { while (strpos($value,'YAMLEmpty') !== false) { $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); $value = $explode[$key]; } } } $finished = true; foreach ($explode as $key => $value) { if (strpos($value,'YAMLSeq') !== false) { $finished = false; break; } if (strpos($value,'YAMLMap') !== false) { $finished = false; break; } if (strpos($value,'YAMLString') !== false) { $finished = false; break; } if (strpos($value,'YAMLEmpty') !== false) { $finished = false; break; } } if ($finished) break; $i++; if ($i > 10) break; // Prevent infinite loops. } return $explode; } private function literalBlockContinues ($line, $lineIndent) { if (!trim($line)) return true; if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; return false; } private function referenceContentsByAlias ($alias) { do { if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } $groupPath = $this->SavedGroups[$alias]; $value = $this->result; foreach ($groupPath as $k) { $value = $value[$k]; } } while (false); return $value; } private function addArrayInline ($array, $indent) { $CommonGroupPath = $this->path; if (empty ($array)) return false; foreach ($array as $k => $_) { $this->addArray(array($k => $_), $indent); $this->path = $CommonGroupPath; } return true; } private function addArray ($incoming_data, $incoming_indent) { // print_r ($incoming_data); if (count ($incoming_data) > 1) return $this->addArrayInline ($incoming_data, $incoming_indent); $key = key ($incoming_data); $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; if ($key === '__!YAMLZero') $key = '0'; if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. if ($key || $key === '' || $key === '0') { $this->result[$key] = $value; } else { $this->result[] = $value; end ($this->result); $key = key ($this->result); } $this->path[$incoming_indent] = $key; return; } $history = array(); // Unfolding inner array tree. $history[] = $_arr = $this->result; foreach ($this->path as $k) { $history[] = $_arr = $_arr[$k]; } if ($this->_containsGroupAlias) { $value = $this->referenceContentsByAlias($this->_containsGroupAlias); $this->_containsGroupAlias = false; } // Adding string or numeric key to the innermost level or $this->arr. if (is_string($key) && $key == '<<') { if (!is_array ($_arr)) { $_arr = array (); } $_arr = array_merge ($_arr, $value); } else if ($key || $key === '' || $key === '0') { if (!is_array ($_arr)) $_arr = array ($key=>$value); else $_arr[$key] = $value; } else { if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } } $reverse_path = array_reverse($this->path); $reverse_history = array_reverse ($history); $reverse_history[0] = $_arr; $cnt = count($reverse_history) - 1; for ($i = 0; $i < $cnt; $i++) { $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; } $this->result = $reverse_history[$cnt]; $this->path[$incoming_indent] = $key; if ($this->_containsGroupAnchor) { $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; if (is_array ($value)) { $k = key ($value); if (!is_int ($k)) { $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; } } $this->_containsGroupAnchor = false; } } private static function startsLiteralBlock ($line) { $lastChar = substr (trim($line), -1); if ($lastChar != '>' && $lastChar != '|') return false; if ($lastChar == '|') return $lastChar; // HTML tags should not be counted as literal blocks. if (preg_match ('#<.*?>$#', $line)) return false; return $lastChar; } private static function greedilyNeedNextLine($line) { $line = trim ($line); if (!strlen($line)) return false; if (substr ($line, -1, 1) == ']') return false; if ($line[0] == '[') return true; if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; return false; } private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { $line = self::stripIndent($line, $indent); if ($literalBlockStyle !== '|') { $line = self::stripIndent($line); } $line = rtrim ($line, "\r\n\t ") . "\n"; if ($literalBlockStyle == '|') { return $literalBlock . $line; } if (strlen($line) == 0) return rtrim($literalBlock, ' ') . "\n"; if ($line == "\n" && $literalBlockStyle == '>') { return rtrim ($literalBlock, " \t") . "\n"; } if ($line != "\n") $line = trim ($line, "\r\n ") . " "; return $literalBlock . $line; } function revertLiteralPlaceHolder ($lineArray, $literalBlock) { foreach ($lineArray as $k => $_) { if (is_array($_)) $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) $lineArray[$k] = rtrim ($literalBlock, " \r\n"); } return $lineArray; } private static function stripIndent ($line, $indent = -1) { if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); return substr ($line, $indent); } private function getParentPathByIndent ($indent) { if ($indent == 0) return array(); $linePath = $this->path; do { end($linePath); $lastIndentInParentPath = key($linePath); if ($indent <= $lastIndentInParentPath) array_pop ($linePath); } while ($indent <= $lastIndentInParentPath); return $linePath; } private function clearBiggerPathValues ($indent) { if ($indent == 0) $this->path = array(); if (empty ($this->path)) return true; foreach ($this->path as $k => $_) { if ($k > $indent) unset ($this->path[$k]); } return true; } private static function isComment ($line) { if (!$line) return false; if ($line[0] == '#') return true; if (trim($line, " \r\n\t") == '---') return true; return false; } private static function isEmpty ($line) { return (trim ($line) === ''); } private function isArrayElement ($line) { if (!$line || !is_scalar($line)) return false; if (substr($line, 0, 2) != '- ') return false; if (strlen ($line) > 3) if (substr($line,0,3) == '---') return false; return true; } private function isHashElement ($line) { return strpos($line, ':'); } private function isLiteral ($line) { if ($this->isArrayElement($line)) return false; if ($this->isHashElement($line)) return false; return true; } private static function unquote ($value) { if (!$value) return $value; if (!is_string($value)) return $value; if ($value[0] == '\'') return trim ($value, '\''); if ($value[0] == '"') return trim ($value, '"'); return $value; } private function startsMappedSequence ($line) { return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':'); } private function returnMappedSequence ($line) { $array = array(); $key = self::unquote(trim(substr($line,1,-1))); $array[$key] = array(); $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); return array($array); } private function checkKeysInValue($value) { if (strchr('[{"\'', $value[0]) === false) { if (strchr($value, ': ') !== false) { throw new \Exception('Too many keys: '.$value); } } } private function returnMappedValue ($line) { $this->checkKeysInValue($line); $array = array(); $key = self::unquote (trim(substr($line,0,-1))); $array[$key] = ''; return $array; } private function startsMappedValue ($line) { return (substr ($line, -1, 1) == ':'); } private function isPlainArray ($line) { return ($line[0] == '[' && substr ($line, -1, 1) == ']'); } private function returnPlainArray ($line) { return $this->_toType($line); } private function returnKeyValuePair ($line) { $array = array(); $key = ''; if (strpos ($line, ': ')) { // It's a key/value pair most likely // If the key is in double quotes pull it out if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { $value = trim(str_replace($matches[1],'',$line)); $key = $matches[2]; } else { // Do some guesswork as to the key and the value $explode = explode(': ', $line); $key = trim(array_shift($explode)); $value = trim(implode(': ', $explode)); $this->checkKeysInValue($value); } // Set the type of the value. Int, string, etc $value = $this->_toType($value); if ($key === '0') $key = '__!YAMLZero'; $array[$key] = $value; } else { $array = array ($line); } return $array; } private function returnArrayElement ($line) { if (strlen($line) <= 1) return array(array()); // Weird %) $array = array(); $value = trim(substr($line,1)); $value = $this->_toType($value); if ($this->isArrayElement($value)) { $value = $this->returnArrayElement($value); } $array[] = $value; return $array; } private function nodeContainsGroup ($line) { $symbolsForReference = 'A-z0-9_\-'; if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; return false; } private function addGroup ($line, $group) { if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); //print_r ($this->path); } private function stripGroup ($line, $group) { $line = trim(str_replace($group, '', $line)); return $line; } } The events to listen to, and their * associated handlers. */ public static function getSubscribedEvents() { return array( ScriptEvents::PRE_AUTOLOAD_DUMP => 'dump', ); } /** * Dump the event for discovery purposes. * * @param Event $event Event that was triggered. */ public static function dump(Event $event) { $composer = $event->getComposer(); $installationManager = $composer->getInstallationManager(); $repoManager = $composer->getRepositoryManager(); $localRepo = $repoManager->getLocalRepository(); $package = $composer->getPackage(); $config = $composer->getConfig(); // To enable class-based filtering, we force `--optimize` flag for our // custom autoloaders, so that all PSR-0 and PSR-4 namespaces are // iterated and translated to arrays of classes. $optimize = true; $vendorDir = $config->get('vendor-dir', Config::RELATIVE_PATHS); $defaultLocation = "{$vendorDir}/wp-cli/wp-cli/php/WP_CLI/AutoloadSplitter.php"; $suffix = $config->get('autoloader-suffix'); self::$extra = $event->getComposer() ->getPackage() ->getExtra(); $splitterLogic = self::getExtraKey(self::LOGIC_CLASS_KEY, 'WP_CLI\AutoloadSplitter'); $splitterLocation = self::getExtraKey(self::LOGIC_CLASS_LOCATION_KEY, $defaultLocation); $filePrefixTrue = self::getExtraKey(self::SPLIT_TARGET_PREFIX_TRUE_KEY, 'autoload_commands'); $filePrefixFalse = self::getExtraKey(self::SPLIT_TARGET_PREFIX_FALSE_KEY, 'autoload_framework'); if (!class_exists($splitterLogic)) { $splitterClassPath = sprintf('%s/%s', getcwd(), $splitterLocation); // Avoid proceeding if the splitter class file does not exist. if (!is_readable( $splitterClassPath)) { return; } include_once $splitterClassPath; } $generator = new AutoloadGenerator( $composer->getEventDispatcher(), $event->getIO(), $splitterLogic, $filePrefixTrue, $filePrefixFalse ); $generator->dump( $config, $localRepo, $package, $installationManager, 'composer', $optimize, $suffix ); } /** * Activate the Composer plugin. * * @param Composer $composer Reference to the Composer instance. * @param IOInterface $io Reference to the IO interface. */ public function activate(Composer $composer, IOInterface $io) { } /** * Get the value of a config key from the "Extra" section. * * @param string $key Key to look for. * @param string $fallback Fallback value to use if the key was not provided. * * @return string */ private static function getExtraKey($key, $fallback) { static $autosplitter = null; if (null === $autosplitter) { $autosplitter = isset(self::$extra[self::EXTRA_KEY]) ? self::$extra[self::EXTRA_KEY] : array(); } return isset($autosplitter[$key]) ? $autosplitter[$key] : $fallback; } } eventDispatcher = $eventDispatcher; $this->io = $io; $this->splitterLogic = $splitterLogic; $this->filePrefixTrue = $filePrefixTrue; $this->filePrefixFalse = $filePrefixFalse; parent::__construct($eventDispatcher); } public function setDevMode($devMode = true) { $this->devMode = (boolean)$devMode; } /** * Whether or not generated autoloader considers the class map * authoritative. * * @param bool $classMapAuthoritative */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = (boolean)$classMapAuthoritative; } /** * Whether or not generated autoloader considers APCu caching. * * @param bool $apcu */ public function setApcu($apcu) { $this->apcu = (boolean)$apcu; } /** * Set whether to run scripts or not * * @param bool $runScripts */ public function setRunScripts($runScripts = true) { $this->runScripts = (boolean)$runScripts; } public function dump( Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '' ) { if ($this->classMapAuthoritative) { // Force scanPsr0Packages when classmap is authoritative $scanPsr0Packages = true; } $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($config->get('vendor-dir')); // Do not remove double realpath() calls. // Fixes failing Windows realpath() implementation. // See https://bugs.php.net/bug.php?id=72738 $basePath = $filesystem->normalizePath(realpath(realpath(getcwd()))); $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir')))); $useGlobalIncludePath = (bool)$config->get('use-include-path'); $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; $targetDir = $vendorPath . '/' . $targetDir; $filesystem->ensureDirectoryExists($targetDir); $vendorPathCode = $filesystem->findShortestPathCode( realpath($targetDir), $vendorPath, true ); $vendorPathCode52 = str_replace('__DIR__', 'dirname(__FILE__)', $vendorPathCode); $vendorPathToTargetDirCode = $filesystem->findShortestPathCode( $vendorPath, realpath($targetDir), true ); $appBaseDirCode = $filesystem->findShortestPathCode( $vendorPath, $basePath, true ); $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode); $filePrefixes = array( $this->filePrefixTrue, $this->filePrefixFalse, ); $classmapFile = array(); foreach ($filePrefixes as $filePrefix) { $classmapFile[$filePrefix] = <<buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() ); $autoloads = $this->parseAutoloads($packageMap, $mainPackage); $blacklist = null; if (! empty($autoloads['exclude-from-classmap'])) { $blacklist = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; } // flatten array $classMap = array(); if ($scanPsr0Packages) { $namespacesToScan = array(); // Scan the PSR-0/4 directories for class files, and add them to the class map foreach (array('psr-0', 'psr-4') as $psrType) { foreach ($autoloads[$psrType] as $namespace => $paths) { $namespacesToScan[$namespace][] = array( 'paths' => $paths, 'type' => $psrType, ); } } krsort($namespacesToScan); foreach ($namespacesToScan as $namespace => $groups) { foreach ($groups as $group) { foreach ($group['paths'] as $dir) { $dir = $filesystem->normalizePath( $filesystem->isAbsolutePath($dir) ? $dir : $basePath . '/' . $dir ); if (! is_dir($dir)) { continue; } $namespaceFilter = $namespace === '' ? null : $namespace; $classMap = $this->addClassMapCode( $filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap ); } } } } foreach ($autoloads['classmap'] as $dir) { $classMap = $this->addClassMapCode( $filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap ); } ksort($classMap); foreach ($classMap as $class => $code) { $logic = new $this->splitterLogic(); $filePrefix = $logic($class, $code) ? $this->filePrefixTrue : $this->filePrefixFalse; $classmapFile[$filePrefix] .= ' ' . var_export($class, true) . ' => ' . $code; } foreach ($filePrefixes as $filePrefix) { $classmapFile[$filePrefix] .= ");\n"; } if (! $suffix) { if (! $config->get('autoloader-suffix') && is_readable($vendorPath . '/autoload.php') ) { $content = file_get_contents($vendorPath . '/autoload.php'); if (preg_match( '{ComposerAutoloaderInit([^:\s]+)::}', $content, $match )) { $suffix = $match[1]; } } if (! $suffix) { $suffix = $config->get('autoloader-suffix') ?: md5(uniqid('', true)); } } foreach ($filePrefixes as $filePrefix) { $fileSuffix = md5($filePrefix . $suffix); file_put_contents( "{$targetDir}/{$filePrefix}_classmap.php", $classmapFile[$filePrefix] ); file_put_contents( "{$vendorPath}/{$filePrefix}.php", $this->getCustomAutoloadFile( $filePrefix, $vendorPathToTargetDirCode, $fileSuffix ) ); file_put_contents( "{$targetDir}/{$filePrefix}_real.php", $this->getCustomAutoloadRealFile( $filePrefix, $fileSuffix, $useGlobalIncludePath, $prependAutoloader ) ); } } private function addClassMapCode( $filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array() ) { foreach ( $this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) { $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ", \n"; if (! isset($classMap[$class])) { $classMap[$class] = $pathCode; } elseif ($this->io && $classMap[$class] !== $pathCode && ! preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/')) ) { $this->io->writeError( 'Warning: Ambiguous class resolution, "' . $class . '"' . ' was found in both "' . str_replace(array('$vendorDir . \'', "',\n",), array($vendorPath, ''), $classMap[$class]) . '" and "' . $path . '", the first will be used.' ); } } return $classMap; } private function generateClassMap( $dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true ) { return ClassMapGenerator::createMap( $dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter ); } protected function getCustomAutoloadFile($filePrefix, $vendorPathToTargetDirCode, $suffix) { $lastChar = $vendorPathToTargetDirCode[strlen($vendorPathToTargetDirCode) - 1]; if ("'" === $lastChar || '"' === $lastChar ) { $vendorPathToTargetDirCode = substr($vendorPathToTargetDirCode, 0, -1) . "/{$filePrefix}_real.php{$lastChar}"; } else { $vendorPathToTargetDirCode .= " . '/{$filePrefix}_real.php'"; } return <<addClassMap(\$classMap); } CLASSMAP; if ($this->apcu) { $apcuPrefix = substr(base64_encode(md5(uniqid('', true), true)), 0, -3); $file .= <<setApcuPrefix('$apcuPrefix'); APCU; } if ($useGlobalIncludePath) { $file .= <<<'INCLUDEPATH' $loader->setUseIncludePath(true); INCLUDEPATH; } $file .= <<register($prependAutoloader); REGISTER_LOADER; $file .= << function () { if ( !wp_image_editor_supports() ) { WP_CLI::error( 'No support for generating images found. ' . 'Please install the Imagick or GD PHP extensions.' ); } } ) ); ...] * : One or more IDs of the attachments to regenerate. * * [--image_size=] * : Name of the image size to regenerate. Only thumbnails of this image size will be regenerated, thumbnails of other image sizes will not. * * [--skip-delete] * : Skip deletion of the original thumbnails. If your thumbnails are linked from sources outside your control, it's likely best to leave them around. Defaults to false. * * [--only-missing] * : Only generate thumbnails for images missing image sizes. * * [--yes] * : Answer yes to the confirmation message. Confirmation only shows when no IDs passed as arguments. * * ## EXAMPLES * * # Regenerate thumbnails for given attachment IDs. * $ wp media regenerate 123 124 125 * Found 3 images to regenerate. * 1/3 Regenerated thumbnails for "Vertical Image" (ID 123). * 2/3 Regenerated thumbnails for "Horizontal Image" (ID 124). * 3/3 Regenerated thumbnails for "Beautiful Picture" (ID 125). * Success: Regenerated 3 of 3 images. * * # Regenerate all thumbnails, without confirmation. * $ wp media regenerate --yes * Found 3 images to regenerate. * 1/3 Regenerated thumbnails for "Sydney Harbor Bridge" (ID 760). * 2/3 Regenerated thumbnails for "Boardwalk" (ID 757). * 3/3 Regenerated thumbnails for "Sunburst Over River" (ID 756). * Success: Regenerated 3 of 3 images. * * # Re-generate all thumbnails that have IDs between 1000 and 2000. * $ seq 1000 2000 | xargs wp media regenerate * Found 4 images to regenerate. * 1/4 Regenerated thumbnails for "Vertical Featured Image" (ID 1027). * 2/4 Regenerated thumbnails for "Horizontal Featured Image" (ID 1022). * 3/4 Regenerated thumbnails for "Unicorn Wallpaper" (ID 1045). * 4/4 Regenerated thumbnails for "I Am Worth Loving Wallpaper" (ID 1023). * Success: Regenerated 4 of 4 images. * * # Re-generate only the thumbnails of "large" image size for all images. * $ wp media regenerate --image_size=large * Do you really want to regenerate the "large" image size for all images? [y/n] y * Found 3 images to regenerate. * 1/3 Regenerated "large" thumbnail for "Yoogest Image Ever, Really" (ID 9999). * 2/3 No "large" thumbnail regeneration needed for "Snowflake" (ID 9998). * 3/3 Regenerated "large" thumbnail for "Even Yooger than the Yoogest Image Ever, Really" (ID 9997). * Success: Regenerated 3 of 3 images. */ function regenerate( $args, $assoc_args = array() ) { $assoc_args = wp_parse_args( $assoc_args, array( 'image_size' => '', ) ); $image_size = $assoc_args['image_size']; if ( $image_size && ! in_array( $image_size, get_intermediate_image_sizes(), true ) ) { WP_CLI::error( sprintf( 'Unknown image size "%s".', $image_size ) ); } if ( empty( $args ) ) { if ( $image_size ) { WP_CLI::confirm( sprintf( 'Do you really want to regenerate the "%s" image size for all images?', $image_size ), $assoc_args ); } else { WP_CLI::confirm( 'Do you really want to regenerate all images?', $assoc_args ); } } $skip_delete = \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-delete' ); $only_missing = \WP_CLI\Utils\get_flag_value( $assoc_args, 'only-missing' ); if ( $only_missing ) { $skip_delete = true; } $mime_types = array( 'image' ); if ( Utils\wp_version_compare( '4.7', '>=' ) ) { $mime_types[] = 'application/pdf'; } $query_args = array( 'post_type' => 'attachment', 'post__in' => $args, 'post_mime_type' => $mime_types, 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids' ); $images = new WP_Query( $query_args ); $count = $images->post_count; if ( !$count ) { WP_CLI::warning( 'No images found.' ); return; } WP_CLI::log( sprintf( 'Found %1$d %2$s to regenerate.', $count, _n( 'image', 'images', $count ) ) ); if ( $image_size ) { $image_size_filters = $this->add_image_size_filters( $image_size ); } $successes = $errors = $skips = 0; foreach ( $images->posts as $number => $id ) { $this->process_regeneration( $id, $skip_delete, $only_missing, $image_size, ( $number + 1 ) . '/' . $count, $successes, $errors, $skips ); } if ( $image_size ) { $this->remove_image_size_filters( $image_size_filters ); } Utils\report_batch_operation_results( 'image', 'regenerate', $count, $successes, $errors, $skips ); } /** * Create attachments from local files or URLs. * * ## OPTIONS * * ... * : Path to file or files to be imported. Supports the glob(3) capabilities of the current shell. * If file is recognized as a URL (for example, with a scheme of http or ftp), the file will be * downloaded to a temp file before being sideloaded. * * [--post_id=] * : ID of the post to attach the imported files to. * * [--title=] * : Attachment title (post title field). * * [--caption=<caption>] * : Caption for attachent (post excerpt field). * * [--alt=<alt_text>] * : Alt text for image (saved as post meta). * * [--desc=<description>] * : "Description" field (post content) of attachment post. * * [--skip-copy] * : If set, media files (local only) are imported to the library but not moved on disk. * * [--preserve-filetime] * : Use the file modified time as the post published & modified dates. * Remote files will always use the current time. * * [--featured_image] * : If set, set the imported image as the Featured Image of the post its attached to. * * [--porcelain] * : Output just the new attachment ID. * * ## EXAMPLES * * # Import all jpgs in the current user's "Pictures" directory, not attached to any post. * $ wp media import ~/Pictures/**\/*.jpg * Imported file '/home/person/Pictures/beautiful-youg-girl-in-ivy.jpg' as attachment ID 1751. * Imported file '/home/person/Pictures/fashion-girl.jpg' as attachment ID 1752. * Success: Imported 2 of 2 items. * * # Import a local image and set it to be the post thumbnail for a post. * $ wp media import ~/Downloads/image.png --post_id=123 --title="A downloaded picture" --featured_image * Imported file '/home/person/Downloads/image.png' as attachment ID 1753 and attached to post 123 as featured image. * Success: Imported 1 of 1 images. * * # Import a local image, but set it as the featured image for all posts. * # 1. Import the image and get its attachment ID. * # 2. Assign the attachment ID as the featured image for all posts. * $ ATTACHMENT_ID="$(wp media import ~/Downloads/image.png --porcelain)" * $ wp post list --post_type=post --format=ids | xargs -d ' ' -I % wp post meta add % _thumbnail_id $ATTACHMENT_ID * Success: Added custom field. * Success: Added custom field. * * # Import an image from the web. * $ wp media import http://s.wordpress.org/style/images/wp-header-logo.png --title='The WordPress logo' --alt="Semantic personal publishing" * Imported file 'http://s.wordpress.org/style/images/wp-header-logo.png' as attachment ID 1755. * Success: Imported 1 of 1 images. */ function import( $args, $assoc_args = array() ) { $assoc_args = wp_parse_args( $assoc_args, array( 'title' => '', 'caption' => '', 'alt' => '', 'desc' => '', ) ); // Assume the most generic term $noun = 'item'; // Current site's timezone offset. $gmt_offset = get_option( 'gmt_offset' ); // Use the noun `image` when sure the media file is an image if ( Utils\get_flag_value( $assoc_args, 'featured_image' ) || $assoc_args['alt'] ) { $noun = 'image'; } if ( isset( $assoc_args['post_id'] ) ) { if ( !get_post( $assoc_args['post_id'] ) ) { WP_CLI::warning( "Invalid --post_id" ); $assoc_args['post_id'] = false; } } else { $assoc_args['post_id'] = false; } $successes = $errors = 0; foreach ( $args as $file ) { $is_file_remote = parse_url( $file, PHP_URL_HOST ); $orig_filename = $file; $file_time = ''; if ( empty( $is_file_remote ) ) { if ( !file_exists( $file ) ) { WP_CLI::warning( "Unable to import file '$file'. Reason: File doesn't exist." ); $errors++; continue; } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-copy' ) ) { $tempfile = $file; } else { $tempfile = $this->make_copy( $file ); } $name = Utils\basename( $file ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'preserve-filetime' ) ) { $file_time = @filemtime( $file ); } } else { $tempfile = download_url( $file ); if ( is_wp_error( $tempfile ) ) { WP_CLI::warning( sprintf( "Unable to import file '%s'. Reason: %s", $file, implode( ', ', $tempfile->get_error_messages() ) ) ); $errors++; continue; } $name = strtok( Utils\basename( $file ), '?' ); } $file_array = array( 'tmp_name' => $tempfile, 'name' => $name, ); $post_array = array( 'post_title' => $assoc_args['title'], 'post_excerpt' => $assoc_args['caption'], 'post_content' => $assoc_args['desc'], ); if ( ! empty( $file_time ) ) { $post_array['post_date'] = gmdate( 'Y-m-d H:i:s', $file_time + ( $gmt_offset * HOUR_IN_SECONDS ) ); $post_array['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $file_time ); $post_array['post_modified'] = gmdate( 'Y-m-d H:i:s', $file_time + ( $gmt_offset * HOUR_IN_SECONDS ) ); $post_array['post_modified_gmt'] = gmdate( 'Y-m-d H:i:s', $file_time ); } $post_array = wp_slash( $post_array ); // use image exif/iptc data for title and caption defaults if possible if ( empty( $post_array['post_title'] ) || empty( $post_array['post_excerpt'] ) ) { // @codingStandardsIgnoreStart $image_meta = @wp_read_image_metadata( $tempfile ); // @codingStandardsIgnoreEnd if ( ! empty( $image_meta ) ) { if ( empty( $post_array['post_title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) { $post_array['post_title'] = $image_meta['title']; } if ( empty( $post_array['post_excerpt'] ) && trim( $image_meta['caption'] ) ) { $post_array['post_excerpt'] = $image_meta['caption']; } } } if ( empty( $post_array['post_title'] ) ) { $post_array['post_title'] = preg_replace( '/\.[^.]+$/', '', Utils\basename( $file ) ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-copy' ) ) { $wp_filetype = wp_check_filetype( $file, null ); $post_array['post_mime_type'] = $wp_filetype['type']; $post_array['post_status'] = 'inherit'; $success = wp_insert_attachment( $post_array, $file, $assoc_args['post_id'] ); if ( is_wp_error( $success ) ) { WP_CLI::warning( sprintf( "Unable to insert file '%s'. Reason: %s", $orig_filename, implode( ', ', $success->get_error_messages() ) ) ); $errors++; continue; } wp_update_attachment_metadata( $success, wp_generate_attachment_metadata( $success, $file ) ); } else { // Deletes the temporary file. $success = media_handle_sideload( $file_array, $assoc_args['post_id'], $assoc_args['title'], $post_array ); if ( is_wp_error( $success ) ) { WP_CLI::warning( sprintf( "Unable to import file '%s'. Reason: %s", $orig_filename, implode( ', ', $success->get_error_messages() ) ) ); $errors++; continue; } } // Set alt text if ( $assoc_args['alt'] ) { update_post_meta( $success, '_wp_attachment_image_alt', wp_slash( $assoc_args['alt'] ) ); } // Set as featured image, if --post_id and --featured_image are set if ( $assoc_args['post_id'] && \WP_CLI\Utils\get_flag_value( $assoc_args, 'featured_image' ) ) { update_post_meta( $assoc_args['post_id'], '_thumbnail_id', $success ); } $attachment_success_text = ''; if ( $assoc_args['post_id'] ) { $attachment_success_text = " and attached to post {$assoc_args['post_id']}"; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'featured_image' ) ) $attachment_success_text .= ' as featured image'; } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { WP_CLI::line( $success ); } else { WP_CLI::log( sprintf( "Imported file '%s' as attachment ID %d%s.", $orig_filename, $success, $attachment_success_text ) ); } $successes++; } // Report the result of the operation if ( ! Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { Utils\report_batch_operation_results( $noun, 'import', count( $args ), $successes, $errors ); } elseif ( $errors ) { WP_CLI::halt( 1 ); } } /** * List image sizes registered with WordPress. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a specific format * --- * default: table * options: * - table * - json * - csv * - yaml * - count * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each image size: * * name * * width * * height * * crop * * ## EXAMPLES * * # List all registered image sizes * $ wp media image-size * +---------------------------+-------+--------+-------+ * | name | width | height | crop | * +---------------------------+-------+--------+-------+ * | full | | | N/A | * | twentyfourteen-full-width | 1038 | 576 | hard | * | large | 1024 | 1024 | soft | * | medium_large | 768 | 0 | soft | * | medium | 300 | 300 | soft | * | thumbnail | 150 | 150 | hard | * +---------------------------+-------+--------+-------+ * * @subcommand image-size */ public function image_size( $args, $assoc_args ) { global $_wp_additional_image_sizes; $assoc_args = array_merge( array( 'fields' => 'name,width,height,crop' ), $assoc_args ); $sizes = array( array( 'name' => 'large', 'width' => intval( get_option( 'large_size_w' ) ), 'height' => intval( get_option( 'large_size_h' ) ), 'crop' => false !== get_option( 'large_crop' ) ? 'hard' : 'soft', ), array( 'name' => 'medium_large', 'width' => intval( get_option( 'medium_large_size_w' ) ), 'height' => intval( get_option( 'medium_large_size_h' ) ), 'crop' => false !== get_option( 'medium_large_crop' ) ? 'hard' : 'soft', ), array( 'name' => 'medium', 'width' => intval( get_option( 'medium_size_w' ) ), 'height' => intval( get_option( 'medium_size_h' ) ), 'crop' => false !== get_option( 'medium_crop' ) ? 'hard' : 'soft', ), array( 'name' => 'thumbnail', 'width' => intval( get_option( 'thumbnail_size_w' ) ), 'height' => intval( get_option( 'thumbnail_size_h' ) ), 'crop' => false !== get_option( 'thumbnail_crop' ) ? 'hard' : 'soft', ), ); if ( is_array( $_wp_additional_image_sizes ) ) { foreach( $_wp_additional_image_sizes as $size => $size_args ) { $crop = filter_var( $size_args['crop'], FILTER_VALIDATE_BOOLEAN ); $sizes[] = array( 'name' => $size, 'width' => $size_args['width'], 'height' => $size_args['height'], 'crop' => empty( $crop ) || is_array( $size_args['crop'] ) ? 'soft' : 'hard', ); } } usort( $sizes, function( $a, $b ){ if ( $a['width'] == $b['width'] ) { return 0; } return ( $a['width'] < $b['width'] ) ? 1 : -1; }); array_unshift( $sizes, array( 'name' => 'full', 'width' => '', 'height' => '', 'crop' => 'N/A', ) ); WP_CLI\Utils\format_items( $assoc_args['format'], $sizes, explode( ',', $assoc_args['fields'] ) ); } // wp_tempnam() inexplicably forces a .tmp extension, which spoils MIME type detection private function make_copy( $path ) { $dir = get_temp_dir(); $filename = Utils\basename( $path ); if ( empty( $filename ) ) $filename = time(); $filename = $dir . wp_unique_filename( $dir, $filename ); if ( !copy( $path, $filename ) ) WP_CLI::error( "Could not create temporary file for $path." ); return $filename; } private function process_regeneration( $id, $skip_delete, $only_missing, $image_size, $progress, &$successes, &$errors, &$skips ) { $title = get_the_title( $id ); if ( '' === $title ) { // If audio or video cover art then the id is the sub attachment id, which has no title. if ( metadata_exists( 'post', $id, '_cover_hash' ) ) { // Unfortunately the only way to get the attachment title would be to do a non-indexed query against the meta value of `_thumbnail_id`. So don't. $att_desc = sprintf( 'cover attachment (ID %d)', $id ); } else { $att_desc = sprintf( '"(no title)" (ID %d)', $id ); } } else { $att_desc = sprintf( '"%1$s" (ID %2$d)', $title, $id ); } $thumbnail_desc = $image_size ? sprintf( '"%s" thumbnail', $image_size ) : 'thumbnail'; $fullsizepath = get_attached_file( $id ); if ( false === $fullsizepath || !file_exists( $fullsizepath ) ) { WP_CLI::warning( "Can't find $att_desc." ); $errors++; return; } if ( ! $skip_delete ) { $this->remove_old_images( $id, $fullsizepath, $image_size ); } $is_pdf = 'application/pdf' === get_post_mime_type( $id ); $needs_regeneration = $this->needs_regeneration( $id, $fullsizepath, $is_pdf, $image_size, $skip_it ); if ( $skip_it ) { WP_CLI::log( "$progress Skipped $thumbnail_desc regeneration for $att_desc." ); $skips++; return; } if ( $only_missing && ! $needs_regeneration ) { WP_CLI::log( "$progress No $thumbnail_desc regeneration needed for $att_desc." ); $successes++; return; } $metadata = wp_generate_attachment_metadata( $id, $fullsizepath ); if ( is_wp_error( $metadata ) ) { WP_CLI::warning( $metadata->get_error_message() ); $errors++; return; } // Note it's possible for no metadata to be generated for PDFs if restricted to a specific image size. if ( empty( $metadata ) && ! ( $is_pdf && $image_size ) ) { WP_CLI::warning( "$progress Couldn't regenerate thumbnails for $att_desc." ); $errors++; return; } if ( $image_size ) { if ( $this->update_attachment_metadata_for_image_size( $id, $metadata, $image_size ) ) { WP_CLI::log( "$progress Regenerated $thumbnail_desc for $att_desc." ); } else { WP_CLI::log( "$progress No $thumbnail_desc regeneration needed for $att_desc." ); } } else { wp_update_attachment_metadata( $id, $metadata ); WP_CLI::log( "$progress Regenerated thumbnails for $att_desc." ); } $successes++; } private function remove_old_images( $att_id, $fullsizepath, $image_size ) { $metadata = wp_get_attachment_metadata( $att_id ); if ( ! is_array( $metadata ) ) { return; } if ( empty( $metadata['sizes'] ) ) { return; } if ( $image_size ) { if ( empty( $metadata['sizes'][ $image_size ] ) ) { return; } $metadata['sizes'] = array( $image_size => $metadata['sizes'][ $image_size ] ); } $dir_path = dirname( $fullsizepath ) . '/'; foreach ( $metadata['sizes'] as $size_info ) { $intermediate_path = $dir_path . $size_info['file']; if ( $intermediate_path === $fullsizepath ) continue; if ( file_exists( $intermediate_path ) ) unlink( $intermediate_path ); } } private function needs_regeneration( $att_id, $fullsizepath, $is_pdf, $image_size, &$skip_it ) { // Assume not skipping. $skip_it = false; $metadata = wp_get_attachment_metadata($att_id); if ( ! is_array( $metadata ) ) { if ( $is_pdf ) { $editor = wp_get_image_editor( $fullsizepath ); $no_pdf_editor = is_wp_error( $editor ); unset( $editor ); if ( $no_pdf_editor ) { // No PDF thumbnail generation available, so skip. $skip_it = true; return false; } // Assume it may be possible to regenerate the PDF thumbnails and allow processing to continue and possibly fail. return true; } // Assume it's not a standard image (eg an SVG) and skip. $skip_it = true; return false; } // Note that an attachment can have no sizes if it's on or below the thumbnail threshold. // Check whether there's new sizes or they've changed. $image_sizes = $this->get_intermediate_image_sizes_for_attachment( $fullsizepath, $is_pdf, $metadata ); if ( is_wp_error( $image_sizes ) ) { if ( $is_pdf && 'image_no_editor' === $image_sizes->get_error_code() ) { // No PDF thumbnail generation available, so skip. $skip_it = true; return false; } // Warn but assume it may be possible to regenerate and allow processing to continue and possibly fail. WP_CLI::warning( $image_sizes->get_error_message() ); return true; } if ( $image_size ) { if ( empty( $image_sizes[ $image_size ] ) ) { return false; } if ( empty( $metadata['sizes'][ $image_size ] ) ) { return true; } $metadata['sizes'] = array( $image_size => $metadata['sizes'][ $image_size ] ); } if ( $this->image_sizes_differ( $image_sizes, $metadata['sizes'] ) ) { return true; } $dir_path = dirname( $fullsizepath ) . '/'; // Check that the thumbnail files exist. foreach( $metadata['sizes'] as $size_info ) { $intermediate_path = $dir_path . $size_info['file']; if ( $intermediate_path === $fullsizepath ) continue; if ( ! file_exists( $intermediate_path ) ) { return true; } } return false; } // Whether there's new image sizes or the width/height of existing image sizes have changed. private function image_sizes_differ( $image_sizes, $meta_sizes ) { // Check if have new image size(s). if ( array_diff( array_keys( $image_sizes ), array_keys( $meta_sizes ) ) ) { return true; } // Check if image sizes have changed. foreach ( $image_sizes as $name => $image_size ) { if ( $image_size['width'] !== $meta_sizes[ $name ]['width'] || $image_size['height'] !== $meta_sizes[ $name ]['height'] ) { return true; } } return false; } // Like WP's get_intermediate_image_sizes(), but removes sizes that won't be generated for a particular attachment due to its being on or below their thresholds, // and returns associative array with size name => width/height entries, resolved to crop values if applicable. private function get_intermediate_image_sizes_for_attachment( $fullsizepath, $is_pdf, $metadata ) { // Need to get width, height of attachment for image_resize_dimensions(). $editor = wp_get_image_editor( $fullsizepath ); if ( is_wp_error( $editor ) ) { return $editor; } if ( is_wp_error( $result = $editor->load() ) ) { unset( $editor ); return $result; } list( $width, $height ) = array_values( $editor->get_size() ); unset( $editor ); $sizes = array(); foreach ( $this->get_intermediate_sizes( $is_pdf, $metadata ) as $name => $size ) { // Need to check destination and original width or height differ before calling image_resize_dimensions(), otherwise it will return non-false. if ( ( $width !== $size['width'] || $height !== $size['height'] ) && ( $dims = image_resize_dimensions( $width, $height, $size['width'], $size['height'], $size['crop'] ) ) ) { list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; $sizes[ $name ] = array( 'width' => $dst_w, 'height' => $dst_h ); } } return $sizes; } // Like WP's get_intermediate_image_sizes(), but returns associative array with name => size info entries (and caters for PDFs also). private function get_intermediate_sizes( $is_pdf, $metadata ) { if ( $is_pdf ) { // Copied from wp_generate_attachment_metadata() in "wp-admin/includes/image.php". $fallback_sizes = array( 'thumbnail', 'medium', 'large', ); $intermediate_image_sizes = apply_filters( 'fallback_intermediate_image_sizes', $fallback_sizes, $metadata ); } else { $intermediate_image_sizes = get_intermediate_image_sizes(); } // Adapted from wp_generate_attachment_metadata() in "wp-admin/includes/image.php". if ( function_exists( 'wp_get_additional_image_sizes' ) ) { $_wp_additional_image_sizes = wp_get_additional_image_sizes(); } else { // For WP < 4.7.0. global $_wp_additional_image_sizes; if ( ! $_wp_additional_image_sizes ) { $_wp_additional_image_sizes = array(); } } $sizes = array(); foreach ( $intermediate_image_sizes as $s ) { if ( isset( $_wp_additional_image_sizes[ $s ]['width'] ) ) { $sizes[ $s ]['width'] = (int) $_wp_additional_image_sizes[ $s ]['width']; } else { $sizes[ $s ]['width'] = (int) get_option( "{$s}_size_w" ); } if ( isset( $_wp_additional_image_sizes[ $s ]['height'] ) ) { $sizes[ $s ]['height'] = (int) $_wp_additional_image_sizes[ $s ]['height']; } else { $sizes[ $s ]['height'] = (int) get_option( "{$s}_size_h" ); } if ( isset( $_wp_additional_image_sizes[ $s ]['crop'] ) ) { $sizes[ $s ]['crop'] = (bool) $_wp_additional_image_sizes[ $s ]['crop']; } else { // Force PDF thumbnails to be soft crops. if ( $is_pdf && 'thumbnail' === $s ) { $sizes[ $s ]['crop'] = false; } else { $sizes[ $s ]['crop'] = (bool) get_option( "{$s}_crop" ); } } } if ( ! $is_pdf ) { $sizes = apply_filters( 'intermediate_image_sizes_advanced', $sizes, $metadata ); } return $sizes; } // Add filters to only process a particular intermediate image size in wp_generate_attachment_metadata(). private function add_image_size_filters( $image_size ) { $image_size_filters = array(); // For images. $image_size_filters['intermediate_image_sizes_advanced'] = function ( $sizes ) use ( $image_size ) { // $sizes is associative array of name => size info entries. if ( isset( $sizes[ $image_size ] ) ) { return array( $image_size => $sizes[ $image_size ] ); } return array(); }; // For PDF previews. $image_size_filters['fallback_intermediate_image_sizes'] = function ( $fallback_sizes ) use ( $image_size ) { // $fallback_sizes is indexed array of size names. if ( in_array( $image_size, $fallback_sizes, true ) ) { return array( $image_size ); } return array(); }; foreach ( $image_size_filters as $name => $filter ) { add_filter( $name, $filter, PHP_INT_MAX ); } return $image_size_filters; } // Remove above intermediate image size filters. private function remove_image_size_filters( $image_size_filters ) { foreach ( $image_size_filters as $name => $filter ) { remove_filter( $name, $filter, PHP_INT_MAX ); } } // Update attachment sizes metadata just for a particular intermediate image size. private function update_attachment_metadata_for_image_size( $id, $new_metadata, $image_size ) { $metadata = wp_get_attachment_metadata( $id ); if ( ! is_array( $metadata ) ) { return false; } // If have metadata for image_size. if ( ! empty( $new_metadata['sizes'][ $image_size ] ) ) { $metadata['sizes'][ $image_size ] = $new_metadata['sizes'][ $image_size ]; wp_update_attachment_metadata( $id, $metadata ); return true; } // Else remove unused metadata if any. if ( ! empty( $metadata['sizes'][ $image_size ] ) ) { unset( $metadata['sizes'][ $image_size ] ); wp_update_attachment_metadata( $id, $metadata ); // Treat removing unused metadata as no change. } return false; } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'cap', 'Capabilities_Command' ); WP_CLI::add_command( 'role', 'Role_Command' ); <?php /** * Manages user roles, including creating new roles and resetting to defaults. * * See references for [Roles and Capabilities](https://codex.wordpress.org/Roles_and_Capabilities) and [WP User class](https://codex.wordpress.org/Class_Reference/WP_User). * * ## EXAMPLES * * # List roles. * $ wp role list --fields=role --format=csv * role * administrator * editor * author * contributor * subscriber * * # Check to see if a role exists. * $ wp role exists editor * Success: Role with ID 'editor' exists. * * # Create a new role. * $ wp role create approver Approver * Success: Role with key 'approver' created. * * # Delete an existing role. * $ wp role delete approver * Success: Role with key 'approver' deleted. * * # Reset existing roles to their default capabilities. * $ wp role reset administrator author contributor * Success: Reset 3/3 roles. * * @package wp-cli */ class Role_Command extends WP_CLI_Command { private $fields = array( 'name', 'role' ); /** * List all roles. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each role: * * * name * * role * * There are no optional fields. * * ## EXAMPLES * * # List roles. * $ wp role list --fields=role --format=csv * role * administrator * editor * author * contributor * subscriber * * @subcommand list */ public function list_( $args, $assoc_args ) { global $wp_roles; $output_roles = array(); foreach ( $wp_roles->roles as $key => $role ) { $output_role = new stdClass; $output_role->name = $role['name']; $output_role->role = $key; $output_roles[] = $output_role; } $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields ); $formatter->display_items( $output_roles ); } /** * Check if a role exists. * * Exits with return code 0 if the role exists, 1 if it does not. * * ## OPTIONS * * <role-key> * : The internal name of the role. * * ## EXAMPLES * * # Check if a role exists. * $ wp role exists editor * Success: Role with ID 'editor' exists. */ public function exists( $args ) { global $wp_roles; if ( ! in_array($args[0], array_keys( $wp_roles->roles ) ) ) { WP_CLI::error( "Role with ID '$args[0]' does not exist." ); } WP_CLI::success( "Role with ID '$args[0]' exists." ); } /** * Create a new role. * * ## OPTIONS * * <role-key> * : The internal name of the role. * * <role-name> * : The publicly visible name of the role. * * [--clone=<role>] * : Clone capabilities from an existing role. * * ## EXAMPLES * * # Create role for Approver. * $ wp role create approver Approver * Success: Role with key 'approver' created. * * # Create role for Product Administrator. * $ wp role create productadmin "Product Administrator" * Success: Role with key 'productadmin' created. */ public function create( $args, $assoc_args ) { global $wp_roles; self::persistence_check(); $role_key = array_shift( $args ); $role_name = array_shift( $args ); if ( empty( $role_key ) || empty( $role_name ) ) { WP_CLI::error( "Can't create role, insufficient information provided."); } $capabilities = false; if ( ! empty( $assoc_args['clone'] ) ) { $role_obj = $wp_roles->get_role( $assoc_args['clone'] ); if ( ! $role_obj ) { WP_CLI::error( "'{$assoc_args['clone']}' role not found." ); } $capabilities = array_keys( $role_obj->capabilities ); } if ( add_role( $role_key, $role_name ) ) { if ( ! empty( $capabilities ) ) { $role_obj = $wp_roles->get_role( $role_key ); foreach( $capabilities as $cap ) { $role_obj->add_cap( $cap ); } WP_CLI::success( sprintf( "Role with key '%s' created. Cloned capabilities from '%s'.", $role_key, $assoc_args['clone'] ) ); } else { WP_CLI::success( sprintf( "Role with key '%s' created.", $role_key ) ); } } else { WP_CLI::error( "Role couldn't be created." ); } } /** * Delete an existing role. * * ## OPTIONS * * <role-key> * : The internal name of the role. * * ## EXAMPLES * * # Delete approver role. * $ wp role delete approver * Success: Role with key 'approver' deleted. * * # Delete productadmin role. * wp role delete productadmin * Success: Role with key 'productadmin' deleted. */ public function delete( $args ) { global $wp_roles; self::persistence_check(); $role_key = array_shift( $args ); if ( empty( $role_key ) || ! isset( $wp_roles->roles[$role_key] ) ) WP_CLI::error( "Role key not provided, or is invalid." ); remove_role( $role_key ); // Note: remove_role() doesn't indicate success or otherwise, so we have to // check ourselves if ( ! isset( $wp_roles->roles[$role_key] ) ) WP_CLI::success( sprintf( "Role with key '%s' deleted.", $role_key ) ); else WP_CLI::error( sprintf( "Role with key '%s' could not be deleted.", $role_key ) ); } /** * Reset any default role to default capabilities. * * ## OPTIONS * * [<role-key>...] * : The internal name of one or more roles to reset. * * [--all] * : If set, all default roles will be reset. * * ## EXAMPLES * * # Reset role. * $ wp role reset administrator author contributor * Success: Reset 1/3 roles. * * # Reset all default roles. * $ wp role reset --all * Success: All default roles reset. */ public function reset( $args, $assoc_args ) { self::persistence_check(); if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) WP_CLI::error( "Role key not provided, or is invalid." ); if ( ! function_exists( 'populate_roles' ) ) { require_once( ABSPATH.'wp-admin/includes/schema.php' ); } global $wp_roles; $all_roles = array_keys( $wp_roles->roles ); $preserve_args = $args; // Get our default roles. $default_roles = $preserve = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); $before = array(); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) { foreach( $default_roles as $role ) { $before[ $role ] = get_role( $role ); remove_role( $role ); $args[]= $role; } populate_roles(); $not_affected_roles = array_diff( $all_roles, $default_roles ); if ( ! empty( $not_affected_roles ) ) { foreach ( $not_affected_roles as $not_affected_role ) { WP_CLI::log( "Custom role '{$not_affected_role}' not affected." ); } } } else { foreach ( $args as $k => $role_key ) { $key = array_search( $role_key, $default_roles ); if ( false !== $key ) { unset( $preserve[ $key ] ); $before[ $role_key ] = get_role( $role_key ); remove_role( $role_key ); } else { unset( $args[ $k ] ); } } $not_affected_roles = array_diff( $preserve_args, $default_roles ); if ( ! empty( $not_affected_roles ) ) { foreach ( $not_affected_roles as $not_affected_role ) { WP_CLI::log( "Custom role '{$not_affected_role}' not affected." ); } } // No roles were unset, bail. if ( count( $default_roles ) == count( $preserve ) ) { WP_CLI::error( 'Must specify a default role to reset.' ); } // For the roles we're not resetting. foreach ( $preserve as $k => $role ) { /* save roles * if get_role is null * save role name for re-removal */ $roleobj = get_role( $role ); $preserve[ $k ] = is_null( $roleobj ) ? $role : $roleobj; remove_role( $role ); } // Put back all default roles and capabilities. populate_roles(); // Restore the preserved roles. foreach ( $preserve as $k => $roleobj ) { // Re-remove after populating. if ( is_a( $roleobj, 'WP_Role' ) ) { remove_role( $roleobj->name ); add_role( $roleobj->name, ucwords( $roleobj->name ), $roleobj->capabilities ); } else { // When not an object, that means the role didn't exist before. remove_role( $roleobj ); } } } $num_reset = 0; $args = array_unique( $args ); $num_to_reset = count( $args ); foreach( $args as $role_key ) { $after[ $role_key ] = get_role( $role_key ); if ( $after[ $role_key ] != $before[ $role_key ] ) { ++$num_reset; $restored_cap = array_diff_key( $after[ $role_key ]->capabilities, $before[ $role_key ]->capabilities ); $removed_cap = array_diff_key( $before[ $role_key ]->capabilities, $after[ $role_key ]->capabilities ); $restored_cap_count = count( $restored_cap ); $removed_cap_count = count( $removed_cap ); $restored_text = ( 1 === $restored_cap_count ) ? '%d capability' : '%d capabilities'; $removed_text = ( 1 === $removed_cap_count ) ? '%d capability' : '%d capabilities'; $message = "Restored ". $restored_text . " to and removed " . $removed_text . " from '%s' role."; WP_CLI::log( sprintf( $message, $restored_cap_count, $removed_cap_count, $role_key ) ); } else { WP_CLI::log( "No changes necessary for '{$role_key}' role." ); } } if ( $num_reset ) { if ( 1 === count( $args ) ) { WP_CLI::success( 'Role reset.' ); } else { WP_CLI::success( "{$num_reset} of {$num_to_reset} roles reset." ); } } else { if ( 1 === count( $args ) ) { WP_CLI::success( 'Role didn\'t need resetting.' ); } else { WP_CLI::success( 'No roles needed resetting.' ); } } } private static function persistence_check() { global $wp_roles; if ( !$wp_roles->use_db ) WP_CLI::error( "Role definitions are not persistent." ); } } <?php /** * Adds, removes, and lists capabilities of a user role. * * See references for [Roles and Capabilities](https://codex.wordpress.org/Roles_and_Capabilities) and [WP User class](https://codex.wordpress.org/Class_Reference/WP_User). * * ## EXAMPLES * * # Add 'spectate' capability to 'author' role. * $ wp cap add 'author' 'spectate' * Success: Added 1 capability to 'author' role. * * # Add all caps from 'editor' role to 'author' role. * $ wp cap list 'editor' | xargs wp cap add 'author' * Success: Added 24 capabilities to 'author' role. * * # Remove all caps from 'editor' role that also appear in 'author' role. * $ wp cap list 'author' | xargs wp cap remove 'editor' * Success: Removed 34 capabilities from 'editor' role. */ class Capabilities_Command extends WP_CLI_Command { private $fields = array( 'name' ); /** * List capabilities for a given role. * * ## OPTIONS * * <role> * : Key for the role. * * [--format=<format>] * : Render output in a particular format. * --- * default: list * options: * - list * - table * - csv * - json * - count * - yaml * --- * * ## EXAMPLES * * # Display alphabetical list of Contributor capabilities. * $ wp cap list 'contributor' | sort * delete_posts * edit_posts * level_0 * level_1 * read * * @subcommand list */ public function list_( $args, $assoc_args ) { $role_obj = self::get_role( $args[0] ); if ( 'list' === $assoc_args['format'] ) { foreach ( array_keys( $role_obj->capabilities ) as $cap ) { WP_CLI::line( $cap ); } } else { $output_caps = array(); foreach ( array_keys( $role_obj->capabilities ) as $cap ) { $output_cap = new stdClass; $output_cap->name = $cap; $output_caps[] = $output_cap; } $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields ); $formatter->display_items( $output_caps ); } } /** * Add capabilities to a given role. * * ## OPTIONS * * <role> * : Key for the role. * * <cap>... * : One or more capabilities to add. * * ## EXAMPLES * * # Add 'spectate' capability to 'author' role. * $ wp cap add author spectate * Success: Added 1 capability to 'author' role. */ public function add( $args ) { self::persistence_check(); $role = array_shift( $args ); $role_obj = self::get_role( $role ); $count = 0; foreach ( $args as $cap ) { if ( $role_obj->has_cap( $cap ) ) continue; $role_obj->add_cap( $cap ); $count++; } $message = ( 1 === $count ) ? "Added %d capability to '%s' role." : "Added %d capabilities to '%s' role."; WP_CLI::success( sprintf( $message, $count, $role ) ); } /** * Remove capabilities from a given role. * * ## OPTIONS * * <role> * : Key for the role. * * <cap>... * : One or more capabilities to remove. * * ## EXAMPLES * * # Remove 'spectate' capability from 'author' role. * $ wp cap remove author spectate * Success: Removed 1 capability from 'author' role. */ public function remove( $args ) { self::persistence_check(); $role = array_shift( $args ); $role_obj = self::get_role( $role ); $count = 0; foreach ( $args as $cap ) { if ( !$role_obj->has_cap( $cap ) ) continue; $role_obj->remove_cap( $cap ); $count++; } $message = ( 1 === $count ) ? "Removed %d capability from '%s' role." : "Removed %d capabilities from '%s' role."; WP_CLI::success( sprintf( $message, $count, $role ) ); } private static function get_role( $role ) { global $wp_roles; $role_obj = $wp_roles->get_role( $role ); if ( !$role_obj ) WP_CLI::error( "'$role' role not found." ); return $role_obj; } private static function persistence_check() { global $wp_roles; if ( !$wp_roles->use_db ) WP_CLI::error( "Role definitions are not persistent." ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'import', 'Import_Command' ); <?php class Import_Command extends WP_CLI_Command { var $processed_posts = array(); /** * Import content from a WXR file. * * Provides a command line interface to the WordPress Importer plugin, for * performing data migrations. * * ## OPTIONS * * <file>... * : Path to one or more valid WXR files for importing. Directories are also accepted. * * --authors=<authors> * : How the author mapping should be handled. Options are 'create', 'mapping.csv', or 'skip'. The first will create any non-existent users from the WXR file. The second will read author mapping associations from a CSV, or create a CSV for editing if the file path doesn't exist. The CSV requires two columns, and a header row like "old_user_login,new_user_login". The last option will skip any author mapping. * * [--skip=<data-type>] * : Skip importing specific data. Supported options are: 'attachment' and 'image_resize' (skip time-consuming thumbnail generation). * * ## EXAMPLES * * # Import content from a WXR file * $ wp import example.wordpress.2016-06-21.xml --authors=create * Starting the import process... * Processing post #1 ("Hello world!") (post_type: post) * -- 1 of 1 * -- Tue, 21 Jun 2016 05:31:12 +0000 * -- Imported post as post_id #1 * Success: Finished importing from 'example.wordpress.2016-06-21.xml' file. */ public function __invoke( $args, $assoc_args ) { $defaults = array( 'authors' => null, 'skip' => array(), ); $assoc_args = wp_parse_args( $assoc_args, $defaults ); if ( ! is_array( $assoc_args['skip'] ) ) { $assoc_args['skip'] = explode( ',', $assoc_args['skip'] ); } $importer = $this->is_importer_available(); if ( is_wp_error( $importer ) ) { WP_CLI::error( $importer ); } $this->add_wxr_filters(); WP_CLI::log( 'Starting the import process...' ); $new_args = array(); foreach( $args as $arg ) { if ( is_dir( $arg ) ) { $dir = WP_CLI\Utils\trailingslashit( $arg ); if ( ( $files = glob( $dir . '*.wxr' ) ) ) { $new_args = array_merge( $new_args, $files ); } if ( ( $files = glob( $dir . '*.xml' ) ) ) { $new_args = array_merge( $new_args, $files ); } } else { if ( file_exists( $arg ) ) { $new_args[] = $arg; } } } $args = $new_args; foreach ( $args as $file ) { if ( ! is_readable( $file ) ) { WP_CLI::warning( "Can't read '$file' file." ); } $ret = $this->import_wxr( $file, $assoc_args ); if ( is_wp_error( $ret ) ) { WP_CLI::error( $ret ); } else { WP_CLI::log(''); // WXR import ends with HTML, so make sure message is on next line WP_CLI::success( "Finished importing from '$file' file." ); } } } /** * Import a WXR file */ private function import_wxr( $file, $args ) { $wp_import = new WP_Import; $wp_import->processed_posts = $this->processed_posts; $import_data = $wp_import->parse( $file ); if ( is_wp_error( $import_data ) ) return $import_data; // Prepare the data to be used in process_author_mapping(); $wp_import->get_authors_from_import( $import_data ); // We no longer need the original data, so unset to avoid using excess // memory. unset( $import_data ); $author_data = array(); foreach ( $wp_import->authors as $wxr_author ) { $author = new \stdClass; // Always in the WXR $author->user_login = $wxr_author['author_login']; // Should be in the WXR; no guarantees if ( isset ( $wxr_author['author_email'] ) ) { $author->user_email = $wxr_author['author_email']; } if ( isset( $wxr_author['author_display_name'] ) ) $author->display_name = $wxr_author['author_display_name']; if ( isset( $wxr_author['author_first_name'] ) ) $author->first_name = $wxr_author['author_first_name']; if ( isset( $wxr_author['author_last_name'] ) ) $author->last_name = $wxr_author['author_last_name']; $author_data[] = $author; } // Build the author mapping $author_mapping = $this->process_author_mapping( $args['authors'], $author_data ); if ( is_wp_error( $author_mapping ) ) return $author_mapping; $author_in = wp_list_pluck( $author_mapping, 'old_user_login' ); $author_out = wp_list_pluck( $author_mapping, 'new_user_login' ); unset( $author_mapping, $author_data ); // $user_select needs to be an array of user IDs $user_select = array(); $invalid_user_select = array(); foreach ( $author_out as $author_login ) { $user = get_user_by( 'login', $author_login ); if ( $user ) $user_select[] = $user->ID; else $invalid_user_select[] = $author_login; } if ( ! empty( $invalid_user_select ) ) return new WP_Error( 'invalid-author-mapping', sprintf( "These user_logins are invalid: %s", implode( ',', $invalid_user_select ) ) ); unset( $author_out ); // Drive the import $wp_import->fetch_attachments = !in_array( 'attachment', $args['skip'] ); $_GET = array( 'import' => 'wordpress', 'step' => 2 ); $_POST = array( 'imported_authors' => $author_in, 'user_map' => $user_select, 'fetch_attachments' => $wp_import->fetch_attachments, ); if ( in_array( 'image_resize', $args['skip'] ) ) { add_filter( 'intermediate_image_sizes_advanced', array( $this, 'filter_set_image_sizes' ) ); } $GLOBALS['wp_cli_import_current_file'] = basename( $file ); $wp_import->import( $file ); $this->processed_posts += $wp_import->processed_posts; return true; } public function filter_set_image_sizes( $sizes ) { // Return null here to prevent the core image resizing logic from running. return null; } /** * Useful verbosity filters for the WXR importer */ private function add_wxr_filters() { add_filter( 'wp_import_posts', function( $posts ) { global $wpcli_import_counts; $wpcli_import_counts['current_post'] = 0; $wpcli_import_counts['total_posts'] = count( $posts ); return $posts; }, 10 ); add_filter( 'wp_import_post_comments', function( $comments, $post_id, $post ) { global $wpcli_import_counts; $wpcli_import_counts['current_comment'] = 0; $wpcli_import_counts['total_comments'] = count( $comments ); return $comments; }, 10, 3 ); add_filter( 'wp_import_post_data_raw', function( $post ) { global $wpcli_import_counts, $wp_cli_import_current_file; $wpcli_import_counts['current_post']++; WP_CLI::log(''); WP_CLI::log(''); WP_CLI::log( sprintf( 'Processing post #%d ("%s") (post_type: %s)', $post['post_id'], $post['post_title'], $post['post_type'] ) ); WP_CLI::log( sprintf( '-- %s of %s (in file %s)', number_format( $wpcli_import_counts['current_post'] ), number_format( $wpcli_import_counts['total_posts'] ), $wp_cli_import_current_file ) ); WP_CLI::log( '-- ' . date( 'r' ) ); return $post; } ); add_action( 'wp_import_insert_post', function( $post_id, $original_post_ID, $post, $postdata ) { global $wpcli_import_counts; if ( is_wp_error( $post_id ) ) { WP_CLI::warning( "-- Error importing post: " . $post_id->get_error_code() ); } else { WP_CLI::log( "-- Imported post as post_id #{$post_id}" ); } if ( $wpcli_import_counts['current_post'] % 500 === 0 ) { WP_CLI\Utils\wp_clear_object_cache(); WP_CLI::log( "-- Cleared object cache." ); } }, 10, 4 ); add_action( 'wp_import_insert_term', function( $t, $import_term, $post_id, $post ) { WP_CLI::log( "-- Created term \"{$import_term['name']}\"" ); }, 10, 4 ); add_action( 'wp_import_set_post_terms', function( $tt_ids, $term_ids, $taxonomy, $post_id, $post ) { WP_CLI::log( "-- Added terms (" . implode( ',', $term_ids ) .") for taxonomy \"{$taxonomy}\"" ); }, 10, 5 ); add_action( 'wp_import_insert_comment', function( $comment_id, $comment, $comment_post_ID, $post ) { global $wpcli_import_counts; $wpcli_import_counts['current_comment']++; WP_CLI::log( sprintf( '-- Added comment #%d (%s of %s)', $comment_id, number_format( $wpcli_import_counts['current_comment'] ), number_format( $wpcli_import_counts['total_comments'] ) ) ); }, 10, 4 ); add_action( 'import_post_meta', function( $post_id, $key, $value ) { WP_CLI::log( "-- Added post_meta $key" ); }, 10, 3 ); } /** * Is the requested importer available? */ private function is_importer_available() { require_once ABSPATH . 'wp-admin/includes/plugin.php'; if ( class_exists( 'WP_Import' ) ) { return true; } $plugins = get_plugins(); $wordpress_importer = 'wordpress-importer/wordpress-importer.php'; if ( array_key_exists( $wordpress_importer, $plugins ) ) $error_msg = "WordPress Importer needs to be activated. Try 'wp plugin activate wordpress-importer'."; else $error_msg = "WordPress Importer needs to be installed. Try 'wp plugin install wordpress-importer --activate'."; return new WP_Error( 'importer-missing', $error_msg ); } /** * Process how the authors should be mapped * * @param string $authors_arg The `--author` argument originally passed to command * @param array $author_data An array of WP_User-esque author objects * @return array|WP_Error $author_mapping Author mapping array if successful, WP_Error if something bad happened */ private function process_author_mapping( $authors_arg, $author_data ) { // Provided an author mapping file (method checks validity) if ( file_exists( $authors_arg ) ) return $this->read_author_mapping_file( $authors_arg ); // Provided a file reference, but the file doesn't yet exist if ( false !== stripos( $authors_arg, '.csv' ) ) return $this->create_author_mapping_file( $authors_arg, $author_data ); switch( $authors_arg ) { // Create authors if they don't yet exist; maybe match on email or user_login case 'create': return $this->create_authors_for_mapping( $author_data ); break; // Skip any sort of author mapping case 'skip': return array(); break; default: return new WP_Error( 'invalid-argument', "'authors' argument is invalid." ); } } /** * Read an author mapping file */ private function read_author_mapping_file( $file ) { $author_mapping = array(); foreach ( new \WP_CLI\Iterators\CSV( $file ) as $i => $author ) { if ( ! array_key_exists( 'old_user_login', $author ) || ! array_key_exists( 'new_user_login', $author ) ) return new WP_Error( 'invalid-author-mapping', "Author mapping file isn't properly formatted." ); $author_mapping[] = $author; } return $author_mapping; } /** * Create an author mapping file, based on provided author data * * @return WP_Error The file was just now created, so some action needs to be taken */ private function create_author_mapping_file( $file, $author_data ) { if ( touch( $file ) ) { $author_mapping = array(); foreach ( $author_data as $author ) { $author_mapping[] = array( 'old_user_login' => $author->user_login, 'new_user_login' => $this->suggest_user( $author->user_login, $author->user_email ), ); } $file_resource = fopen( $file, 'w' ); \WP_CLI\utils\write_csv( $file_resource, $author_mapping, array( 'old_user_login', 'new_user_login' ) ); return new WP_Error( 'author-mapping-error', sprintf( "Please update author mapping file before continuing: %s", $file ) ); } else { return new WP_Error( 'author-mapping-error', "Couldn't create author mapping file." ); } } /** * Create users if they don't exist, and build an author mapping file */ private function create_authors_for_mapping( $author_data ) { $author_mapping = array(); foreach ( $author_data as $author ) { if ( isset( $author->user_email ) ) { if ( $user = get_user_by( 'email', $author->user_email ) ) { $author_mapping[] = array( 'old_user_login' => $author->user_login, 'new_user_login' => $user->user_login, ); continue; } } if ( $user = get_user_by( 'login', $author->user_login ) ) { $author_mapping[] = array( 'old_user_login' => $author->user_login, 'new_user_login' => $user->user_login, ); continue; } $user = array( 'user_login' => '', 'user_email' => '', 'user_pass' => wp_generate_password(), ); $user = array_merge( $user, (array)$author ); $user_id = wp_insert_user( $user ); if ( is_wp_error( $user_id ) ) return $user_id; $user = get_user_by( 'id', $user_id ); $author_mapping[] = array( 'old_user_login' => $author->user_login, 'new_user_login' => $user->user_login, ); } return $author_mapping; } /** * Suggest a blog user based on the levenshtein distance */ private function suggest_user( $author_user_login, $author_user_email = '' ) { if ( ! isset( $this->blog_users ) ) $this->blog_users = get_users(); $shortest = -1; $shortestavg = array(); $threshold = floor( ( strlen( $author_user_login ) / 100 ) * 10 ); // 10 % of the strlen are valid $closest = ''; foreach ( $this->blog_users as $user ) { // Before we resort to an algorithm, let's try for an exact match if ( $author_user_email && $user->user_email == $author_user_email ) return $user->user_login; $levs = array(); $levs[] = levenshtein( $author_user_login, $user->display_name ); $levs[] = levenshtein( $author_user_login, $user->user_login ); $levs[] = levenshtein( $author_user_login, $user->user_email ); $email_parts = explode( "@", $user->user_email ); $email_login = array_shift( $email_parts ); $levs[] = levenshtein( $author_user_login, $email_login ); rsort( $levs ); $lev = array_pop( $levs ); if ( 0 == $lev ) { $closest = $user->user_login; $shortest = 0; break; } if ( ( $lev <= $shortest || $shortest < 0 ) && $lev <= $threshold ) { $closest = $user->user_login; $shortest = $lev; } $shortestavg[] = $lev; } // in case all usernames have a common pattern if ( $shortest > ( array_sum( $shortestavg ) / count( $shortestavg ) ) ) return ''; return $closest; } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'language core', 'Core_Language_Command', array( 'before_invoke' => function() { if ( \WP_CLI\Utils\wp_version_compare( '4.0', '<' ) ) { WP_CLI::error( "Requires WordPress 4.0 or greater." ); } }) ); <?php namespace WP_CLI; /** * Base class for WP-CLI commands that deal with translations * * @package wp-cli */ abstract class CommandWithTranslation extends \WP_CLI_Command { protected $obj_type; protected $obj_fields = array( 'language', 'english_name', 'native_name', 'status', 'update', 'updated', ); /** * List all available languages. * * [--field=<field>] * : Display the value of a single field * * [--<field>=<value>] * : Filter results by key=value pairs. * * [--fields=<fields>] * : Limit the output to specific fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each translation: * * * language * * english_name * * native_name * * status * * update * * updated * * These fields are optionally available: * * * version * * package * * ## EXAMPLES * * # List language,english_name,status fields of available languages. * $ wp language core list --fields=language,english_name,status * +----------------+-------------------------+-------------+ * | language | english_name | status | * +----------------+-------------------------+-------------+ * | ar | Arabic | uninstalled | * | ary | Moroccan Arabic | uninstalled | * | az | Azerbaijani | uninstalled | * * @subcommand list */ public function list_( $args, $assoc_args ) { $translations = $this->get_all_languages(); $available = $this->get_installed_languages(); $updates = $this->get_translation_updates(); $current_locale = get_locale(); $translations = array_map( function( $translation ) use ( $available, $current_locale, $updates ) { $translation['status'] = ( in_array( $translation['language'], $available ) ) ? 'installed' : 'uninstalled'; if ( $current_locale == $translation['language'] ) { $translation['status'] = 'active'; } $update = wp_list_filter( $updates, array( 'language' => $translation['language'] ) ); if ( $update ) { $translation['update'] = 'available'; } else { $translation['update'] = 'none'; } return $translation; }, $translations ); foreach( $translations as $key => $translation ) { $fields = array_keys( $translation ); foreach( $fields as $field ) { if ( isset( $assoc_args[ $field ] ) && $assoc_args[ $field ] != $translation[ $field ] ) { unset( $translations[ $key ] ); } } } $formatter = $this->get_formatter( $assoc_args ); $formatter->display_items( $translations ); } /** * Callback to sort array by a 'language' key. */ protected function sort_translations_callback( $a, $b ) { return strnatcasecmp( $a['language'], $b['language'] ); } /** * Install a given language. * * Downloads the language pack from WordPress.org. * * <language>... * : Language code to install. * * [--activate] * : If set, the language will be activated immediately after install. * * ## EXAMPLES * * # Install the Japanese language. * $ wp language core install ja * Success: Language installed. * * @subcommand install */ public function install( $args, $assoc_args ) { $language_codes = $args; if( 1 < count( $language_codes ) && in_array( true , $assoc_args , true ) ){ \WP_CLI::error( 'Only a single language can be active.' ); } $available = $this->get_installed_languages(); foreach ($language_codes as $language_code) { if ( in_array( $language_code, $available ) ) { \WP_CLI::warning( "Language '{$language_code}' already installed." ); } else { $response = $this->download_language_pack( $language_code ); if ( is_wp_error( $response ) ) { \WP_CLI::error( $response ); } else { \WP_CLI::success( "Language installed." ); } } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { $this->activate( array( $language_code ), array() ); } } } /** * Update installed languages. * * Updates installed languages for core, plugins and themes. * * [--dry-run] * : Preview which translations would be updated. * * ## EXAMPLES * * $ wp language core update * Updating 'Japanese' translation for Akismet 3.1.11... * Downloading translation from https://downloads.wordpress.org/translation/plugin/akismet/3.1.11/ja.zip... * Translation updated successfully. * Updating 'Japanese' translation for Twenty Fifteen 1.5... * Downloading translation from https://downloads.wordpress.org/translation/theme/twentyfifteen/1.5/ja.zip... * Translation updated successfully. * Success: Updated 2/2 translations. * * @subcommand update */ public function update( $args, $assoc_args ) { $updates = $this->get_translation_updates(); if ( empty( $updates ) ) { \WP_CLI::success( 'Translations are up to date.' ); return; } // Gets a list of all languages. $all_languages = $this->get_all_languages(); // Formats the updates list. foreach ( $updates as $update ) { if ( 'plugin' == $update->type ) { $plugins = get_plugins( '/' . $update->slug ); $plugin_data = array_shift( $plugins ); $name = $plugin_data['Name']; } elseif ( 'theme' == $update->type ) { $theme_data = wp_get_theme( $update->slug ); $name = $theme_data['Name']; } else { // Core $name = 'WordPress'; } // Gets the translation data. $translation = wp_list_filter( $all_languages, array( 'language' => $update->language ) ); $translation = (object) reset( $translation ); $update->Type = ucfirst( $update->type ); $update->Name = $name; $update->Version = $update->version; $update->Language = $translation->english_name; } // Only preview which translations would be updated. if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run' ) ) { \WP_CLI::line( sprintf( 'Available %d translations updates:', count( $updates ) ) ); \WP_CLI\Utils\format_items( 'table', $updates, array( 'Type', 'Name', 'Version', 'Language' ) ); return; } $upgrader = 'WP_CLI\\LanguagePackUpgrader'; $results = array(); // Update translations. foreach ( $updates as $update ) { \WP_CLI::line( "Updating '{$update->Language}' translation for {$update->Name} {$update->Version}..." ); $result = Utils\get_upgrader( $upgrader )->upgrade( $update ); $results[] = $result; } $num_to_update = count( $updates ); $num_updated = count( array_filter( $results ) ); $line = "Updated $num_updated/$num_to_update translations."; if ( $num_to_update == $num_updated ) { \WP_CLI::success( $line ); } else if ( $num_updated > 0 ) { \WP_CLI::warning( $line ); } else { \WP_CLI::error( $line ); } } /** * Activate a given language. * * <language> * : Language code to activate. * * ## EXAMPLES * * $ wp language core activate ja * Success: Language activated. * * @subcommand activate */ public function activate( $args, $assoc_args ) { list( $language_code ) = $args; $available = $this->get_installed_languages(); if ( ! in_array( $language_code, $available ) ) { \WP_CLI::error( "Language not installed." ); } if ( $language_code == 'en_US' ) { $language_code = ''; } if ( $language_code === get_locale() ) { \WP_CLI::warning( "Language '{$language_code}' already active." ); return; } update_option( 'WPLANG', $language_code ); \WP_CLI::success( "Language activated." ); } /** * Get all updates available for all translations * * @return array */ private function get_translation_updates() { $available = $this->get_installed_languages(); $func = function() use ( $available ) { return $available; }; $filters = array( 'plugins_update_check_locales', 'themes_update_check_locales' ); foreach( $filters as $filter ) { add_filter( $filter, $func ); } $this->wp_clean_update_cache(); // Clear existing update caches. wp_version_check(); // Check for Core translation updates. wp_update_themes(); // Check for Theme translation updates. wp_update_plugins(); // Check for Plugin translation updates. foreach( $filters as $filter ) { remove_filter( $filter, $func ); } $updates = wp_get_translation_updates(); // Retrieves a list of all translations updates available. return $updates; } /** * Download a language pack. * * @see wp_download_language_pack() * * @param string $download Language code to download. * @return string|WP_Error Returns the language code if successfully downloaded, or a WP_Error object on failure. */ private function download_language_pack( $download ) { $translations = $this->get_all_languages(); foreach ( $translations as $translation ) { if ( $translation['language'] === $download ) { $translation_to_load = true; break; } } if ( empty( $translation_to_load ) ) { return new \WP_Error( 'not_found', "Language '{$download}' not found." ); } $translation = (object) $translation; $translation->type = 'core'; $upgrader = 'WP_CLI\\LanguagePackUpgrader'; $result = Utils\get_upgrader( $upgrader )->upgrade( $translation, array( 'clear_update_cache' => false ) ); if ( is_wp_error( $result ) ) { return $result; } else if ( ! $result ) { return new \WP_Error( 'not_installed', "Could not install language '{$download}'." ); } return $translation->language; } /** * Return a list of installed languages. * * @return array */ protected function get_installed_languages() { $available = wp_get_installed_translations( $this->obj_type ); $available = ! empty( $available['default'] ) ? array_keys( $available['default'] ) : array(); $available[] = 'en_US'; return $available; } /** * Return a list of all languages * * @return array */ protected function get_all_languages() { require_once ABSPATH . '/wp-admin/includes/translation-install.php'; require ABSPATH . WPINC . '/version.php'; $response = translations_api( $this->obj_type, array( 'version' => $wp_version ) ); if ( is_wp_error( $response ) ) { \WP_CLI::error( $response ); } $translations = ! empty( $response['translations'] ) ? $response['translations'] : array(); $en_us = array( 'language' => 'en_US', 'english_name' => 'English (United States)', 'native_name' => 'English (United States)', 'updated' => '', ); array_push( $translations, $en_us ); uasort( $translations, array( $this, 'sort_translations_callback' ) ); return $translations; } /** * Uninstall a given language. * * <language>... * : Language code to uninstall. * * ## EXAMPLES * * $ wp language core uninstall ja * Success: Language uninstalled. * * @subcommand uninstall */ public function uninstall( $args, $assoc_args ) { global $wp_filesystem; $language_codes = $args; $available = $this->get_installed_languages(); foreach ($language_codes as $language_code) { if ( ! in_array( $language_code, $available ) ) { \WP_CLI::error( "Language not installed." ); } $dir = 'core' === $this->obj_type ? '' : "/$this->obj_type"; $files = scandir( WP_LANG_DIR . $dir ); if ( ! $files ) { \WP_CLI::error( "No files found in language directory." ); } $current_locale = get_locale(); if ( $language_code === $current_locale ) { \WP_CLI::warning( "The '{$language_code}' language is active." ); exit; } // As of WP 4.0, no API for deleting a language pack WP_Filesystem(); $deleted = false; foreach ( $files as $file ) { if ( '.' === $file[0] || is_dir( $file ) ) { continue; } $extension_length = strlen( $language_code ) + 4; $ending = substr( $file, -$extension_length ); if ( ! in_array( $file, array( $language_code . '.po', $language_code . '.mo' ) ) && ! in_array( $ending, array( '-' . $language_code . '.po', '-' . $language_code . '.mo' ) ) ) { continue; } $deleted = $wp_filesystem->delete( WP_LANG_DIR . $dir . '/' . $file ); } if ( $deleted ) { \WP_CLI::success( "Language uninstalled." ); } else { \WP_CLI::error( "Couldn't uninstall language." ); } } } /** * Get Formatter object based on supplied parameters. * * @param array $assoc_args Parameters passed to command. Determines formatting. * @return \WP_CLI\Formatter */ protected function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->obj_fields, $this->obj_type ); } /** * Replicates wp_clean_update_cache() for use in WP 4.0 */ private static function wp_clean_update_cache() { if ( function_exists( 'wp_clean_plugins_cache' ) ) { wp_clean_plugins_cache(); } else { delete_site_transient( 'update_plugins' ); } wp_clean_themes_cache(); delete_site_transient( 'update_core' ); } } <?php namespace WP_CLI; use WP_CLI; /** * A Language Pack Upgrader class that caches the download, and uses cached if available * * @package wp-cli */ class LanguagePackUpgrader extends \Language_Pack_Upgrader { /** * Caches the download, and uses cached if available. * * @access public * * @param string $package The URI of the package. If this is the full path to an * existing local file, it will be returned untouched. * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object. */ public function download_package( $package ) { /** * Filter whether to return the package. * * @since 3.7.0 * * @param bool $reply Whether to bail without returning the package. Default is false. * @param string $package The package file name. * @param object $this The WP_Upgrader instance. */ $reply = apply_filters( 'upgrader_pre_download', false, $package, $this ); if ( false !== $reply ) { return $reply; } // Check if package is a local or remote file. Bail if it's local. if ( ! preg_match( '!^(http|https|ftp)://!i', $package ) && file_exists( $package ) ) { return $package; } if ( empty( $package ) ) { return new \WP_Error( 'no_package', $this->strings['no_package'] ); } $language_update = $this->skin->language_update; $type = $language_update->type; $slug = empty( $language_update->slug ) ? 'default' : $language_update->slug; $updated = strtotime( $language_update->updated ); $version = $language_update->version; $language = $language_update->language; $ext = pathinfo( $package, PATHINFO_EXTENSION ); $temp = \WP_CLI\Utils\get_temp_dir() . uniqid( 'wp_' ) . '.' . $ext; $cache = WP_CLI::get_cache(); $cache_key = "translation/{$type}-{$slug}-{$version}-{$language}-{$updated}.{$ext}"; $cache_file = $cache->has( $cache_key ); if ( $cache_file ) { WP_CLI::log( "Using cached file '$cache_file'..." ); copy( $cache_file, $temp ); return $temp; } else { /* * Download to a temporary file because piping from cURL to tar is flaky * on MinGW (and probably in other environments too). */ $headers = array( 'Accept' => 'application/json' ); $options = array( 'timeout' => 600, // 10 minutes ought to be enough for everybody. 'filename' => $temp ); $this->skin->feedback( 'downloading_package', $package ); /** @var \Requests_Response|null $req */ $req = Utils\http_request( 'GET', $package, null, $headers, $options ); if ( ! is_null( $req ) && $req->status_code !== 200 ) { return new \WP_Error( 'download_failed', $this->strings['download_failed'] ); } $cache->import( $cache_key, $temp ); return $temp; } } } <?php /** * Manage core language. * * ## EXAMPLES * * # Install language * $ wp language core install nl_NL * Success: Language installed. * * # Activate language * $ wp language core activate nl_NL * Success: Language activated. * * # Uninstall language * $ wp language core uninstall nl_NL * Success: Language uninstalled. * * # List installed languages * $ wp language core list --status=installed * +----------+--------------+-------------+-----------+-----------+---------------------+ * | language | english_name | native_name | status | update | updated | * +----------+--------------+-------------+-----------+-----------+---------------------+ * | nl_NL | Dutch | Nederlands | installed | available | 2016-05-13 08:12:50 | * +----------+--------------+-------------+-----------+-----------+---------------------+ */ class Core_Language_Command extends WP_CLI\CommandWithTranslation { protected $obj_type = 'core'; } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'db', 'DB_Command' ); <?php use \WP_CLI\Utils; /** * Perform basic database operations using credentials stored in wp-config.php * * ## EXAMPLES * * # Create a new database. * $ wp db create * Success: Database created. * * # Drop an existing database. * $ wp db drop --yes * Success: Database dropped. * * # Reset the current database. * $ wp db reset --yes * Success: Database reset. * * # Execute a SQL query stored in a file. * $ wp db query < debug.sql */ class DB_Command extends WP_CLI_Command { /** * Create a new database. * * Runs `CREATE_DATABASE` SQL statement using `DB_HOST`, `DB_NAME`, * `DB_USER` and `DB_PASSWORD` database credentials specified in * wp-config.php. * * ## EXAMPLES * * $ wp db create * Success: Database created. */ public function create( $_, $assoc_args ) { self::run_query( self::get_create_query() ); WP_CLI::success( "Database created." ); } /** * Delete the existing database. * * Runs `DROP_DATABASE` SQL statement using `DB_HOST`, `DB_NAME`, * `DB_USER` and `DB_PASSWORD` database credentials specified in * wp-config.php. * * ## OPTIONS * * [--yes] * : Answer yes to the confirmation message. * * ## EXAMPLES * * $ wp db drop --yes * Success: Database dropped. */ public function drop( $_, $assoc_args ) { WP_CLI::confirm( "Are you sure you want to drop the '" . DB_NAME . "' database?", $assoc_args ); self::run_query( sprintf( 'DROP DATABASE `%s`', DB_NAME ) ); WP_CLI::success( "Database dropped." ); } /** * Remove all tables from the database. * * Runs `DROP_DATABASE` and `CREATE_DATABASE` SQL statements using * `DB_HOST`, `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials * specified in wp-config.php. * * ## OPTIONS * * [--yes] * : Answer yes to the confirmation message. * * ## EXAMPLES * * $ wp db reset --yes * Success: Database reset. */ public function reset( $_, $assoc_args ) { WP_CLI::confirm( "Are you sure you want to reset the '" . DB_NAME . "' database?", $assoc_args ); self::run_query( sprintf( 'DROP DATABASE IF EXISTS `%s`', DB_NAME ) ); self::run_query( self::get_create_query() ); WP_CLI::success( "Database reset." ); } /** * Check the current status of the database. * * Runs `mysqlcheck` utility with `--check` using `DB_HOST`, * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials * specified in wp-config.php. * * [See docs](http://dev.mysql.com/doc/refman/5.7/en/check-table.html) * for more details on the `CHECK TABLE` statement. * * ## EXAMPLES * * $ wp db check * Success: Database checked. */ public function check() { self::run( Utils\esc_cmd( '/usr/bin/env mysqlcheck --no-defaults %s', DB_NAME ), array( 'check' => true, ) ); WP_CLI::success( "Database checked." ); } /** * Optimize the database. * * Runs `mysqlcheck` utility with `--optimize=true` using `DB_HOST`, * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials * specified in wp-config.php. * * [See docs](http://dev.mysql.com/doc/refman/5.7/en/optimize-table.html) * for more details on the `OPTIMIZE TABLE` statement. * * ## EXAMPLES * * $ wp db optimize * Success: Database optimized. */ public function optimize() { self::run( Utils\esc_cmd( '/usr/bin/env mysqlcheck --no-defaults %s', DB_NAME ), array( 'optimize' => true, ) ); WP_CLI::success( "Database optimized." ); } /** * Repair the database. * * Runs `mysqlcheck` utility with `--repair=true` using `DB_HOST`, * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials * specified in wp-config.php. * * [See docs](http://dev.mysql.com/doc/refman/5.7/en/repair-table.html) for * more details on the `REPAIR TABLE` statement. * * ## EXAMPLES * * $ wp db repair * Success: Database repaired. */ public function repair() { self::run( Utils\esc_cmd( '/usr/bin/env mysqlcheck --no-defaults %s', DB_NAME ), array( 'repair' => true, ) ); WP_CLI::success( "Database repaired." ); } /** * Open a MySQL console using credentials from wp-config.php * * ## OPTIONS * * [--database=<database>] * : Use a specific database. Defaults to DB_NAME. * * [--default-character-set=<character-set>] * : Use a specific character set. Defaults to DB_CHARSET when defined. * * [--<field>=<value>] * : Extra arguments to pass to the MySQL executable. * * ## EXAMPLES * * # Open MySQL console * $ wp db cli * mysql> * * @alias connect */ public function cli( $args, $assoc_args ) { if ( ! isset( $assoc_args['database'] ) ) { $assoc_args['database'] = DB_NAME; } self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $assoc_args ); } /** * Execute a SQL query against the database. * * Executes an arbitrary SQL query using `DB_HOST`, `DB_NAME`, `DB_USER` * and `DB_PASSWORD` database credentials specified in wp-config.php. * * ## OPTIONS * * [<sql>] * : A SQL query. If not passed, will try to read from STDIN. * * [--<field>=<value>] * : Extra arguments to pass to mysql. * * ## EXAMPLES * * # Execute a query stored in a file * $ wp db query < debug.sql * * # Check all tables in the database * $ wp db query "CHECK TABLE $(wp db tables | paste -s -d, -);" * +---------------------------------------+-------+----------+----------+ * | Table | Op | Msg_type | Msg_text | * +---------------------------------------+-------+----------+----------+ * | wordpress_dbase.wp_users | check | status | OK | * | wordpress_dbase.wp_usermeta | check | status | OK | * | wordpress_dbase.wp_posts | check | status | OK | * | wordpress_dbase.wp_comments | check | status | OK | * | wordpress_dbase.wp_links | check | status | OK | * | wordpress_dbase.wp_options | check | status | OK | * | wordpress_dbase.wp_postmeta | check | status | OK | * | wordpress_dbase.wp_terms | check | status | OK | * | wordpress_dbase.wp_term_taxonomy | check | status | OK | * | wordpress_dbase.wp_term_relationships | check | status | OK | * | wordpress_dbase.wp_termmeta | check | status | OK | * | wordpress_dbase.wp_commentmeta | check | status | OK | * +---------------------------------------+-------+----------+----------+ * * # Pass extra arguments through to MySQL * $ wp db query 'SELECT * FROM wp_options WHERE option_name="home"' --skip-column-names * +---+------+------------------------------+-----+ * | 2 | home | http://wordpress-develop.dev | yes | * +---+------+------------------------------+-----+ */ public function query( $args, $assoc_args ) { $assoc_args['database'] = DB_NAME; // The query might come from STDIN if ( !empty( $args ) ) { $assoc_args['execute'] = $args[0]; } self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $assoc_args ); } /** * Exports the database to a file or to STDOUT. * * Runs `mysqldump` utility using `DB_HOST`, `DB_NAME`, `DB_USER` and * `DB_PASSWORD` database credentials specified in wp-config.php. * * ## OPTIONS * * [<file>] * : The name of the SQL file to export. If '-', then outputs to STDOUT. If * omitted, it will be '{dbname}-{Y-m-d}-{random-hash}.sql'. * * [--<field>=<value>] * : Extra arguments to pass to mysqldump * * [--tables=<tables>] * : The comma separated list of specific tables to export. Excluding this parameter will export all tables in the database. * * [--exclude_tables=<tables>] * : The comma separated list of specific tables that should be skipped from exporting. Excluding this parameter will export all tables in the database. * * [--porcelain] * : Output filename for the exported database. * * ## EXAMPLES * * # Export database with drop query included * $ wp db export --add-drop-table * Success: Exported to 'wordpress_dbase-db72bb5.sql'. * * # Export certain tables * $ wp db export --tables=wp_options,wp_users * Success: Exported to 'wordpress_dbase-db72bb5.sql'. * * # Export all tables matching a wildcard * $ wp db export --tables=$(wp db tables 'wp_user*' --format=csv) * Success: Exported to 'wordpress_dbase-db72bb5.sql'. * * # Export all tables matching prefix * $ wp db export --tables=$(wp db tables --all-tables-with-prefix --format=csv) * Success: Exported to 'wordpress_dbase-db72bb5.sql'. * * # Skip certain tables from the exported database * $ wp db export --exclude_tables=wp_options,wp_users * Success: Exported to 'wordpress_dbase-db72bb5.sql'. * * # Skip all tables matching a wildcard from the exported database * $ wp db export --exclude_tables=$(wp db tables 'wp_user*' --format=csv) * Success: Exported to 'wordpress_dbase-db72bb5.sql'. * * # Skip all tables matching prefix from the exported database * $ wp db export --exclude_tables=$(wp db tables --all-tables-with-prefix --format=csv) * Success: Exported to 'wordpress_dbase-db72bb5.sql'. * * # Export database to STDOUT. * $ wp db export - * -- MySQL dump 10.13 Distrib 5.7.19, for osx10.12 (x86_64) * -- * -- Host: localhost Database: wpdev * -- ------------------------------------------------------ * -- Server version 5.7.19 * ... * * @alias dump */ public function export( $args, $assoc_args ) { if ( ! empty( $args[0] ) ) { $result_file = $args[0]; } else { $hash = substr( md5( mt_rand() ), 0, 7 ); $result_file = sprintf( '%s-%s-%s.sql', DB_NAME, date( 'Y-m-d' ), $hash );; } $stdout = ( '-' === $result_file ); $porcelain = \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ); // Bail if both porcelain and STDOUT are set. if ( $stdout && $porcelain ) { WP_CLI::error( 'Porcelain is not allowed when output mode is STDOUT.' ); } if ( ! $stdout ) { $assoc_args['result-file'] = $result_file; } $command = '/usr/bin/env mysqldump --no-defaults %s'; $command_esc_args = array( DB_NAME ); if ( isset( $assoc_args['tables'] ) ) { $tables = explode( ',', trim( $assoc_args['tables'], ',' ) ); unset( $assoc_args['tables'] ); $command .= ' --tables'; foreach ( $tables as $table ) { $command .= ' %s'; $command_esc_args[] = trim( $table ); } } $exclude_tables = WP_CLI\Utils\get_flag_value( $assoc_args, 'exclude_tables' ); if ( isset( $exclude_tables ) ) { $tables = explode( ',', trim( $assoc_args['exclude_tables'], ',' ) ); unset( $assoc_args['exclude_tables'] ); foreach ( $tables as $table ) { $command .= ' --ignore-table'; $command .= ' %s'; $command_esc_args[] = trim( DB_NAME . '.' . $table ); } } $escaped_command = call_user_func_array( '\WP_CLI\Utils\esc_cmd', array_merge( array( $command ), $command_esc_args ) ); // Remove parameters not needed for SQL run. unset( $assoc_args['porcelain'] ); self::run( $escaped_command, $assoc_args ); if ( $porcelain ) { WP_CLI::line( $result_file ); } else if ( ! $stdout ) { WP_CLI::success( sprintf( "Exported to '%s'.", $result_file ) ); } } /** * Import a database from a file or from STDIN. * * Runs SQL queries using `DB_HOST`, `DB_NAME`, `DB_USER` and * `DB_PASSWORD` database credentials specified in wp-config.php. This * does not create database by itself and only performs whatever tasks are * defined in the SQL. * * ## OPTIONS * * [<file>] * : The name of the SQL file to import. If '-', then reads from STDIN. If omitted, it will look for '{dbname}.sql'. * * [--skip-optimization] * : When using an SQL file, do not include speed optimization such as disabling auto-commit and key checks. * * ## EXAMPLES * * # Import MySQL from a file. * $ wp db import wordpress_dbase.sql * Success: Imported from 'wordpress_dbase.sql'. */ public function import( $args, $assoc_args ) { if ( ! empty( $args[0] ) ) { $result_file = $args[0]; } else { $result_file = sprintf( '%s.sql', DB_NAME ); } $mysql_args = array( 'database' => DB_NAME, ); if ( '-' !== $result_file ) { if ( ! is_readable( $result_file ) ) { WP_CLI::error( sprintf( 'Import file missing or not readable: %s', $result_file ) ); } $query = \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-optimization' ) ? 'SOURCE %s;' : 'SET autocommit = 0; SET unique_checks = 0; SET foreign_key_checks = 0; SOURCE %s; COMMIT;'; $mysql_args['execute'] = sprintf( $query, $result_file ); } self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $mysql_args ); WP_CLI::success( sprintf( "Imported from '%s'.", $result_file ) ); } /** * List the database tables. * * Defaults to all tables registered to the $wpdb database handler. * * ## OPTIONS * * [<table>...] * : List tables based on wildcard search, e.g. 'wp_*_options' or 'wp_post?'. * * [--scope=<scope>] * : Can be all, global, ms_global, blog, or old tables. Defaults to all. * * [--network] * : List all the tables in a multisite install. Overrides --scope=<scope>. * * [--all-tables-with-prefix] * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network. * * [--all-tables] * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix. * * [--format=<format>] * : Render output in a particular format. * --- * default: list * options: * - list * - csv * --- * * ## EXAMPLES * * # List tables for a single site, without shared tables like 'wp_users' * $ wp db tables --scope=blog --url=sub.example.com * wp_3_posts * wp_3_comments * wp_3_options * wp_3_postmeta * wp_3_terms * wp_3_term_taxonomy * wp_3_term_relationships * wp_3_termmeta * wp_3_commentmeta * * # Export only tables for a single site * $ wp db export --tables=$(wp db tables --url=sub.example.com --format=csv) * Success: Exported to wordpress_dbase.sql */ public function tables( $args, $assoc_args ) { $format = WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ); unset( $assoc_args['format'] ); if ( empty( $args ) && empty( $assoc_args ) ) { $assoc_args['scope'] = 'all'; } $tables = WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ); if ( 'csv' === $format ) { WP_CLI::line( implode( ',', $tables ) ); } else { foreach ( $tables as $table ) { WP_CLI::line( $table ); } } } /** * Display the database name and size. * * Display the database name and size for `DB_NAME` specified in wp-config.php. * The size defaults to a human-readable number. * * ## OPTIONS * * [--size_format] * : Display the database size only, as a bare number. * --- * default: b * options: * - b (bytes) * - kb (kilobytes) * - mb (megabytes) * --- * * [--tables] * : Display each table name and size instead of the database size. * * [--format] * : table, csv, json * --- * default: table * options: * - table * - csv * - json * --- * * [--scope=<scope>] * : Can be all, global, ms_global, blog, or old tables. Defaults to all. * * [--network] * : List all the tables in a multisite install. Overrides --scope=<scope>. * * [--all-tables-with-prefix] * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network. * * [--all-tables] * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix. * * ## EXAMPLES * * $ wp db size * +-------------------+------+ * | Name | Size | * +-------------------+------+ * | wordpress_default | 6 MB | * +-------------------+------+ * * $ wp db size --tables * +-----------------------+-------+ * | Name | Size | * +-----------------------+-------+ * | wp_users | 64 KB | * | wp_usermeta | 48 KB | * | wp_posts | 80 KB | * | wp_comments | 96 KB | * | wp_links | 32 KB | * | wp_options | 32 KB | * | wp_postmeta | 48 KB | * | wp_terms | 48 KB | * | wp_term_taxonomy | 48 KB | * | wp_term_relationships | 32 KB | * | wp_termmeta | 48 KB | * | wp_commentmeta | 48 KB | * +-----------------------+-------+ * * $ wp db size --size_format=b * 5865472 * * $ wp db size --size_format=kb * 5728 * * $ wp db size --size_format=mb * 6 */ public function size( $args, $assoc_args ) { // Avoid a constant redefinition in wp-config. @WP_CLI::get_runner()-> load_wordpress(); global $wpdb; $format = WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ); $size_format = WP_CLI\Utils\get_flag_value( $assoc_args, 'size_format' ); $tables = WP_CLI\Utils\get_flag_value( $assoc_args, 'tables' ); $tables = ! empty( $tables ); unset( $assoc_args['format'] ); unset( $assoc_args['size_format'] ); unset( $assoc_args['tables'] ); if ( empty( $args ) && empty( $assoc_args ) ) { $assoc_args['scope'] = 'all'; } // Build rows for the formatter. $rows = array(); $fields = array( 'Name', 'Size' ); if ( $tables ) { // Add all of the table sizes foreach( WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ) as $table_name ) { // Get the table size. $table_bytes = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(data_length + index_length) FROM information_schema.TABLES where table_schema = '%s' and Table_Name = '%s' GROUP BY Table_Name LIMIT 1", DB_NAME, $table_name ) ); // Add the table size to the list. $rows[] = array( 'Name' => $table_name, 'Size' => strtoupper( size_format( $table_bytes ) ), ); } } else { // Get the database size. $db_bytes = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(data_length + index_length) FROM information_schema.TABLES where table_schema = '%s' GROUP BY table_schema;", DB_NAME ) ); // Add the database size to the list. $rows[] = array( 'Name' => DB_NAME, 'Size' => strtoupper( size_format( $db_bytes ) ), ); } if ( ! empty( $size_format ) && isset( $db_bytes ) && ! $tables ) { // Display the database size as a number. switch( $size_format ) { case 'mb': $divisor = MB_IN_BYTES; break; case 'kb': $divisor = KB_IN_BYTES; break; case 'b': default: $divisor = 1; break; } WP_CLI::Line( ceil( $db_bytes / $divisor ) ); } else { // Display the rows. $args = array( 'format' => $format, ); $formatter = new \WP_CLI\Formatter( $args, $fields ); $formatter->display_items( $rows ); } } /** * Display the database table prefix. * * Display the database table prefix, as defined by the database handler's interpretation of the current site. * * ## EXAMPLES * * $ wp db prefix * wp_ */ public function prefix() { // Avoid a constant redefinition in wp-config. @WP_CLI::get_runner()->load_wordpress(); global $wpdb; WP_CLI::log( $wpdb->prefix ); } /** * Find a string in the database. * * Searches through all or a selection of database tables for a given string, Outputs colorized references to the string. * * Defaults to searching through all tables registered to $wpdb. On multisite, this default is limited to the tables for the current site. * * ## OPTIONS * * <search> * : String to search for. The search is case-insensitive by default. * * [<tables>...] * : One or more tables to search through for the string. * * [--network] * : Search through all the tables registered to $wpdb in a multisite install. * * [--all-tables-with-prefix] * : Search through all tables that match the registered table prefix, even if not registered on $wpdb. On one hand, sometimes plugins use tables without registering them to $wpdb. On another hand, this could return tables you don't expect. Overrides --network. * * [--all-tables] * : Search through ALL tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --network and --all-tables-with-prefix. * * [--before_context=<num>] * : Number of characters to display before the match. * --- * default: 40 * --- * * [--after_context=<num>] * : Number of characters to display after the match. * --- * default: 40 * --- * * [--regex] * : Runs the search as a regular expression (without delimiters). The search becomes case-sensitive (i.e. no PCRE flags are added). Delimiters must be escaped if they occur in the expression. * * [--regex-flags=<regex-flags>] * : Pass PCRE modifiers to the regex search (e.g. 'i' for case-insensitivity). * * [--regex-delimiter=<regex-delimiter>] * : The delimiter to use for the regex. It must be escaped if it appears in the search string. The default value is the result of `chr(1)`. * * [--table_column_once] * : Output the 'table:column' line once before all matching row lines in the table column rather than before each matching row. * * [--one_line] * : Place the 'table:column' output on the same line as the row id and match ('table:column:id:match'). Overrides --table_column_once. * * [--matches_only] * : Only output the string matches (including context). No 'table:column's or row ids are outputted. * * [--stats] * : Output stats on the number of matches found, time taken, tables/columns/rows searched, tables skipped. * * [--table_column_color=<color_code>] * : Percent color code to use for the 'table:column' output. For a list of available percent color codes, see below. Default '%G' (bright green). * * [--id_color=<color_code>] * : Percent color code to use for the row id output. For a list of available percent color codes, see below. Default '%Y' (bright yellow). * * [--match_color=<color_code>] * : Percent color code to use for the match (unless both before and after context are 0, when no color code is used). For a list of available percent color codes, see below. Default '%3%k' (black on a mustard background). * * The percent color codes available are: * '%y' Yellow (dark) (mustard) * '%g' Green (dark) * '%b' Blue (dark) * '%r' Red (dark) * '%m' Magenta (dark) * '%c' Cyan (dark) * '%w' White (dark) (light gray) * '%k' Black * '%Y' Yellow (bright) * '%G' Green (bright) * '%B' Blue (bright) * '%R' Red (bright) * '%M' Magenta (bright) * '%C' Cyan (bright) * '%W' White * '%K' Black (bright) (dark gray) * '%3' Yellow background (dark) (mustard) * '%2' Green background (dark) * '%4' Blue background (dark) * '%1' Red background (dark) * '%5' Magenta background (dark) * '%6' Cyan background (dark) * '%7' White background (dark) (light gray) * '%0' Black background * '%8' Reverse * '%U' Underline * '%F' Blink (unlikely to work) * * They can be concatenated. For instance, the default match color of black on a mustard (dark yellow) background '%3%k' can be made black on a bright yellow background with '%Y%0%8'. * * ## EXAMPLES * * # Search through the database for the 'wordpress-develop' string * $ wp db search wordpress-develop * wp_options:option_value * 1:http://wordpress-develop.dev * wp_options:option_value * 1:http://example.com/foo * ... * * # Search through a multisite database on the subsite 'foo' for the 'example.com' string * $ wp db search example.com --url=example.com/foo * wp_2_comments:comment_author_url * 1:http://example.com/ * wp_2_options:option_value * ... * * # Search through the database for the 'https?://' regular expression, printing stats. * $ wp db search 'https?://' --regex --stats * wp_comments:comment_author_url * 1:https://wordpress.org/ * ... * Success: Found 99146 matches in 10.752s (10.559s searching). Searched 12 tables, 53 columns, 1358907 rows. 1 table skipped: wp_term_relationships. * */ public function search( $args, $assoc_args ) { global $wpdb; $start_run_time = microtime( true ); // Avoid a constant redefinition in wp-config. @WP_CLI::get_runner()->load_wordpress(); $search = array_shift( $args ); $before_context = \WP_CLI\Utils\get_flag_value( $assoc_args, 'before_context', 40 ); $before_context = '' === $before_context ? $before_context : (int) $before_context; $after_context = \WP_CLI\Utils\get_flag_value( $assoc_args, 'after_context', 40 ); $after_context = '' === $after_context ? $after_context : (int) $after_context; if ( ( $regex = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex', false ) ) ) { $regex_flags = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex-flags', false ); $default_regex_delimiter = false; $regex_delimiter = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex-delimiter', '' ); if ( '' === $regex_delimiter ) { $regex_delimiter = chr( 1 ); $default_regex_delimiter = true; } } $colors = self::get_colors( $assoc_args, array( 'table_column' => '%G', 'id' => '%Y', 'match' => $before_context || $after_context ? '%3%k' : '' ) ); $table_column_once = \WP_CLI\Utils\get_flag_value( $assoc_args, 'table_column_once', false ); $one_line = \WP_CLI\Utils\get_flag_value( $assoc_args, 'one_line', false ); $matches_only = \WP_CLI\Utils\get_flag_value( $assoc_args, 'matches_only', false ); $stats = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stats', false ); $column_count = $row_count = $match_count = 0; $skipped = array(); if ( $regex ) { // Note the user must escape the delimiter in the search. $search_regex = $regex_delimiter . $search . $regex_delimiter; if ( $regex_flags ) { $search_regex .= $regex_flags; } if ( false === @preg_match( $search_regex, '' ) ) { if ( $default_regex_delimiter ) { $flags_msg = $regex_flags ? "flags '$regex_flags'" : "no flags"; $msg = "The regex pattern '$search' with default delimiter 'chr(1)' and {$flags_msg} fails."; } else { $msg = "The regex '$search_regex' fails."; } WP_CLI::error( $msg ); } } else { $search_regex = '#' . preg_quote( $search, '#' ) . '#i'; $esc_like_search = '%' . self::esc_like( $search ) . '%'; } $encoding = null; if ( 0 === strpos( $wpdb->charset, 'utf8' ) ) { $encoding = 'UTF-8'; } $tables = WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ); $start_search_time = microtime( true ); foreach ( $tables as $table ) { list( $primary_keys, $text_columns, $all_columns ) = self::get_columns( $table ); if ( ! $all_columns ) { WP_CLI::error( "No such table '$table'." ); } if ( ! $text_columns ) { if ( $stats ) { $skipped[] = $table; } else { // Don't bother warning for term relationships (which is just 3 int columns). if ( ! preg_match( '/_term_relationships$/', $table ) ) { WP_CLI::warning( $primary_keys ? "No text columns for table '$table' - skipped." : "No primary key or text columns for table '$table' - skipped." ); } } continue; } $table_sql = self::esc_sql_ident( $table ); $column_count += count( $text_columns ); if ( ! $primary_keys ) { WP_CLI::warning( "No primary key for table '$table'. No row ids will be outputted." ); $primary_key = $primary_key_sql = ''; } else { $primary_key = array_shift( $primary_keys ); $primary_key_sql = self::esc_sql_ident( $primary_key ) . ', '; } foreach ( $text_columns as $column ) { $column_sql = self::esc_sql_ident( $column ); if ( $regex ) { $results = $wpdb->get_results( "SELECT {$primary_key_sql}{$column_sql} FROM {$table_sql}" ); } else { $results = $wpdb->get_results( $wpdb->prepare( "SELECT {$primary_key_sql}{$column_sql} FROM {$table_sql} WHERE {$column_sql} LIKE %s;", $esc_like_search ) ); } if ( $results ) { $row_count += count( $results ); $table_column_val = $colors['table_column'][0] . "{$table}:{$column}" . $colors['table_column'][1]; $outputted_table_column_once = false; foreach ( $results as $result ) { $col_val = $result->$column; if ( preg_match_all( $search_regex, $col_val, $matches, PREG_OFFSET_CAPTURE ) ) { if ( ! $matches_only && ( ! $table_column_once || ! $outputted_table_column_once ) && ! $one_line ) { WP_CLI::log( $table_column_val ); $outputted_table_column_once = true; } $pk_val = $primary_key ? ( $colors['id'][0] . $result->$primary_key . $colors['id'][1] . ':' ) : ''; $bits = array(); $col_encoding = $encoding; if ( ! $col_encoding && ( $before_context || $after_context ) && function_exists( 'mb_detect_encoding' ) ) { $col_encoding = mb_detect_encoding( $col_val, null, true /*strict*/ ); } $append_next = false; $last_offset = 0; $match_cnt = count( $matches[0] ); for ( $i = 0; $i < $match_cnt; $i++ ) { $match = $matches[0][ $i ][0]; $offset = $matches[0][ $i ][1]; $log = $colors['match'][0] . $match . $colors['match'][1]; $before = $after = ''; $after_shortened = false; // Offsets are in bytes, so need to use `strlen()` and `substr()` before using `safe_substr()`. if ( $before_context && $offset && ! $append_next ) { $before = \cli\safe_substr( substr( $col_val, $last_offset, $offset - $last_offset ), -$before_context, null /*length*/, false /*is_width*/, $col_encoding ); } if ( $after_context ) { $end_offset = $offset + strlen( $match ); $after = \cli\safe_substr( substr( $col_val, $end_offset ), 0, $after_context, false /*is_width*/, $col_encoding ); // To lessen context duplication in output, shorten the after context if it overlaps with the next match. if ( $i + 1 < $match_cnt && $end_offset + strlen( $after ) > $matches[0][ $i + 1 ][1] ) { $after = substr( $after, 0, $matches[0][ $i + 1 ][1] - $end_offset ); $after_shortened = true; // On the next iteration, will append with no before context. } } if ( $append_next ) { $cnt = count( $bits ); $bits[ $cnt - 1 ] .= $log . $after; } else { $bits[] = $before . $log . $after; } $append_next = $after_shortened; $last_offset = $offset; } $match_count += $match_cnt; $col_val = implode( ' [...] ', $bits ); WP_CLI::log( $matches_only ? $col_val : ( $one_line ? "{$table_column_val}:{$pk_val}{$col_val}" : "{$pk_val}{$col_val}" ) ); } } } } } if ( $stats ) { $table_count = count( $tables ); $skipped_count = count( $skipped ); $match_str = 1 === $match_count ? 'match' : 'matches'; $table_str = 1 === $table_count ? 'table' : 'tables'; $column_str = 1 === $column_count ? 'column' : 'columns'; $row_str = 1 === $row_count ? 'row' : 'rows'; $skipped_str = 1 === $skipped_count ? 'table skipped' : 'tables skipped'; if ( 0 !== $skipped_count ) { $skipped_str .= ': ' . implode( ', ', $skipped ); } $end_time = microtime( true ); $run_time = $end_time - $start_run_time; $search_time = $end_time - $start_search_time; $stats_msg = sprintf( "Found %d %s in %.3fs (%.3fs searching). Searched %d %s, %d %s, %d %s. %d %s.", $match_count, $match_str, $run_time, $search_time, $table_count, $table_str, $column_count, $column_str, $row_count, $row_str, $skipped_count, $skipped_str ); WP_ClI::success( $stats_msg ); } } private static function get_create_query() { $create_query = sprintf( 'CREATE DATABASE %s', self::esc_sql_ident( DB_NAME ) ); if ( defined( 'DB_CHARSET' ) && constant( 'DB_CHARSET' ) ) { $create_query .= sprintf( ' DEFAULT CHARSET %s', self::esc_sql_ident( DB_CHARSET ) ); } if ( defined( 'DB_COLLATE' ) && constant( 'DB_COLLATE' ) ) { $create_query .= sprintf( ' DEFAULT COLLATE %s', self::esc_sql_ident( DB_COLLATE ) ); } return $create_query; } private static function run_query( $query ) { self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', array( 'execute' => $query ) ); } private static function run( $cmd, $assoc_args = array(), $descriptors = null ) { $required = array( 'host' => DB_HOST, 'user' => DB_USER, 'pass' => DB_PASSWORD, ); if ( ! isset( $assoc_args['default-character-set'] ) && defined( 'DB_CHARSET' ) && constant( 'DB_CHARSET' ) ) { $required['default-character-set'] = constant( 'DB_CHARSET' ); } $final_args = array_merge( $assoc_args, $required ); Utils\run_mysql_command( $cmd, $final_args, $descriptors ); } /** * Get the column names of a db table differentiated into key columns and text columns and all columns. * * @param string $table The table name. * @return array A 3 element array consisting of an array of primary key column names, an array of text column names, and an array containing all column names. */ private static function get_columns( $table ) { global $wpdb; $table_sql = self::esc_sql_ident( $table ); $primary_keys = $text_columns = $all_columns = array(); $suppress_errors = $wpdb->suppress_errors(); if ( ( $results = $wpdb->get_results( "DESCRIBE $table_sql" ) ) ) { foreach ( $results as $col ) { if ( 'PRI' === $col->Key ) { $primary_keys[] = $col->Field; } if ( self::is_text_col( $col->Type ) ) { $text_columns[] = $col->Field; } $all_columns[] = $col->Field; } } $wpdb->suppress_errors( $suppress_errors ); return array( $primary_keys, $text_columns, $all_columns ); } /** * Whether a column is considered text or not. * * @param string Column type. * @bool True if text column, false otherwise. */ private static function is_text_col( $type ) { foreach ( array( 'text', 'varchar' ) as $token ) { if ( false !== strpos( $type, $token ) ) return true; } return false; } /** * Escapes a MySQL string for inclusion in a `LIKE` clause. BC wrapper around different WP versions of this. * * @param string $old String to escape. * @param string Escaped string. */ private static function esc_like( $old ) { global $wpdb; // Remove notices in 4.0 and support backwards compatibility if ( method_exists( $wpdb, 'esc_like' ) ) { // 4.0 $old = $wpdb->esc_like( $old ); } else { // 3.9 or less $old = like_escape( esc_sql( $old ) ); } return $old; } /** * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names. * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html * * @param string|array $idents A single identifier or an array of identifiers. * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings. */ private static function esc_sql_ident( $idents ) { $backtick = function ( $v ) { // Escape any backticks in the identifier by doubling. return '`' . str_replace( '`', '``', $v ) . '`'; }; if ( is_string( $idents ) ) { return $backtick( $idents ); } return array_map( $backtick, $idents ); } /** * Gets the color codes from the options if any, and returns the passed in array colorized with 2 elements per entry, a color code (or '') and a reset (or ''). * * @param array $assoc_args The associative argument array passed to the command. * @param array $colors Array of default percent color code strings keyed by the 3 color contexts 'table_column', 'id', 'match'. * @return array Array containing 3 2-element arrays. */ private function get_colors( $assoc_args, $colors ) { $color_reset = WP_CLI::colorize( '%n' ); $color_codes = implode( '', array_map( function ( $v ) { return substr( $v, 1 ); }, array_keys( \cli\Colors::getColors() ) ) ); $color_codes_regex = '/^(?:%[' . $color_codes . '])*$/'; foreach ( array_keys( $colors ) as $color_col ) { if ( false !== ( $col_color_flag = \WP_CLI\Utils\get_flag_value( $assoc_args, $color_col . '_color', false ) ) ) { if ( ! preg_match( $color_codes_regex, $col_color_flag, $matches ) ) { WP_CLI::warning( "Unrecognized percent color code '$col_color_flag' for '{$color_col}_color'." ); } else { $colors[ $color_col ] = $matches[0]; } } $colors[ $color_col ] = $colors[ $color_col ] ? array( WP_CLI::colorize( $colors[ $color_col ] ), $color_reset ) : array( '', '' ); } return $colors; } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) && ! class_exists( 'Package_Command' ) ) { require_once $autoload; } WP_CLI::add_command( 'package', 'Package_Command' ); <?php use \Composer\Composer; use \Composer\Config; use \Composer\Config\JsonConfigSource; use \Composer\DependencyResolver\Pool; use \Composer\EventDispatcher\Event; use \Composer\Factory; use \Composer\IO\NullIO; use \Composer\Installer; use \Composer\Json\JsonFile; use \Composer\Json\JsonManipulator; use \Composer\Package; use \Composer\Package\BasePackage; use \Composer\Package\PackageInterface; use \Composer\Package\Version\VersionParser; use \Composer\Package\Version\VersionSelector; use \Composer\Repository; use \Composer\Repository\CompositeRepository; use \Composer\Repository\ComposerRepository; use \Composer\Repository\RepositoryManager; use \Composer\Util\Filesystem; use \WP_CLI\ComposerIO; use \WP_CLI\Extractor; use \WP_CLI\Utils; /** * Runs WP-CLI package manager commands. * * WP-CLI packages are community-maintained projects built on WP-CLI. They can * contain WP-CLI commands, but they can also just extend WP-CLI in some way. * * Installable packages are listed in the * [Package Index](http://wp-cli.org/package-index/). * * Learn how to create your own command from the * [Commands Cookbook](http://wp-cli.org/docs/commands-cookbook/) * * ## EXAMPLES * * # List installed packages * $ wp package list * +-----------------------+------------------------------------------+---------+------------+ * | name | description | authors | version | * +-----------------------+------------------------------------------+---------+------------+ * | wp-cli/server-command | Start a development server for WordPress | | dev-master | * +-----------------------+------------------------------------------+---------+------------+ * * # Install the latest development version of the package * $ wp package install wp-cli/server-command * Installing package wp-cli/server-command (dev-master) * Updating /home/person/.wp-cli/packages/composer.json to require the package... * Using Composer to install the package... * --- * Loading composer repositories with package information * Updating dependencies * Resolving dependencies through SAT * Dependency resolution completed in 0.005 seconds * Analyzed 732 packages to resolve dependencies * Analyzed 1034 rules to resolve dependencies * - Installing package * Writing lock file * Generating autoload files * --- * Success: Package installed. * * # Uninstall package * $ wp package uninstall wp-cli/server-command * Removing require statement from /home/person/.wp-cli/packages/composer.json * Deleting package directory /home/person/.wp-cli/packages/vendor/wp-cli/server-command * Regenerating Composer autoload. * Success: Uninstalled package. * * @package WP-CLI * * @when before_wp_load */ class Package_Command extends WP_CLI_Command { const PACKAGE_INDEX_URL = 'https://wp-cli.org/package-index/'; private $pool = false; /** * Browse WP-CLI packages available for installation. * * Lists packages available for installation from the [Package Index](http://wp-cli.org/package-index/). * Although the package index will remain in place for backward compatibility reasons, it has been * deprecated and will not be updated further. Please refer to https://github.com/wp-cli/ideas/issues/51 * to read about its potential replacement. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each package: * * * name * * description * * authors * * version * * There are no optionally available fields. * * ## EXAMPLES * * $ wp package browse --format=yaml * --- * 10up/mu-migration: * name: 10up/mu-migration * description: A set of WP-CLI commands to support the migration of single WordPress instances to multisite * authors: Nícholas André * version: dev-master, dev-develop * aaemnnosttv/wp-cli-dotenv-command: * name: aaemnnosttv/wp-cli-dotenv-command * description: Dotenv commands for WP-CLI * authors: Evan Mattson * version: v0.1, v0.1-beta.1, v0.2, dev-master, dev-dev, dev-develop, dev-tests/behat * aaemnnosttv/wp-cli-http-command: * name: aaemnnosttv/wp-cli-http-command * description: WP-CLI command for using the WordPress HTTP API * authors: Evan Mattson * version: dev-master */ public function browse( $_, $assoc_args ) { if ( empty( $assoc_args['format'] ) || 'table' === $assoc_args['format'] ) { WP_CLI::line( WP_CLI::colorize( '%CAlthough the package index will remain in place for backward compatibility reasons, it has been deprecated and will not be updated further. Please refer to https://github.com/wp-cli/ideas/issues/51 to read about its potential replacement.%n' ) ); } $this->show_packages( 'browse', $this->get_community_packages(), $assoc_args ); } /** * Install a WP-CLI package. * * Packages are required to be a valid Composer package, and can be * specified as: * * * Package name from WP-CLI's package index. * * Git URL accessible by the current shell user. * * Path to a directory on the local machine. * * Local or remote .zip file. * * Packages are installed to `~/.wp-cli/packages/` by default. Use the * `WP_CLI_PACKAGES_DIR` environment variable to provide a custom path. * * When installing a local directory, WP-CLI simply registers a * reference to the directory. If you move or delete the directory, WP-CLI's * reference breaks. * * When installing a .zip file, WP-CLI extracts the package to * `~/.wp-cli/packages/local/<package-name>`. * * ## OPTIONS * * <name|git|path|zip> * : Name, git URL, directory path, or .zip file for the package to install. * Names can optionally include a version constraint * (e.g. wp-cli/server-command:@stable). * * ## EXAMPLES * * # Install the latest development version from the package index. * $ wp package install wp-cli/server-command * Installing package wp-cli/server-command (dev-master) * Updating /home/person/.wp-cli/packages/composer.json to require the package... * Using Composer to install the package... * --- * Loading composer repositories with package information * Updating dependencies * Resolving dependencies through SAT * Dependency resolution completed in 0.005 seconds * Analyzed 732 packages to resolve dependencies * Analyzed 1034 rules to resolve dependencies * - Installing package * Writing lock file * Generating autoload files * --- * Success: Package installed. * * # Install the latest stable version. * $ wp package install wp-cli/server-command:@stable * * # Install a package hosted at a git URL. * $ wp package install git@github.com:runcommand/hook.git * * # Install a package in a .zip file. * $ wp package install google-sitemap-generator-cli.zip */ public function install( $args, $assoc_args ) { list( $package_name ) = $args; $git_package = $dir_package = false; $version = 'dev-master'; if ( $this->is_git_repository( $package_name ) ) { $git_package = $package_name; preg_match( '#([^:\/]+\/[^\/]+)\.git#', $package_name, $matches ); if ( ! empty( $matches[1] ) ) { $package_name = $matches[1]; // Generate raw git URL of composer.json file. $raw_content_url = 'https://raw.githubusercontent.com/' . $package_name . '/master/composer.json'; // Convert composer.json JSON to Array. $composer_content_as_array = json_decode( WP_CLI\Utils\http_request( 'GET', $raw_content_url )->body, true ); // Package name in composer.json that is hosted on GitHub. $package_name_on_repo = $composer_content_as_array['name']; // If package name and repository name are not identical, then fix it. if ( $package_name !== $package_name_on_repo ) { $package_name = $package_name_on_repo; WP_CLI::warning( 'Package name mismatch...Updating the name with correct value.' ); } } else { WP_CLI::error( "Couldn't parse package name from expected path '<name>/<package>'." ); } } else if ( ( false !== strpos( $package_name, '://' ) && false !== stripos( $package_name, '.zip' ) ) || ( pathinfo( $package_name, PATHINFO_EXTENSION ) === 'zip' && is_file( $package_name ) ) ) { // Download the remote ZIP file to a temp directory if ( false !== strpos( $package_name, '://' ) ) { $temp = Utils\get_temp_dir() . uniqid('package_') . ".zip"; $options = array( 'timeout' => 600, 'filename' => $temp ); $response = Utils\http_request( 'GET', $package_name, null, array(), $options ); if ( 20 != substr( $response->status_code, 0, 2 ) ) { WP_CLI::error( "Couldn't download package." ); } $package_name = $temp; } $dir_package = Utils\get_temp_dir() . uniqid( 'package_' ); try { // Extract the package to get the package name Extractor::extract( $package_name, $dir_package ); list( $package_name, $version ) = self::get_package_name_and_version_from_dir_package( $dir_package ); // Move to a location based on the package name $local_dir = rtrim( WP_CLI::get_runner()->get_packages_dir_path(), '/' ) . '/local/'; $actual_dir_package = $local_dir . str_replace( '/', '-', $package_name ); Extractor::copy_overwrite_files( $dir_package, $actual_dir_package ); Extractor::rmdir( $dir_package ); // Behold, the extracted package $dir_package = $actual_dir_package; } catch ( Exception $e ) { WP_CLI::error( $e->getMessage() ); } } else if ( is_dir( $package_name ) && file_exists( $package_name . '/composer.json' ) ) { $dir_package = $package_name; if ( ! Utils\is_path_absolute( $dir_package ) ) { $dir_package = getcwd() . DIRECTORY_SEPARATOR . $dir_package; } list( $package_name, $version ) = self::get_package_name_and_version_from_dir_package( $dir_package ); } else { if ( false !== strpos( $package_name, ':' ) ) { list( $package_name, $version ) = explode( ':', $package_name ); } $package = $this->get_package_by_shortened_identifier( $package_name ); if ( $this->is_git_repository( $package ) ) { $git_package = $package; } if ( ! $package ) { WP_CLI::error( "Invalid package." ); } } WP_CLI::log( sprintf( "Installing package %s (%s)", $package_name, $version ) ); $composer_json_obj = $this->get_composer_json(); // Add the 'require' to composer.json WP_CLI::log( sprintf( "Updating %s to require the package...", $composer_json_obj->getPath() ) ); $composer_backup = file_get_contents( $composer_json_obj->getPath() ); $json_manipulator = new JsonManipulator( $composer_backup ); $json_manipulator->addMainKey( 'name', 'wp-cli/wp-cli' ); $json_manipulator->addMainKey( 'version', self::get_wp_cli_version_composer() ); $json_manipulator->addLink( 'require', $package_name, $version ); $json_manipulator->addConfigSetting( 'secure-http', true ); if ( $git_package ) { WP_CLI::log( sprintf( 'Registering %s as a VCS repository...', $git_package ) ); $json_manipulator->addRepository( $package_name, array( 'type' => 'vcs', 'url' => $git_package ) ); } else if ( $dir_package ) { WP_CLI::log( sprintf( 'Registering %s as a path repository...', $dir_package ) ); $json_manipulator->addRepository( $package_name, array( 'type' => 'path', 'url' => $dir_package ) ); } $composer_backup_decoded = json_decode( $composer_backup, true ); // If the composer file does not contain the current package index repository, refresh the repository definition. if ( empty( $composer_backup_decoded['repositories']['wp-cli']['url'] ) || self::PACKAGE_INDEX_URL != $composer_backup_decoded['repositories']['wp-cli']['url'] ) { WP_CLI::log( 'Updating package index repository url...' ); $json_manipulator->addRepository( 'wp-cli', array( 'type' => 'composer', 'url' => self::PACKAGE_INDEX_URL ) ); } file_put_contents( $composer_json_obj->getPath(), $json_manipulator->getContents() ); try { $composer = $this->get_composer(); } catch( Exception $e ) { WP_CLI::error( $e->getMessage() ); } // Set up the EventSubscriber $event_subscriber = new \WP_CLI\PackageManagerEventSubscriber; $composer->getEventDispatcher()->addSubscriber( $event_subscriber ); // Set up the installer $install = Installer::create( new ComposerIO, $composer ); $install->setUpdate( true ); // Installer class will only override composer.lock with this flag $install->setPreferSource( true ); // Use VCS when VCS for easier contributions. // Try running the installer, but revert composer.json if failed WP_CLI::log( 'Using Composer to install the package...' ); WP_CLI::log( '---' ); $res = false; try { $res = $install->run(); } catch ( Exception $e ) { WP_CLI::warning( $e->getMessage() ); } WP_CLI::log( '---' ); if ( 0 === $res ) { WP_CLI::success( "Package installed." ); } else { file_put_contents( $composer_json_obj->getPath(), $composer_backup ); $res_msg = false !== $res ? " (Composer return code {$res})" : ''; WP_CLI::error( "Package installation failed{$res_msg}. Reverted composer.json" ); } } /** * List installed WP-CLI packages. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each package: * * * name * * authors * * version * * update * * update_version * * These fields are optionally available: * * * description * * ## EXAMPLES * * $ wp package list * +-----------------------+------------------------------------------+---------+------------+ * | name | description | authors | version | * +-----------------------+------------------------------------------+---------+------------+ * | wp-cli/server-command | Start a development server for WordPress | | dev-master | * +-----------------------+------------------------------------------+---------+------------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { $this->show_packages( 'list', $this->get_installed_packages(), $assoc_args ); } /** * Get the path to an installed WP-CLI package, or the package directory. * * If you want to contribute to a package, this is a great way to jump to it. * * ## OPTIONS * * [<name>] * : Name of the package to get the directory for. * * ## EXAMPLES * * # Get package path * $ wp package path * /home/person/.wp-cli/packages/ * * # Change directory to package path * $ cd $(wp package path) && pwd * /home/vagrant/.wp-cli/packages */ function path( $args ) { $packages_dir = WP_CLI::get_runner()->get_packages_dir_path(); if ( ! empty( $args ) ) { $packages_dir .= 'vendor/' . $args[0]; if ( ! is_dir( $packages_dir ) ) { WP_CLI::error( 'Invalid package name.' ); } } WP_CLI::line( $packages_dir ); } /** * Update all installed WP-CLI packages to their latest version. * * ## EXAMPLES * * $ wp package update * Using Composer to update packages... * --- * Loading composer repositories with package information * Updating dependencies * Resolving dependencies through SAT * Dependency resolution completed in 0.074 seconds * Analyzed 1062 packages to resolve dependencies * Analyzed 22383 rules to resolve dependencies * Writing lock file * Generating autoload files * --- * Success: Packages updated. */ public function update() { try { $composer = $this->get_composer(); } catch( Exception $e ) { WP_CLI::error( $e->getMessage() ); } // Set up the EventSubscriber $event_subscriber = new \WP_CLI\PackageManagerEventSubscriber; $composer->getEventDispatcher()->addSubscriber( $event_subscriber ); // Set up the installer $install = Installer::create( new ComposerIO, $composer ); $install->setUpdate( true ); // Installer class will only override composer.lock with this flag $install->setPreferSource( true ); // Use VCS when VCS for easier contributions. WP_CLI::log( 'Using Composer to update packages...' ); WP_CLI::log( '---' ); $res = false; try { $res = $install->run(); } catch ( Exception $e ) { WP_CLI::warning( $e->getMessage() ); } WP_CLI::log( '---' ); if ( 0 === $res ) { WP_CLI::success( "Packages updated." ); } else { $res_msg = false !== $res ? " (Composer return code {$res})" : ''; WP_CLI::error( "Failed to update packages{$res_msg}." ); } } /** * Uninstall a WP-CLI package. * * ## OPTIONS * * <name> * : Name of the package to uninstall. * * ## EXAMPLES * * $ wp package uninstall wp-cli/server-command * Removing require statement from /home/person/.wp-cli/packages/composer.json * Deleting package directory /home/person/.wp-cli/packages/vendor/wp-cli/server-command * Regenerating Composer autoload. * Success: Uninstalled package. */ public function uninstall( $args ) { list( $package_name ) = $args; try { $composer = $this->get_composer(); } catch( Exception $e ) { WP_CLI::error( $e->getMessage() ); } if ( false === ( $package = $this->get_installed_package_by_name( $package_name ) ) ) { WP_CLI::error( "Package not installed." ); } $composer_json_obj = $this->get_composer_json(); // Remove the 'require' from composer.json. $json_path = $composer_json_obj->getPath(); WP_CLI::log( sprintf( 'Removing require statement from %s', $json_path ) ); $composer_backup = file_get_contents( $composer_json_obj->getPath() ); $manipulator = new JsonManipulator( $composer_backup ); $manipulator->removeSubNode( 'require', $package_name ); $composer_json_array = json_decode( $composer_backup ); // Remove the 'repository' details from composer.json. if ( is_object( $composer_json_array ) && property_exists( $composer_json_array->repositories, $package_name ) ) { WP_CLI::log( sprintf( 'Removing repository details from %s', $json_path ) ); $manipulator->removeRepository( $package_name ); } file_put_contents( $composer_json_obj->getPath(), $manipulator->getContents() ); try { $composer = $this->get_composer(); } catch ( Exception $e ) { WP_CLI::error( $e->getMessage() ); } // Set up the installer. $install = Installer::create( new NullIO, $composer ); $install->setUpdate( true ); // Installer class will only override composer.lock with this flag $install->setPreferSource( true ); // Use VCS when VCS for easier contributions. WP_CLI::log( 'Removing package directories and regenerating autoloader...' ); $res = false; try { $res = $install->run(); } catch ( Exception $e ) { WP_CLI::warning( $e->getMessage() ); } if ( 0 === $res ) { WP_CLI::success( "Uninstalled package." ); } else { file_put_contents( $composer_json_obj->getPath(), $composer_backup ); $res_msg = false !== $res ? " (Composer return code {$res})" : ''; WP_CLI::error( "Package removal failed{$res_msg}." ); } } /** * Check whether a package is a WP-CLI community package based * on membership in our package index. * * @param object $package A package object * @return bool */ private function is_community_package( $package ) { return $this->package_index()->hasPackage( $package ); } /** * Get a Composer instance. */ private function get_composer() { $composer_path = $this->get_composer_json_path(); // Composer's auto-load generating code makes some assumptions about where // the 'vendor-dir' is, and where Composer is running from. // Best to just pretend we're installing a package from ~/.wp-cli or similar chdir( pathinfo( $composer_path, PATHINFO_DIRNAME ) ); // Prevent DateTime error/warning when no timezone set date_default_timezone_set( @date_default_timezone_get() ); return Factory::create( new NullIO, $composer_path ); } /** * Get all of the community packages. * * @return array */ private function get_community_packages() { static $community_packages; if ( null === $community_packages ) { try { $community_packages = $this->package_index()->getPackages(); } catch( Exception $e ) { WP_CLI::error( $e->getMessage() ); } } return $community_packages; } /** * Get the package index instance * * We need to construct the instance manually, because there's no way to select * a particular instance using $composer->getRepositoryManager() * * @return ComposerRepository */ private function package_index() { static $package_index; if ( !$package_index ) { $config = new Config(); $config->merge( array( 'config' => array( 'secure-http' => true, 'home' => dirname( $this->get_composer_json_path() ), ) )); $config->setConfigSource( new JsonConfigSource( $this->get_composer_json() ) ); try { $package_index = new ComposerRepository( array( 'url' => self::PACKAGE_INDEX_URL ), new NullIO, $config ); } catch ( Exception $e ) { WP_CLI::error( $e->getMessage() ); } } return $package_index; } /** * Display a set of packages * * @param string $context * @param array * @param array */ private function show_packages( $context, $packages, $assoc_args ) { if ( 'list' === $context ) { $default_fields = array( 'name', 'authors', 'version', 'update', 'update_version', ); } else if ( 'browse' === $context ) { $default_fields = array( 'name', 'description', 'authors', 'version', ); } $defaults = array( 'fields' => implode( ',', $default_fields ), 'format' => 'table' ); $assoc_args = array_merge( $defaults, $assoc_args ); $list = array(); foreach ( $packages as $package ) { $name = $package->getName(); if ( isset( $list[ $name ] ) ) { $list[ $name ]['version'][] = $package->getPrettyVersion(); } else { $package_output = array(); $package_output['name'] = $package->getName(); $package_output['description'] = $package->getDescription(); $package_output['authors'] = implode( ', ', array_column( (array) $package->getAuthors(), 'name' ) ); $package_output['version'] = array( $package->getPrettyVersion() ); $update = 'none'; $update_version = ''; if ( 'list' === $context ) { $latest = $this->find_latest_package( $package, $this->get_composer(), null ); if ( $latest && $latest->getFullPrettyVersion() !== $package->getFullPrettyVersion() ) { $update = 'available'; $update_version = $latest->getPrettyVersion(); } } $package_output['update'] = $update; $package_output['update_version'] = $update_version; $list[ $package_output['name'] ] = $package_output; } } $list = array_map( function( $package ){ $package['version'] = implode( ', ', $package['version'] ); return $package; }, $list ); ksort( $list ); if ( 'ids' === $assoc_args['format'] ) { $list = array_keys( $list ); } WP_CLI\Utils\format_items( $assoc_args['format'], $list, $assoc_args['fields'] ); } /** * Get a package by its shortened identifier. * * A shortened identifier has the form `<vendor>/<package>`. * * This method first checks the deprecated package index, for BC reasons, * and then falls back to the corresponding GitHub URL. */ private function get_package_by_shortened_identifier( $package_name ) { // Check the package index first, so we don't break existing behavior. foreach( $this->get_community_packages() as $package ) { if ( $package_name == $package->getName() ) { return $package; } } // Fall back to GitHub URL if we had no match in the package index. $response = Utils\http_request( 'GET', "https://github.com/{$package_name}.git" ); if ( 20 === (int) substr( $response->status_code, 0, 2 ) ) { return "git@github.com:{$package_name}.git"; } return false; } /** * Get the installed community packages. */ private function get_installed_packages() { try { $composer = $this->get_composer(); } catch( Exception $e ) { WP_CLI::error( $e->getMessage() ); } $repo = $composer->getRepositoryManager()->getLocalRepository(); $existing = json_decode( file_get_contents( $this->get_composer_json_path() ), true ); $installed_package_keys = ! empty( $existing['require'] ) ? array_keys( $existing['require'] ) : array(); if ( empty( $installed_package_keys ) ) { return array(); } $installed_packages = array(); foreach( $repo->getCanonicalPackages() as $package ) { if ( in_array( $package->getName(), $installed_package_keys, true ) ) { $installed_packages[] = $package; } } return $installed_packages; } /** * Get an installed package by its name. */ private function get_installed_package_by_name( $package_name ) { foreach( $this->get_installed_packages() as $package ) { if ( $package_name == $package->getName() ) { return $package; } } return false; } /** * Check if the package name provided is already installed. */ private function is_package_installed( $package_name ) { if ( $this->get_installed_package_by_name( $package_name ) ) { return true; } else { return false; } } /** * Get the name of the package from the composer.json in a directory path * * @param string $dir_package * @return string */ private static function get_package_name_and_version_from_dir_package( $dir_package ) { $composer_file = $dir_package . '/composer.json'; $package_name = ''; $version = 'dev-master'; if ( file_exists( $composer_file ) ) { $composer_data = json_decode( file_get_contents( $composer_file ), true ); if ( ! empty( $composer_data['name'] ) ) { $package_name = $composer_data['name']; } if ( ! empty( $composer_data['version'] ) ) { $version = $composer_data['version']; } } if ( empty( $package_name ) ) { WP_CLI::error( "Invalid package." ); } return array( $package_name, $version ); } /** * Get the composer.json object */ private function get_composer_json() { return new JsonFile( $this->get_composer_json_path() ); } /** * Get the path to composer.json */ private function get_composer_json_path() { static $composer_path; if ( null === $composer_path ) { if ( getenv( 'WP_CLI_PACKAGES_DIR' ) ) { $composer_path = rtrim( getenv( 'WP_CLI_PACKAGES_DIR' ), '/' ) . '/composer.json'; } else { $home = getenv( 'HOME' ); if ( ! $home ) { // In Windows $HOME may not be defined $home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); } $composer_path = rtrim( $home, '/\\' ) . '/.wp-cli/packages/composer.json'; } // `composer.json` and its directory might need to be created if ( ! file_exists( $composer_path ) ) { $this->create_default_composer_json( $composer_path ); } } return $composer_path; } /** * Get the WP-CLI version for composer.json */ private static function get_wp_cli_version_composer() { preg_match( '#^[0-9\.]+(-(alpha|beta)[^-]{0,})?#', WP_CLI_VERSION, $matches ); return isset( $matches[0] ) ? $matches[0] : ''; } /** * Create a default composer.json, should one not already exist * * @param string $composer_path Where the composer.json should be created * @return true|WP_Error */ private function create_default_composer_json( $composer_path ) { $composer_dir = pathinfo( $composer_path, PATHINFO_DIRNAME ); if ( ! is_dir( $composer_dir ) ) { \WP_CLI\Process::create( WP_CLI\Utils\esc_cmd( 'mkdir -p %s', $composer_dir ) )->run(); } if ( ! is_dir( $composer_dir ) ) { WP_CLI::error( "Composer directory for packages couldn't be created." ); } $json_file = new JsonFile( $composer_path ); $author = (object)array( 'name' => 'WP-CLI', 'email' => 'noreply@wpcli.org' ); $repositories = (object)array( 'wp-cli' => (object)array( 'type' => 'composer', 'url' => self::PACKAGE_INDEX_URL, ), ); $options = array( 'name' => 'wp-cli/wp-cli', 'description' => 'Installed community packages used by WP-CLI', 'version' => self::get_wp_cli_version_composer(), 'authors' => array( $author ), 'homepage' => self::PACKAGE_INDEX_URL, 'require' => new stdClass, 'require-dev' => new stdClass, 'minimum-stability' => 'dev', 'license' => 'MIT', 'repositories' => $repositories, ); try { $json_file->write( $options ); } catch( Exception $e ) { WP_CLI::error( $e->getMessage() ); } return true; } /** * Given a package, this finds the latest package matching it * * @param PackageInterface $package * @param Composer $composer * @param string $phpVersion * @param bool $minorOnly * * @return PackageInterface|null */ private function find_latest_package( PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false ) { // find the latest version allowed in this pool $name = $package->getName(); $versionSelector = new VersionSelector($this->get_pool($composer)); $stability = $composer->getPackage()->getMinimumStability(); $flags = $composer->getPackage()->getStabilityFlags(); if (isset($flags[$name])) { $stability = array_search($flags[$name], BasePackage::$stabilities, true); } $bestStability = $stability; if ($composer->getPackage()->getPreferStable()) { $bestStability = $package->getStability(); } $targetVersion = null; if (0 === strpos($package->getVersion(), 'dev-')) { $targetVersion = $package->getVersion(); } if ($targetVersion === null && $minorOnly) { $targetVersion = '^' . $package->getVersion(); } return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability); } private function get_pool( Composer $composer ) { if (!$this->pool) { $this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); $this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); } return $this->pool; } /** * Check whether a given package is a git repository. * * @param string $package Package name to check. * * @return bool Whether the package is a git repository. */ private function is_git_repository( $package ) { return '.git' === strtolower( substr( $package, -4, 4 ) ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'scaffold', 'Scaffold_Command' ); <?php /* * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * and is licensed under the MIT license. For more information, see * <http://www.doctrine-project.org>. */ namespace WP_CLI; use WP_CLI; /** * Doctrine inflector has static methods for inflecting text. * * The methods in these classes are from several different sources collected * across several different php projects and several different authors. The * original author names and emails are not known. * * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications. * * @link www.doctrine-project.org * @since 1.0 * @author Konsta Vesterinen <kvesteri@cc.hut.fi> * @author Jonathan H. Wage <jonwage@gmail.com> */ class Inflector { /** * Plural inflector rules. * * @var array */ private static $plural = array( 'rules' => array( '/(s)tatus$/i' => '\1\2tatuses', '/(quiz)$/i' => '\1zes', '/^(ox)$/i' => '\1\2en', '/([m|l])ouse$/i' => '\1ice', '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', '/(x|ch|ss|sh)$/i' => '\1es', '/([^aeiouy]|qu)y$/i' => '\1ies', '/(hive)$/i' => '\1s', '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', '/sis$/i' => 'ses', '/([ti])um$/i' => '\1a', '/(p)erson$/i' => '\1eople', '/(m)an$/i' => '\1en', '/(c)hild$/i' => '\1hildren', '/(f)oot$/i' => '\1eet', '/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes', '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', '/us$/i' => 'uses', '/(alias)$/i' => '\1es', '/(analys|ax|cris|test|thes)is$/i' => '\1es', '/s$/' => 's', '/^$/' => '', '/$/' => 's', ), 'uninflected' => array( '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie' ), 'irregular' => array( 'atlas' => 'atlases', 'axe' => 'axes', 'beef' => 'beefs', 'brother' => 'brothers', 'cafe' => 'cafes', 'chateau' => 'chateaux', 'child' => 'children', 'cookie' => 'cookies', 'corpus' => 'corpuses', 'cow' => 'cows', 'criterion' => 'criteria', 'curriculum' => 'curricula', 'demo' => 'demos', 'domino' => 'dominoes', 'echo' => 'echoes', 'foot' => 'feet', 'fungus' => 'fungi', 'ganglion' => 'ganglions', 'genie' => 'genies', 'genus' => 'genera', 'graffito' => 'graffiti', 'hippopotamus' => 'hippopotami', 'hoof' => 'hoofs', 'human' => 'humans', 'iris' => 'irises', 'leaf' => 'leaves', 'loaf' => 'loaves', 'man' => 'men', 'medium' => 'media', 'memorandum' => 'memoranda', 'money' => 'monies', 'mongoose' => 'mongooses', 'motto' => 'mottoes', 'move' => 'moves', 'mythos' => 'mythoi', 'niche' => 'niches', 'nucleus' => 'nuclei', 'numen' => 'numina', 'occiput' => 'occiputs', 'octopus' => 'octopuses', 'opus' => 'opuses', 'ox' => 'oxen', 'penis' => 'penises', 'person' => 'people', 'plateau' => 'plateaux', 'runner-up' => 'runners-up', 'sex' => 'sexes', 'soliloquy' => 'soliloquies', 'son-in-law' => 'sons-in-law', 'syllabus' => 'syllabi', 'testis' => 'testes', 'thief' => 'thieves', 'tooth' => 'teeth', 'tornado' => 'tornadoes', 'trilby' => 'trilbys', 'turf' => 'turfs', 'volcano' => 'volcanoes', ) ); /** * Singular inflector rules. * * @var array */ private static $singular = array( 'rules' => array( '/(s)tatuses$/i' => '\1\2tatus', '/^(.*)(menu)s$/i' => '\1\2', '/(quiz)zes$/i' => '\\1', '/(matr)ices$/i' => '\1ix', '/(vert|ind)ices$/i' => '\1ex', '/^(ox)en/i' => '\1', '/(alias)(es)*$/i' => '\1', '/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o', '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', '/([ftw]ax)es/i' => '\1', '/(analys|ax|cris|test|thes)es$/i' => '\1is', '/(shoe|slave)s$/i' => '\1', '/(o)es$/i' => '\1', '/ouses$/' => 'ouse', '/([^a])uses$/' => '\1us', '/([m|l])ice$/i' => '\1ouse', '/(x|ch|ss|sh)es$/i' => '\1', '/(m)ovies$/i' => '\1\2ovie', '/(s)eries$/i' => '\1\2eries', '/([^aeiouy]|qu)ies$/i' => '\1y', '/([lr])ves$/i' => '\1f', '/(tive)s$/i' => '\1', '/(hive)s$/i' => '\1', '/(drive)s$/i' => '\1', '/([^fo])ves$/i' => '\1fe', '/(^analy)ses$/i' => '\1sis', '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', '/([ti])a$/i' => '\1um', '/(p)eople$/i' => '\1\2erson', '/(m)en$/i' => '\1an', '/(c)hildren$/i' => '\1\2hild', '/(f)eet$/i' => '\1oot', '/(n)ews$/i' => '\1\2ews', '/eaus$/' => 'eau', '/^(.*us)$/' => '\\1', '/s$/i' => '', ), 'uninflected' => array( '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss', ), 'irregular' => array( 'criteria' => 'criterion', 'curves' => 'curve', 'emphases' => 'emphasis', 'foes' => 'foe', 'hoaxes' => 'hoax', 'media' => 'medium', 'neuroses' => 'neurosis', 'waves' => 'wave', 'oases' => 'oasis', ) ); /** * Words that should not be inflected. * * @var array */ private static $uninflected = array( 'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus', 'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps', 'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder', 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti', 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings', 'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media', 'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese', 'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese', 'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors', 'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine', 'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting', 'wildebeest', 'Yengeese' ); /** * Method cache array. * * @var array */ private static $cache = array(); /** * The initial state of Inflector so reset() works. * * @var array */ private static $initialState = array(); /** * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. * * @param string $word The word to tableize. * * @return string The tableized word. */ public static function tableize($word) { return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); } /** * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. * * @param string $word The word to classify. * * @return string The classified word. */ public static function classify($word) { return str_replace(" ", "", ucwords(strtr($word, "_-", " "))); } /** * Camelizes a word. This uses the classify() method and turns the first character to lowercase. * * @param string $word The word to camelize. * * @return string The camelized word. */ public static function camelize($word) { return lcfirst(self::classify($word)); } /** * Uppercases words with configurable delimeters between words. * * Takes a string and capitalizes all of the words, like PHP's built-in * ucwords function. This extends that behavior, however, by allowing the * word delimeters to be configured, rather than only separating on * whitespace. * * Here is an example: * <code> * <?php * $string = 'top-o-the-morning to all_of_you!'; * echo \Doctrine\Common\Inflector\Inflector::ucwords($string); * // Top-O-The-Morning To All_of_you! * * echo \Doctrine\Common\Inflector\Inflector::ucwords($string, '-_ '); * // Top-O-The-Morning To All_Of_You! * ?> * </code> * * @param string $string The string to operate on. * @param string $delimiters A list of word separators. * * @return string The string with all delimeter-separated words capitalized. */ public static function ucwords($string, $delimiters = " \n\t\r\0\x0B-") { return preg_replace_callback( '/[^' . preg_quote($delimiters, '/') . ']+/', function($matches) { return ucfirst($matches[0]); }, $string ); } /** * Clears Inflectors inflected value caches, and resets the inflection * rules to the initial values. * * @return void */ public static function reset() { if (empty(self::$initialState)) { self::$initialState = get_class_vars('Inflector'); return; } foreach (self::$initialState as $key => $val) { if ($key != 'initialState') { self::${$key} = $val; } } } /** * Adds custom inflection $rules, of either 'plural' or 'singular' $type. * * ### Usage: * * {{{ * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables')); * Inflector::rules('plural', array( * 'rules' => array('/^(inflect)ors$/i' => '\1ables'), * 'uninflected' => array('dontinflectme'), * 'irregular' => array('red' => 'redlings') * )); * }}} * * @param string $type The type of inflection, either 'plural' or 'singular' * @param array $rules An array of rules to be added. * @param boolean $reset If true, will unset default inflections for all * new rules that are being defined in $rules. * * @return void */ public static function rules($type, $rules, $reset = false) { foreach ($rules as $rule => $pattern) { if ( ! is_array($pattern)) { continue; } if ($reset) { self::${$type}[$rule] = $pattern; } else { self::${$type}[$rule] = ($rule === 'uninflected') ? array_merge($pattern, self::${$type}[$rule]) : $pattern + self::${$type}[$rule]; } unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]); if (isset(self::${$type}['merged'][$rule])) { unset(self::${$type}['merged'][$rule]); } if ($type === 'plural') { self::$cache['pluralize'] = self::$cache['tableize'] = array(); } elseif ($type === 'singular') { self::$cache['singularize'] = array(); } } self::${$type}['rules'] = $rules + self::${$type}['rules']; } /** * Returns a word in plural form. * * @param string $word The word in singular form. * * @return string The word in plural form. */ public static function pluralize($word) { if (isset(self::$cache['pluralize'][$word])) { return self::$cache['pluralize'][$word]; } if (!isset(self::$plural['merged']['irregular'])) { self::$plural['merged']['irregular'] = self::$plural['irregular']; } if (!isset(self::$plural['merged']['uninflected'])) { self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected); } if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) { self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')'; self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')'; } if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) { self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1); return self::$cache['pluralize'][$word]; } if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) { self::$cache['pluralize'][$word] = $word; return $word; } foreach (self::$plural['rules'] as $rule => $replacement) { if (preg_match($rule, $word)) { self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word); return self::$cache['pluralize'][$word]; } } } /** * Returns a word in singular form. * * @param string $word The word in plural form. * * @return string The word in singular form. */ public static function singularize($word) { if (isset(self::$cache['singularize'][$word])) { return self::$cache['singularize'][$word]; } if (!isset(self::$singular['merged']['uninflected'])) { self::$singular['merged']['uninflected'] = array_merge( self::$singular['uninflected'], self::$uninflected ); } if (!isset(self::$singular['merged']['irregular'])) { self::$singular['merged']['irregular'] = array_merge( self::$singular['irregular'], array_flip(self::$plural['irregular']) ); } if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) { self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')'; self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')'; } if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) { self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1); return self::$cache['singularize'][$word]; } if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) { self::$cache['singularize'][$word] = $word; return $word; } foreach (self::$singular['rules'] as $rule => $replacement) { if (preg_match($rule, $word)) { self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word); return self::$cache['singularize'][$word]; } } self::$cache['singularize'][$word] = $word; return $word; } } <?php use WP_CLI\Utils; use WP_CLI\Process; use WP_CLI\Inflector; /** * Generate code for post types, taxonomies, plugins, child themes, etc. * * ## EXAMPLES * * # Generate a new plugin with unit tests * $ wp scaffold plugin sample-plugin * Success: Created plugin files. * Success: Created test files. * * # Generate theme based on _s * $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe" * Success: Created theme 'Sample Theme'. * * # Generate code for post type registration in given theme * $ wp scaffold post-type movie --label=Movie --theme=simple-life * Success: Created /var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php * * @package wp-cli */ class Scaffold_Command extends WP_CLI_Command { /** * Generate PHP code for registering a custom post type. * * ## OPTIONS * * <slug> * : The internal name of the post type. * * [--label=<label>] * : The text used to translate the update messages. * * [--textdomain=<textdomain>] * : The textdomain to use for the labels. * * [--dashicon=<dashicon>] * : The dashicon to use in the menu. * * [--theme] * : Create a file in the active theme directory, instead of sending to * STDOUT. Specify a theme with `--theme=<theme>` to have the file placed in that theme. * * [--plugin=<plugin>] * : Create a file in the given plugin's directory, instead of sending to STDOUT. * * [--raw] * : Just generate the `register_post_type()` call and nothing else. * * [--force] * : Overwrite files that already exist. * * ## EXAMPLES * * # Generate a 'movie' post type for the 'simple-life' theme * $ wp scaffold post-type movie --label=Movie --theme=simple-life * Success: Created '/var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php'. * * @subcommand post-type * * @alias cpt */ public function post_type( $args, $assoc_args ) { if ( strlen( $args[0] ) > 20 ) { WP_CLI::error( "Post type slugs cannot exceed 20 characters in length." ); } $defaults = array( 'textdomain' => '', 'dashicon' => 'admin-post', ); $this->_scaffold( $args[0], $assoc_args, $defaults, '/post-types/', array( 'post_type.mustache', 'post_type_extended.mustache', ) ); } /** * Generate PHP code for registering a custom taxonomy. * * ## OPTIONS * * <slug> * : The internal name of the taxonomy. * * [--post_types=<post-types>] * : Post types to register for use with the taxonomy. * * [--label=<label>] * : The text used to translate the update messages. * * [--textdomain=<textdomain>] * : The textdomain to use for the labels. * * [--theme] * : Create a file in the active theme directory, instead of sending to * STDOUT. Specify a theme with `--theme=<theme>` to have the file placed in that theme. * * [--plugin=<plugin>] * : Create a file in the given plugin's directory, instead of sending to STDOUT. * * [--raw] * : Just generate the `register_taxonomy()` call and nothing else. * * [--force] * : Overwrite files that already exist. * * ## EXAMPLES * * # Generate PHP code for registering a custom taxonomy and save in a file * $ wp scaffold taxonomy venue --post_types=event,presentation > taxonomy.php * * @subcommand taxonomy * * @alias tax */ public function taxonomy( $args, $assoc_args ) { $defaults = array( 'textdomain' => '', 'post_types' => "'post'", ); if ( isset( $assoc_args['post_types'] ) ) { $assoc_args['post_types'] = $this->quote_comma_list_elements( $assoc_args['post_types'] ); } $this->_scaffold( $args[0], $assoc_args, $defaults, '/taxonomies/', array( 'taxonomy.mustache', 'taxonomy_extended.mustache' ) ); } private function _scaffold( $slug, $assoc_args, $defaults, $subdir, $templates ) { $wp_filesystem = $this->init_wp_filesystem(); $control_args = $this->extract_args( $assoc_args, array( 'label' => preg_replace( '/_|-/', ' ', strtolower( $slug ) ), 'theme' => false, 'plugin' => false, 'raw' => false, ) ); $vars = $this->extract_args( $assoc_args, $defaults ); $vars['slug'] = $slug; $vars['textdomain'] = $this->get_textdomain( $vars['textdomain'], $control_args ); $vars['label'] = $control_args['label']; $vars['label_ucfirst'] = ucfirst( $vars['label'] ); $vars['label_plural'] = $this->pluralize( $vars['label'] ); $vars['label_plural_ucfirst'] = ucfirst( $vars['label_plural'] ); // We use the machine name for function declarations $machine_name = preg_replace( '/-/', '_', $slug ); $machine_name_plural = $this->pluralize( $slug ); list( $raw_template, $extended_template ) = $templates; $raw_output = self::mustache_render( $raw_template, $vars ); if ( ! $control_args['raw'] ) { $vars = array_merge( $vars, array( 'machine_name' => $machine_name, 'output' => $raw_output, ) ); $final_output = self::mustache_render( $extended_template, $vars ); } else { $final_output = $raw_output; } if ( $path = $this->get_output_path( $control_args, $subdir ) ) { $filename = $path . $slug . '.php'; $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $files_written = $this->create_files( array( $filename => $final_output ), $force ); $this->log_whether_files_written( $files_written, $skip_message = "Skipped creating '$filename'.", $success_message = "Created '$filename'." ); } else { // STDOUT echo $final_output; } } /** * Generate starter code for a theme based on _s. * * See the [Underscores website](https://underscores.me/) for more details. * * ## OPTIONS * * <slug> * : The slug for the new theme, used for prefixing functions. * * [--activate] * : Activate the newly downloaded theme. * * [--enable-network] * : Enable the newly downloaded theme for the entire network. * * [--theme_name=<title>] * : What to put in the 'Theme Name:' header in 'style.css'. * * [--author=<full-name>] * : What to put in the 'Author:' header in 'style.css'. * * [--author_uri=<uri>] * : What to put in the 'Author URI:' header in 'style.css'. * * [--sassify] * : Include stylesheets as SASS. * * [--force] * : Overwrite files that already exist. * * ## EXAMPLES * * # Generate a theme with name "Sample Theme" and author "John Doe" * $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe" * Success: Created theme 'Sample Theme'. */ public function _s( $args, $assoc_args ) { $theme_slug = $args[0]; $theme_path = WP_CONTENT_DIR . "/themes"; $url = "https://underscores.me"; $timeout = 30; if ( ! preg_match( '/^[a-z_]\w+$/i', str_replace( '-', '_', $theme_slug ) ) ) { WP_CLI::error( "Invalid theme slug specified. Theme slugs can only contain letters, numbers, underscores and hyphens, and can only start with a letter or underscore." ); } $data = wp_parse_args( $assoc_args, array( 'theme_name' => ucfirst( $theme_slug ), 'author' => "Me", 'author_uri' => "", ) ); $_s_theme_path = "$theme_path/$data[theme_name]"; if ( $error_msg = $this->check_target_directory( "theme", $_s_theme_path ) ) { WP_CLI::error( "Invalid theme slug specified. {$error_msg}" ); } $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $should_write_file = $this->prompt_if_files_will_be_overwritten( $_s_theme_path, $force ); if ( ! $should_write_file ) { WP_CLI::log( 'No files created' ); die; } $theme_description = "Custom theme: " . $data['theme_name'] . ", developed by " . $data['author']; $body = array(); $body['underscoresme_name'] = $data['theme_name']; $body['underscoresme_slug'] = $theme_slug; $body['underscoresme_author'] = $data['author']; $body['underscoresme_author_uri'] = $data['author_uri']; $body['underscoresme_description'] = $theme_description; $body['underscoresme_generate_submit'] = "Generate"; $body['underscoresme_generate'] = "1"; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'sassify' ) ) { $body['underscoresme_sass'] = 1; } $tmpfname = wp_tempnam( $url ); $response = wp_remote_post( $url, array( 'timeout' => $timeout, 'body' => $body, 'stream' => true, 'filename' => $tmpfname, ) ); if ( is_wp_error( $response ) ) { WP_CLI::error( $response ); } $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 != $response_code ) { WP_CLI::error( "Couldn't create theme (received $response_code response)." ); } $this->maybe_create_themes_dir(); $this->init_wp_filesystem(); $unzip_result = unzip_file( $tmpfname, $theme_path ); unlink( $tmpfname ); if ( true === $unzip_result ) { $this->create_files( array( "$theme_path/{$theme_slug}/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), ), false ); WP_CLI::success( "Created theme '{$data['theme_name']}'." ); } else { WP_CLI::error( "Could not decompress your theme files ('{$tmpfname}') at '{$theme_path}': {$unzip_result->get_error_message()}" ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) ); } else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) { WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( 'network' => true ) ); } } /** * Generate child theme based on an existing theme. * * Creates a child theme folder with `functions.php` and `style.css` files. * * ## OPTIONS * * <slug> * : The slug for the new child theme. * * --parent_theme=<slug> * : What to put in the 'Template:' header in 'style.css'. * * [--theme_name=<title>] * : What to put in the 'Theme Name:' header in 'style.css'. * * [--author=<full-name>] * : What to put in the 'Author:' header in 'style.css'. * * [--author_uri=<uri>] * : What to put in the 'Author URI:' header in 'style.css'. * * [--theme_uri=<uri>] * : What to put in the 'Theme URI:' header in 'style.css'. * * [--activate] * : Activate the newly created child theme. * * [--enable-network] * : Enable the newly created child theme for the entire network. * * [--force] * : Overwrite files that already exist. * * ## EXAMPLES * * # Generate a 'sample-theme' child theme based on TwentySixteen * $ wp scaffold child-theme sample-theme --parent_theme=twentysixteen * Success: Created '/var/www/example.com/public_html/wp-content/themes/sample-theme'. * * @subcommand child-theme */ function child_theme( $args, $assoc_args ) { $theme_slug = $args[0]; if ( in_array( $theme_slug, array( '.', '..' ) ) ) { WP_CLI::error( "Invalid theme slug specified. The slug cannot be '.' or '..'." ); } $data = wp_parse_args( $assoc_args, array( 'theme_name' => ucfirst( $theme_slug ), 'author' => "Me", 'author_uri' => "", 'theme_uri' => "", ) ); $data['slug'] = $theme_slug; $data['parent_theme_function_safe'] = str_replace( array( ' ', '-' ), '_', $data['parent_theme'] ); $data['description'] = ucfirst( $data['parent_theme'] ) . " child theme."; $theme_dir = WP_CONTENT_DIR . "/themes" . "/$theme_slug"; if ( $error_msg = $this->check_target_directory( "theme", $theme_dir ) ) { WP_CLI::error( "Invalid theme slug specified. {$error_msg}" ); } $theme_style_path = "$theme_dir/style.css"; $theme_functions_path = "$theme_dir/functions.php"; $this->maybe_create_themes_dir(); $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $files_written = $this->create_files( array( $theme_style_path => self::mustache_render( 'child_theme.mustache', $data ), $theme_functions_path => self::mustache_render( 'child_theme_functions.mustache', $data ), "$theme_dir/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), ), $force ); $this->log_whether_files_written( $files_written, $skip_message = 'All theme files were skipped.', $success_message = "Created '$theme_dir'." ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) ); } else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) { WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( 'network' => true ) ); } } private function get_output_path( $assoc_args, $subdir ) { if ( $assoc_args['theme'] ) { $theme = $assoc_args['theme']; if ( is_string( $theme ) ) { $path = get_theme_root( $theme ) . '/' . $theme; } else { $path = get_stylesheet_directory(); } } elseif ( $assoc_args['plugin'] ) { $plugin = $assoc_args['plugin']; $path = WP_PLUGIN_DIR . '/' . $plugin; if ( ! is_dir( $path ) ) { WP_CLI::error( "Can't find '$plugin' plugin." ); } } else { return false; } $path .= $subdir; return $path; } /** * Generate starter code for a plugin. * * The following files are always generated: * * * `plugin-slug.php` is the main PHP plugin file. * * `readme.txt` is the readme file for the plugin. * * `package.json` needed by NPM holds various metadata relevant to the project. Packages: `grunt`, `grunt-wp-i18n` and `grunt-wp-readme-to-markdown`. * * `Gruntfile.js` is the JS file containing Grunt tasks. Tasks: `i18n` containing `addtextdomain` and `makepot`, `readme` containing `wp_readme_to_markdown`. * * `.editorconfig` is the configuration file for Editor. * * `.gitignore` tells which files (or patterns) git should ignore. * * `.distignore` tells which files and folders should be ignored in distribution. * * The following files are also included unless the `--skip-tests` is used: * * * `phpunit.xml.dist` is the configuration file for PHPUnit. * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite. * * `tests/test-sample.php` is a sample file containing test cases. * * `phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. * * ## OPTIONS * * <slug> * : The internal name of the plugin. * * [--dir=<dirname>] * : Put the new plugin in some arbitrary directory path. Plugin directory will be path plus supplied slug. * * [--plugin_name=<title>] * : What to put in the 'Plugin Name:' header. * * [--plugin_description=<description>] * : What to put in the 'Description:' header. * * [--plugin_author=<author>] * : What to put in the 'Author:' header. * * [--plugin_author_uri=<url>] * : What to put in the 'Author URI:' header. * * [--plugin_uri=<url>] * : What to put in the 'Plugin URI:' header. * * [--skip-tests] * : Don't generate files for unit testing. * * [--ci=<provider>] * : Choose a configuration file for a continuous integration provider. * --- * default: travis * options: * - travis * - circle * - gitlab * --- * * [--activate] * : Activate the newly generated plugin. * * [--activate-network] * : Network activate the newly generated plugin. * * [--force] * : Overwrite files that already exist. * * ## EXAMPLES * * $ wp scaffold plugin sample-plugin * Success: Created plugin files. * Success: Created test files. */ function plugin( $args, $assoc_args ) { $plugin_slug = $args[0]; $plugin_name = ucwords( str_replace( '-', ' ', $plugin_slug ) ); $plugin_package = str_replace( ' ', '_', $plugin_name ); if ( in_array( $plugin_slug, array( '.', '..' ) ) ) { WP_CLI::error( "Invalid plugin slug specified. The slug cannot be '.' or '..'." ); } $data = wp_parse_args( $assoc_args, array( 'plugin_slug' => $plugin_slug, 'plugin_name' => $plugin_name, 'plugin_package' => $plugin_package, 'plugin_description' => 'PLUGIN DESCRIPTION HERE', 'plugin_author' => 'YOUR NAME HERE', 'plugin_author_uri' => 'YOUR SITE HERE', 'plugin_uri' => 'PLUGIN SITE HERE', 'plugin_tested_up_to' => get_bloginfo('version'), ) ); $data['textdomain'] = $plugin_slug; if ( ! empty( $assoc_args['dir'] ) ) { if ( ! is_dir( $assoc_args['dir'] ) ) { WP_CLI::error( "Cannot create plugin in directory that doesn't exist." ); } $plugin_dir = $assoc_args['dir'] . "/$plugin_slug"; } else { $plugin_dir = WP_PLUGIN_DIR . "/$plugin_slug"; $this->maybe_create_plugins_dir(); if ( $error_msg = $this->check_target_directory( "plugin", $plugin_dir ) ) { WP_CLI::error( "Invalid plugin slug specified. {$error_msg}" ); } } $plugin_path = "$plugin_dir/$plugin_slug.php"; $plugin_readme_path = "$plugin_dir/readme.txt"; $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $files_written = $this->create_files( array( $plugin_path => self::mustache_render( 'plugin.mustache', $data ), $plugin_readme_path => self::mustache_render( 'plugin-readme.mustache', $data ), "$plugin_dir/package.json" => self::mustache_render( 'plugin-packages.mustache', $data ), "$plugin_dir/Gruntfile.js" => self::mustache_render( 'plugin-gruntfile.mustache', $data ), "$plugin_dir/.gitignore" => self::mustache_render( 'plugin-gitignore.mustache', $data ), "$plugin_dir/.distignore" => self::mustache_render( 'plugin-distignore.mustache', $data ), "$plugin_dir/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), ), $force ); $this->log_whether_files_written( $files_written, $skip_message = 'All plugin files were skipped.', $success_message = 'Created plugin files.' ); if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-tests' ) ) { $ci = empty( $assoc_args['ci'] ) ? '' : $assoc_args['ci']; WP_CLI::run_command( array( 'scaffold', 'plugin-tests', $plugin_slug ), array( 'dir' => $plugin_dir, 'ci' => $ci, 'force' => $force ) ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug ) ); } else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate-network' ) ) { WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug), array( 'network' => true ) ); } } /** * Generate files needed for running PHPUnit tests in a plugin. * * The following files are generated by default: * * * `phpunit.xml.dist` is the configuration file for PHPUnit. * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite. * * `tests/test-sample.php` is a sample file containing the actual tests. * * `phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. * * Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/plugin-unit-tests/). * * ## ENVIRONMENT * * The `tests/bootstrap.php` file looks for the WP_TESTS_DIR environment * variable. * * ## OPTIONS * * [<plugin>] * : The name of the plugin to generate test files for. * * [--dir=<dirname>] * : Generate test files for a non-standard plugin path. If no plugin slug is specified, the directory name is used. * * [--ci=<provider>] * : Choose a configuration file for a continuous integration provider. * --- * default: travis * options: * - travis * - circle * - gitlab * --- * * [--force] * : Overwrite files that already exist. * * ## EXAMPLES * * # Generate unit test files for plugin 'sample-plugin'. * $ wp scaffold plugin-tests sample-plugin * Success: Created test files. * * @subcommand plugin-tests */ public function plugin_tests( $args, $assoc_args ) { $this->scaffold_plugin_theme_tests( $args, $assoc_args, 'plugin' ); } /** * Generate files needed for running PHPUnit tests in a theme. * * The following files are generated by default: * * * `phpunit.xml.dist` is the configuration file for PHPUnit. * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * * `tests/bootstrap.php` is the file that makes the current theme active when running the test suite. * * `tests/test-sample.php` is a sample file containing the actual tests. * * `phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. * * Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/plugin-unit-tests/). * * ## ENVIRONMENT * * The `tests/bootstrap.php` file looks for the WP_TESTS_DIR environment * variable. * * ## OPTIONS * * [<theme>] * : The name of the theme to generate test files for. * * [--dir=<dirname>] * : Generate test files for a non-standard theme path. If no theme slug is specified, the directory name is used. * * [--ci=<provider>] * : Choose a configuration file for a continuous integration provider. * --- * default: travis * options: * - travis * - circle * - gitlab * --- * * [--force] * : Overwrite files that already exist. * * ## EXAMPLES * * # Generate unit test files for theme 'twentysixteenchild'. * $ wp scaffold theme-tests twentysixteenchild * Success: Created test files. * * @subcommand theme-tests */ public function theme_tests( $args, $assoc_args ) { $this->scaffold_plugin_theme_tests( $args, $assoc_args, 'theme' ); } private function scaffold_plugin_theme_tests( $args, $assoc_args, $type ) { $wp_filesystem = $this->init_wp_filesystem(); if ( ! empty( $args[0] ) ) { $slug = $args[0]; if ( in_array( $slug, array( '.', '..' ) ) ) { WP_CLI::error( "Invalid {$type} slug specified. The slug cannot be '.' or '..'." ); } if ( 'theme' === $type ) { $theme = wp_get_theme( $slug ); if ( $theme->exists() ) { $target_dir = $theme->get_stylesheet_directory(); } else { WP_CLI::error( "Invalid {$type} slug specified. The theme '{$slug}' does not exist." ); } } else { $target_dir = WP_PLUGIN_DIR . "/$slug"; } if ( empty( $assoc_args['dir'] ) && ! is_dir( $target_dir ) ) { WP_CLI::error( "Invalid {$type} slug specified. No such target directory '{$target_dir}'." ); } if ( $error_msg = $this->check_target_directory( $type, $target_dir ) ) { WP_CLI::error( "Invalid {$type} slug specified. {$error_msg}" ); } } if ( ! empty( $assoc_args['dir'] ) ) { $target_dir = $assoc_args['dir']; if ( ! is_dir( $target_dir ) ) { WP_CLI::error( "Invalid {$type} directory specified. No such directory '{$target_dir}'." ); } if ( empty( $slug ) ) { $slug = Utils\basename( $target_dir ); } } if ( empty( $slug ) || empty( $target_dir ) ) { WP_CLI::error( "Invalid {$type} specified." ); } $name = ucwords( str_replace( '-', ' ', $slug ) ); $package = str_replace( ' ', '_', $name ); $tests_dir = "{$target_dir}/tests"; $bin_dir = "{$target_dir}/bin"; $wp_filesystem->mkdir( $tests_dir ); $wp_filesystem->mkdir( $bin_dir ); $wp_versions_to_test = array(); // Parse plugin readme.txt if ( file_exists( $target_dir . '/readme.txt' ) ) { $readme_content = file_get_contents( $target_dir . '/readme.txt' ); preg_match( '/Requires at least\:(.*)\n/m', $readme_content, $matches ); if ( isset( $matches[1] ) && $matches[1] ) { $wp_versions_to_test[] = trim( $matches[1] ); } } $wp_versions_to_test[] = 'latest'; $wp_versions_to_test[] = 'trunk'; $template_data = array( "{$type}_slug" => $slug, "{$type}_package" => $package, ); $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $files_to_create = array( "$tests_dir/bootstrap.php" => self::mustache_render( "{$type}-bootstrap.mustache", $template_data ), "$tests_dir/test-sample.php" => self::mustache_render( "{$type}-test-sample.mustache", $template_data ), ); if ( 'travis' === $assoc_args['ci'] ) { $files_to_create["{$target_dir}/.travis.yml"] = self::mustache_render( 'plugin-travis.mustache', compact( 'wp_versions_to_test' ) ); } else if ( 'circle' === $assoc_args['ci'] ) { $files_to_create["{$target_dir}/circle.yml"] = self::mustache_render( 'plugin-circle.mustache', compact( 'wp_versions_to_test' ) ); } else if ( 'gitlab' === $assoc_args['ci'] ) { $files_to_create["{$target_dir}/.gitlab-ci.yml"] = self::mustache_render( 'plugin-gitlab.mustache' ); } $files_written = $this->create_files( $files_to_create, $force ); $to_copy = array( 'install-wp-tests.sh' => $bin_dir, 'phpunit.xml.dist' => $target_dir, 'phpcs.xml.dist' => $target_dir, ); foreach ( $to_copy as $file => $dir ) { $file_name = "$dir/$file"; $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $should_write_file = $this->prompt_if_files_will_be_overwritten( $file_name, $force ); if ( ! $should_write_file ) { continue; } $files_written[] = $file_name; $wp_filesystem->copy( self::get_template_path( $file ), $file_name, true ); if ( 'install-wp-tests.sh' === $file ) { if ( ! $wp_filesystem->chmod( "$dir/$file", 0755 ) ) { WP_CLI::warning( "Couldn't mark 'install-wp-tests.sh' as executable." ); } } } $this->log_whether_files_written( $files_written, $skip_message = 'All test files were skipped.', $success_message = 'Created test files.' ); } /** * Checks that the `$target_dir` is a child directory of the WP themes or plugins directory, depending on `$type`. * * @param string $type "theme" or "plugin" * @param string $target_dir The theme/plugin directory to check. * * @return null|string Returns null on success, error message on error. */ private function check_target_directory( $type, $target_dir ) { $parent_dir = dirname( self::canonicalize_path( str_replace( '\\', '/', $target_dir ) ) ); if ( 'theme' === $type && str_replace( '\\', '/', WP_CONTENT_DIR . '/themes' ) !== $parent_dir ) { return sprintf( 'The target directory \'%1$s\' is not in \'%2$s\'.', $target_dir, WP_CONTENT_DIR . '/themes' ); } if ( 'plugin' === $type && str_replace( '\\', '/', WP_PLUGIN_DIR ) !== $parent_dir ) { return sprintf( 'The target directory \'%1$s\' is not in \'%2$s\'.', $target_dir, WP_PLUGIN_DIR ); } // Success. return null; } protected function create_files( $files_and_contents, $force ) { $wp_filesystem = $this->init_wp_filesystem(); $wrote_files = array(); foreach ( $files_and_contents as $filename => $contents ) { $should_write_file = $this->prompt_if_files_will_be_overwritten( $filename, $force ); if ( ! $should_write_file ) { continue; } $wp_filesystem->mkdir( dirname( $filename ) ); if ( ! $wp_filesystem->put_contents( $filename, $contents ) ) { WP_CLI::error( "Error creating file: $filename" ); } elseif ( $should_write_file ) { $wrote_files[] = $filename; } } return $wrote_files; } protected function prompt_if_files_will_be_overwritten( $filename, $force ) { $should_write_file = true; if ( ! file_exists( $filename ) ) { return true; } WP_CLI::warning( 'File already exists.' ); WP_CLI::log( $filename ); if ( ! $force ) { do { $answer = cli\prompt( 'Skip this file, or replace it with scaffolding?', $default = false, $marker = '[s/r]: ' ); } while ( ! in_array( $answer, array( 's', 'r' ) ) ); $should_write_file = 'r' === $answer; } $outcome = $should_write_file ? 'Replacing' : 'Skipping'; WP_CLI::log( $outcome . PHP_EOL ); return $should_write_file; } protected function log_whether_files_written( $files_written, $skip_message, $success_message ) { if ( empty( $files_written ) ) { WP_CLI::log( $skip_message ); } else { WP_CLI::success( $success_message ); } } /** * If you're writing your files to your theme directory your textdomain also needs to be the same as your theme. * Same goes for when plugin is being used. */ private function get_textdomain( $textdomain, $args ) { if ( strlen( $textdomain ) ) { return $textdomain; } if ( $args['theme'] ) { return strtolower( wp_get_theme()->template ); } if ( $args['plugin'] && true !== $args['plugin'] ) { return $args['plugin']; } return 'YOUR-TEXTDOMAIN'; } /** * Pluralize noun * * @see Inflector::pluralize() * @param string $word Word to be pluralized. * @return string */ private function pluralize( $word ) { return Inflector::pluralize( $word ); } protected function extract_args( $assoc_args, $defaults ) { $out = array(); foreach ( $defaults as $key => $value ) { $out[ $key ] = \WP_CLI\Utils\get_flag_value( $assoc_args, $key, $value ); } return $out; } protected function quote_comma_list_elements( $comma_list ) { return "'" . implode( "', '", explode( ',', $comma_list ) ) . "'"; } /** * Create the themes directory if it doesn't already exist */ protected function maybe_create_themes_dir() { $themes_dir = WP_CONTENT_DIR . '/themes'; if ( ! is_dir( $themes_dir ) ) { wp_mkdir_p( $themes_dir ); } } /** * Create the plugins directory if it doesn't already exist */ protected function maybe_create_plugins_dir() { if ( ! is_dir( WP_PLUGIN_DIR ) ) { wp_mkdir_p( WP_PLUGIN_DIR ); } } /** * Initialize WP Filesystem */ protected function init_wp_filesystem() { global $wp_filesystem; WP_Filesystem(); return $wp_filesystem; } /** * Localize path to template */ private static function mustache_render( $template, $data = array() ) { return Utils\mustache_render( dirname( dirname( __FILE__ ) ) . '/templates/' . $template, $data ); } /** * Get template path based on installation type */ private static function get_template_path( $template ) { $command_root = Utils\phar_safe_path( dirname( __DIR__ ) ); $template_path = "{$command_root}/templates/{$template}"; if ( ! file_exists( $template_path ) ) { WP_CLI::error( "Couldn't find {$template}" ); } return $template_path; } /* * Returns the canonicalized path, with dot and double dot segments resolved. * * Copied from Symfony\Component\DomCrawler\AbstractUriElement::canonicalizePath(). * Implements RFC 3986, section 5.2.4. * * @param string $path The path to make canonical. * * @return string The canonicalized path. */ private static function canonicalize_path( $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 ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'comment', 'Comment_Command' ); WP_CLI::add_command( 'comment meta', 'Comment_Meta_Command' ); WP_CLI::add_command( 'menu', 'Menu_Command' ); WP_CLI::add_command( 'menu item', 'Menu_Item_Command' ); WP_CLI::add_command( 'menu location', 'Menu_Location_Command' ); WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array( 'before_invoke' => function () { if ( !is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } } ) ); WP_CLI::add_command( 'option', 'Option_Command' ); WP_CLI::add_command( 'post', 'Post_Command' ); WP_CLI::add_command( 'post meta', 'Post_Meta_Command' ); WP_CLI::add_command( 'post term', 'Post_Term_Command' ); WP_CLI::add_command( 'post-type', 'Post_Type_Command' ); WP_CLI::add_command( 'site', 'Site_Command' ); WP_CLI::add_command( 'site option', 'Site_Option_Command', array( 'before_invoke' => function() { if ( !is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } } ) ); WP_CLI::add_command( 'taxonomy', 'Taxonomy_Command' ); WP_CLI::add_command( 'term', 'Term_Command' ); WP_CLI::add_command( 'term meta', 'Term_Meta_Command', array( 'before_invoke' => function() { if ( \WP_CLI\Utils\wp_version_compare( '4.4', '<' ) ) { WP_CLI::error( "Requires WordPress 4.4 or greater." ); } }) ); WP_CLI::add_command( 'user', 'User_Command' ); WP_CLI::add_command( 'user meta', 'User_Meta_Command' ); WP_CLI::add_command( 'user session', 'User_Session_Command', array( 'before_invoke' => function() { if ( \WP_CLI\Utils\wp_version_compare( '4.0', '<' ) ) { WP_CLI::error( "Requires WordPress 4.0 or greater." ); } }) ); WP_CLI::add_command( 'user term', 'User_Term_Command' ); <?php /** * Retrieves information about registered taxonomies. * * See references for [built-in taxonomies](https://developer.wordpress.org/themes/basics/categories-tags-custom-taxonomies/) and [custom taxonomies](https://developer.wordpress.org/plugins/taxonomies/working-with-custom-taxonomies/). * * ## EXAMPLES * * # List all taxonomies with 'post' object type. * $ wp taxonomy list --object_type=post --fields=name,public * +-------------+--------+ * | name | public | * +-------------+--------+ * | category | 1 | * | post_tag | 1 | * | post_format | 1 | * +-------------+--------+ * * # Get capabilities of 'post_tag' taxonomy. * $ wp taxonomy get post_tag --field=cap * {"manage_terms":"manage_categories","edit_terms":"manage_categories","delete_terms":"manage_categories","assign_terms":"edit_posts"} * * @package wp-cli */ class Taxonomy_Command extends WP_CLI_Command { private $fields = array( 'name', 'label', 'description', 'object_type', 'show_tagcloud', 'hierarchical', 'public', ); public function __construct() { if ( \WP_CLI\Utils\wp_version_compare( 3.7, '<' ) ) { // remove description for wp <= 3.7 $this->fields = array_values( array_diff( $this->fields, array( 'description' ) ) ); } parent::__construct(); } /** * List registered taxonomies. * * ## OPTIONS * * [--<field>=<value>] * : Filter by one or more fields (see get_taxonomies() first parameter for a list of available fields). * * [--field=<field>] * : Prints the value of a single field for each taxonomy. * * [--fields=<fields>] * : Limit the output to specific taxonomy fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each term: * * * name * * label * * description * * public * * hierarchical * * There are no optionally available fields. * * ## EXAMPLES * * # List all taxonomies. * $ wp taxonomy list --format=csv * name,label,description,object_type,show_tagcloud,hierarchical,public * category,Categories,,post,1,1,1 * post_tag,Tags,,post,1,,1 * nav_menu,"Navigation Menus",,nav_menu_item,,, * link_category,"Link Categories",,link,1,, * post_format,Format,,post,,,1 * * # List all taxonomies with 'post' object type. * $ wp taxonomy list --object_type=post --fields=name,public * +-------------+--------+ * | name | public | * +-------------+--------+ * | category | 1 | * | post_tag | 1 | * | post_format | 1 | * +-------------+--------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); if ( isset( $assoc_args['object_type'] ) ) { $assoc_args['object_type'] = array( $assoc_args['object_type'] ); } $taxonomies = get_taxonomies( $assoc_args, 'objects' ); $taxonomies = array_map( function( $taxonomy ) { $taxonomy->object_type = implode( ', ', $taxonomy->object_type ); return $taxonomy; }, $taxonomies ); $formatter->display_items( $taxonomies ); } /** * Get details about a registered taxonomy. * * ## OPTIONS * * <taxonomy> * : Taxonomy slug. * * [--field=<field>] * : Instead of returning the whole taxonomy, returns the value of a single field. * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Get details of `category` taxonomy. * $ wp taxonomy get category --fields=name,label,object_type * +-------------+------------+ * | Field | Value | * +-------------+------------+ * | name | category | * | label | Categories | * | object_type | ["post"] | * +-------------+------------+ * * # Get capabilities of 'post_tag' taxonomy. * $ wp taxonomy get post_tag --field=cap * {"manage_terms":"manage_categories","edit_terms":"manage_categories","delete_terms":"manage_categories","assign_terms":"edit_posts"} */ public function get( $args, $assoc_args ) { $taxonomy = get_taxonomy( $args[0] ); if ( ! $taxonomy ) { WP_CLI::error( "Taxonomy {$args[0]} doesn't exist." ); } if ( empty( $assoc_args['fields'] ) ) { $default_fields = array_merge( $this->fields, array( 'labels', 'cap' ) ); $assoc_args['fields'] = $default_fields; } $data = array( 'name' => $taxonomy->name, 'label' => $taxonomy->label, 'description' => $taxonomy->description, 'object_type' => $taxonomy->object_type, 'show_tagcloud' => $taxonomy->show_tagcloud, 'hierarchical' => $taxonomy->hierarchical, 'public' => $taxonomy->public, 'labels' => $taxonomy->labels, 'cap' => $taxonomy->cap, ); $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $data ); } private function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'taxonomy' ); } } <?php /** * Manage comment custom fields. * * ## EXAMPLES * * # Set comment meta * $ wp comment meta set 123 description "Mary is a WordPress developer." * Success: Updated custom field 'description'. * * # Get comment meta * $ wp comment meta get 123 description * Mary is a WordPress developer. * * # Update comment meta * $ wp comment meta update 123 description "Mary is an awesome WordPress developer." * Success: Updated custom field 'description'. * * # Delete comment meta * $ wp comment meta delete 123 description * Success: Deleted custom field. */ class Comment_Meta_Command extends \WP_CLI\CommandWithMeta { protected $meta_type = 'comment'; /** * Check that the comment ID exists * * @param int */ protected function check_object_id( $object_id ) { $fetcher = new \WP_CLI\Fetchers\Comment; $comment = $fetcher->get_check( $object_id ); return $comment->comment_ID; } } <?php namespace WP_CLI\Fetchers; /** * Fetch a WordPress user based on one of its attributes. */ class User extends Base { /** * @var string $msg Error message to use when invalid data is provided */ protected $msg = "Invalid user ID, email or login: '%s'"; /** * Get a user object by one of its identifying attributes * * @param mixed $id_email_or_login * @return WP_User|false */ public function get( $id_email_or_login ) { if ( is_numeric( $id_email_or_login ) ) { $user = get_user_by( 'id', $id_email_or_login ); } elseif ( is_email( $id_email_or_login ) ) { $user = get_user_by( 'email', $id_email_or_login ); // Logins can be emails if ( ! $user ) { $user = get_user_by( 'login', $id_email_or_login ); } } else { $user = get_user_by( 'login', $id_email_or_login ); } return $user; } } <?php namespace WP_CLI\Fetchers; /** * Fetch a WordPress post based on one of its attributes. */ class Post extends Base { /** * @var string $msg Error message to use when invalid data is provided */ protected $msg = "Could not find the post with ID %d."; /** * Get a post object by ID * * @param int $arg * @return WP_Post|false */ public function get( $arg ) { return get_post( $arg ); } } <?php namespace WP_CLI\Fetchers; /** * Fetch a WordPress comment based on one of its attributes. */ class Comment extends Base { /** * @var string $msg Error message to use when invalid data is provided */ protected $msg = "Could not find the comment with ID %d."; /** * Get a comment object by ID * * @param int $arg * @return object|false */ public function get( $arg ) { $comment_id = (int) $arg; $comment = get_comment( $comment_id ); if ( is_null( $comment ) ) { return false; } return $comment; } } <?php namespace WP_CLI\Fetchers; /** * Fetch a WordPress site based on one of its attributes. */ class Site extends Base { /** * @var string $msg Error message to use when invalid data is provided */ protected $msg = "Could not find the site with ID %d."; /** * Get a site object by ID * * @param int $site_id * @return object|false */ public function get( $site_id ) { return $this->_get_site( $site_id ); } /** * Get site (blog) data for a given id. * * @param int $site_id * @return bool|array False if no site found with given id, array otherwise */ private function _get_site( $site_id ) { global $wpdb; // Load site data $site = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->blogs WHERE blog_id = %d", $site_id ) ); if ( !empty( $site ) ) { // Only care about domain and path which are set here return $site; } return false; } } <?php namespace WP_CLI; use WP_CLI; use WP_CLI\Entity\RecursiveDataStructureTraverser; /** * Base class for WP-CLI commands that deal with metadata * * @package wp-cli */ abstract class CommandWithMeta extends \WP_CLI_Command { protected $meta_type; /** * List all metadata associated with an object. * * ## OPTIONS * * <id> * : ID for the object. * * [--keys=<keys>] * : Limit output to metadata of specific keys. * * [--fields=<fields>] * : Limit the output to specific row fields. Defaults to id,meta_key,meta_value. * * [--format=<format>] * : Accepted values: table, csv, json, count. Default: table * * [--orderby=<fields>] * : Set orderby which field. * --- * default: id * options: * - id * - meta_key * - meta_value * --- * * [--order=<order>] * : Set ascending or descending order. * --- * default: asc * options: * - asc * - desc * --- * @subcommand list */ public function list_( $args, $assoc_args ) { list( $object_id ) = $args; $keys = ! empty( $assoc_args['keys'] ) ? explode( ',', $assoc_args['keys'] ) : array(); $object_id = $this->check_object_id( $object_id ); $metadata = get_metadata( $this->meta_type, $object_id ); if ( ! $metadata ) { $metadata = array(); } $items = array(); foreach( $metadata as $key => $values ) { // Skip if not requested if ( ! empty( $keys ) && ! in_array( $key, $keys ) ) { continue; } foreach( $values as $item_value ) { $item_value = maybe_unserialize( $item_value ); $items[] = (object) array( "{$this->meta_type}_id" => $object_id, 'meta_key' => $key, 'meta_value' => $item_value, ); } } $order = Utils\get_flag_value( $assoc_args, 'order' ); $orderby = Utils\get_flag_value( $assoc_args, 'orderby' ); if ( 'id' !== $orderby ) { usort( $items, function ( $a, $b ) use ( $orderby, $order ) { // Sort array. return 'asc' === $order ? $a->$orderby > $b->$orderby : $a->$orderby < $b->$orderby; }); } elseif ( 'id' === $orderby && 'desc' === $order ) { // Sort by default descending. krsort( $items ); } if ( ! empty( $assoc_args['fields'] ) ) { $fields = explode( ',', $assoc_args['fields'] ); } else { $fields = $this->get_fields(); } $formatter = new \WP_CLI\Formatter( $assoc_args, $fields, $this->meta_type ); $formatter->display_items( $items ); } /** * Get meta field value. * * ## OPTIONS * * <id> * : The ID of the object. * * <key> * : The name of the meta field to get. * * [--format=<format>] * : Accepted values: table, json. Default: table */ public function get( $args, $assoc_args ) { list( $object_id, $meta_key ) = $args; $object_id = $this->check_object_id( $object_id ); $value = get_metadata( $this->meta_type, $object_id, $meta_key, true ); if ( '' === $value ) die(1); WP_CLI::print_value( $value, $assoc_args ); } /** * Delete a meta field. * * ## OPTIONS * * <id> * : The ID of the object. * * [<key>] * : The name of the meta field to delete. * * [<value>] * : The value to delete. If omitted, all rows with key will deleted. * * [--all] * : Delete all meta for the object. */ public function delete( $args, $assoc_args ) { list( $object_id ) = $args; $meta_key = ! empty( $args[1] ) ? $args[1] : ''; $meta_value = ! empty( $args[2] ) ? $args[2] : ''; if ( empty( $meta_key ) && ! Utils\get_flag_value( $assoc_args, 'all' ) ) { WP_CLI::error( 'Please specify a meta key, or use the --all flag.' ); } $object_id = $this->check_object_id( $object_id ); if ( Utils\get_flag_value( $assoc_args, 'all' ) ) { $errors = false; foreach( get_metadata( $this->meta_type, $object_id ) as $meta_key => $values ) { $success = delete_metadata( $this->meta_type, $object_id, $meta_key ); if ( $success ) { WP_CLI::log( "Deleted '{$meta_key}' custom field." ); } else { WP_CLI::warning( "Failed to delete '{$meta_key}' custom field." ); $errors = true; } } if ( $errors ) { WP_CLI::error( 'Failed to delete all custom fields.' ); } else { WP_CLI::success( 'Deleted all custom fields.' ); } } else { $success = delete_metadata( $this->meta_type, $object_id, $meta_key, $meta_value ); if ( $success ) { WP_CLI::success( "Deleted custom field." ); } else { WP_CLI::error( "Failed to delete custom field." ); } } } /** * Add a meta field. * * ## OPTIONS * * <id> * : The ID of the object. * * <key> * : The name of the meta field to create. * * [<value>] * : The value of the meta field. If omitted, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- */ public function add( $args, $assoc_args ) { list( $object_id, $meta_key ) = $args; $meta_value = WP_CLI::get_value_from_arg_or_stdin( $args, 2 ); $meta_value = WP_CLI::read_value( $meta_value, $assoc_args ); $object_id = $this->check_object_id( $object_id ); $meta_value = wp_slash( $meta_value ); $success = add_metadata( $this->meta_type, $object_id, $meta_key, $meta_value ); if ( $success ) { WP_CLI::success( "Added custom field." ); } else { WP_CLI::error( "Failed to add custom field." ); } } /** * Update a meta field. * * ## OPTIONS * * <id> * : The ID of the object. * * <key> * : The name of the meta field to update. * * [<value>] * : The new value. If omitted, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- * * @alias set */ public function update( $args, $assoc_args ) { list( $object_id, $meta_key ) = $args; $meta_value = WP_CLI::get_value_from_arg_or_stdin( $args, 2 ); $meta_value = WP_CLI::read_value( $meta_value, $assoc_args ); $object_id = $this->check_object_id( $object_id ); $meta_value = sanitize_meta( $meta_key, $meta_value, $this->meta_type ); $old_value = sanitize_meta( $meta_key, get_metadata( $this->meta_type, $object_id, $meta_key, true ), $this->meta_type ); if ( $meta_value === $old_value ) { WP_CLI::success( "Value passed for custom field '$meta_key' is unchanged." ); } else { $meta_value = wp_slash( $meta_value ); $success = update_metadata( $this->meta_type, $object_id, $meta_key, $meta_value ); if ( $success ) { WP_CLI::success( "Updated custom field '$meta_key'." ); } else { WP_CLI::error( "Failed to update custom field '$meta_key'." ); } } } /** * Get a nested value from a meta field. * * ## OPTIONS * * <id> * : The ID of the object. * * <key> * : The name of the meta field to get. * * <key-path>... * : The name(s) of the keys within the value to locate the value to pluck. * * [--format=<format>] * : The output format of the value. * --- * default: plaintext * options: * - plaintext * - json * - yaml */ public function pluck( $args, $assoc_args ) { list( $object_id, $meta_key ) = $args; $object_id = $this->check_object_id( $object_id ); $key_path = array_map( function( $key ) { if ( is_numeric( $key ) && ( $key === (string) intval( $key ) ) ) { return (int) $key; } return $key; }, array_slice( $args, 2 ) ); $value = get_metadata( $this->meta_type, $object_id, $meta_key, true ); $traverser = new RecursiveDataStructureTraverser( $value ); try { $value = $traverser->get( $key_path ); } catch ( \Exception $e ) { die( 1 ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Update a nested value for a meta field. * * ## OPTIONS * * <action> * : Patch action to perform. * --- * options: * - insert * - update * - delete * --- * * <id> * : The ID of the object. * * <key> * : The name of the meta field to update. * * <key-path>... * : The name(s) of the keys within the value to locate the value to patch. * * [<value>] * : The new value. If omitted, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- */ public function patch( $args, $assoc_args ) { list( $action, $object_id, $meta_key ) = $args; $object_id = $this->check_object_id( $object_id ); $key_path = array_map( function( $key ) { if ( is_numeric( $key ) && ( $key === (string) intval( $key ) ) ) { return (int) $key; } return $key; }, array_slice( $args, 3 ) ); if ( 'delete' == $action ) { $patch_value = null; } elseif ( Entity\Utils::has_stdin() ) { $stdin_value = WP_CLI::get_value_from_arg_or_stdin( $args, -1 ); $patch_value = WP_CLI::read_value( trim( $stdin_value ), $assoc_args ); } else { // Take the patch value as the last positional argument. Mutates $key_path to be 1 element shorter! $patch_value = WP_CLI::read_value( array_pop( $key_path ), $assoc_args ); } /* Need to make a copy of $current_meta_value here as it is modified by reference */ $current_meta_value = $old_meta_value = sanitize_meta( $meta_key, get_metadata( $this->meta_type, $object_id, $meta_key, true ), $this->meta_type ); $traverser = new RecursiveDataStructureTraverser( $current_meta_value ); try { $traverser->$action( $key_path, $patch_value ); } catch ( \Exception $e ) { WP_CLI::error( $e->getMessage() ); } $patched_meta_value = sanitize_meta( $meta_key, $traverser->value(), $this->meta_type ); if ( $patched_meta_value === $old_meta_value ) { WP_CLI::success( "Value passed for custom field '$meta_key' is unchanged." ); } else { $slashed = wp_slash( $patched_meta_value ); $success = update_metadata( $this->meta_type, $object_id, $meta_key, $slashed ); if ( $success ) { WP_CLI::success( "Updated custom field '$meta_key'." ); } else { WP_CLI::error( "Failed to update custom field '$meta_key'." ); } } } /** * Get the fields for this object's meta * * @return array */ private function get_fields() { return array( "{$this->meta_type}_id", 'meta_key', 'meta_value', ); } /** * Check that the object ID exists * * @param int */ protected function check_object_id( $object_id ) { // Needs to be set in subclass return $object_id; } } <?php namespace WP_CLI; /** * Base class for WP-CLI commands that deal with database objects. * * @package wp-cli */ abstract class CommandWithDBObject extends \WP_CLI_Command { /** * @var string $object_type WordPress' expected name for the object. */ protected $obj_type; /** * @var string $obj_id_key Key representing object's PK field in db. */ protected $obj_id_key = 'ID'; /** * @var array $obj_fields Default fields to display for each object. */ protected $obj_fields = null; /** * Create a given database object. * Exits with status. * * @param array $args Arguments passed to command. Generally unused. * @param array $assoc_args Parameters passed to command to be passed to callback. * @param string $callback Function used to create object. */ protected function _create( $args, $assoc_args, $callback ) { unset( $assoc_args[ $this->obj_id_key ] ); $obj_id = $callback( $assoc_args ); if ( is_wp_error( $obj_id ) ) { \WP_CLI::error( $obj_id ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) \WP_CLI::line( $obj_id ); else \WP_CLI::success( "Created $this->obj_type $obj_id." ); } /** * Update a given database object. * Exits with status. * * @param array $args Collection of one or more object ids to update. * @param array $assoc_args Fields => values to update on each object. * @param string $callback Function used to update object. */ protected function _update( $args, $assoc_args, $callback ) { $status = 0; if ( empty( $assoc_args ) ) { \WP_CLI::error( "Need some fields to update." ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'defer-term-counting' ) ) { wp_defer_term_counting( true ); } foreach ( $args as $obj_id ) { $params = array_merge( $assoc_args, array( $this->obj_id_key => $obj_id ) ); $status = $this->success_or_failure( $this->wp_error_to_resp( $callback( $params ), "Updated $this->obj_type $obj_id." ) ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'defer-term-counting' ) ) { wp_defer_term_counting( false ); } exit( $status ); } /** * Transforms arguments with '__' from CSV into expected arrays * * @param array $assoc_args * @return array */ protected static function process_csv_arguments_to_arrays( $assoc_args ) { foreach( $assoc_args as $k => $v ) { if ( false !== strpos( $k, '__' ) ) { $assoc_args[ $k ] = explode( ',', $v ); } } return $assoc_args; } /** * Delete a given database object. * Exits with status. * * @param array $args Collection of one or more object ids to delete. * @param array $assoc_args Any arguments needed for the callback function. * @param string $callback Function used to delete object. */ protected function _delete( $args, $assoc_args, $callback ) { $status = 0; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'defer-term-counting' ) ) { wp_defer_term_counting( true ); } foreach ( $args as $obj_id ) { $r = $callback( $obj_id, $assoc_args ); $status = $this->success_or_failure( $r ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'defer-term-counting' ) ) { wp_defer_term_counting( false ); } exit( $status ); } /** * Format callback response to consistent format. * * @param WP_Error|true $r Response from CRUD callback. * @param string $success_msg * @return array */ protected function wp_error_to_resp( $r, $success_msg ) { if ( is_wp_error( $r ) ) return array( 'error', $r->get_error_message() ); else return array( 'success', $success_msg ); } /** * Display success or warning based on response; return proper exit code. * * @param array $r Formatted from a CRUD callback. * @return int $status */ protected function success_or_failure( $r ) { list( $type, $msg ) = $r; if ( 'success' == $type ) { \WP_CLI::success( $msg ); $status = 0; } else { \WP_CLI::warning( $msg ); $status = 1; } return $status; } /** * Get Formatter object based on supplied parameters. * * @param array $assoc_args Parameters passed to command. Determines formatting. * @return \WP_CLI\Formatter */ protected function get_formatter( &$assoc_args ) { if ( ! empty( $assoc_args['fields'] ) ) { if ( is_string( $assoc_args['fields'] ) ) { $fields = explode( ',', $assoc_args['fields'] ); } else { $fields = $assoc_args['fields']; } } else { $fields = $this->obj_fields; } return new \WP_CLI\Formatter( $assoc_args, $fields, $this->obj_type ); } /** * Given a callback, display the URL for one or more objects. * * @param array $args One or more object references. * @param string $callback Function to get URL for the object. */ protected function _url( $args, $callback ) { foreach ( $args as $obj_id ) { $object = $this->fetcher->get_check( $obj_id ); \WP_CLI::line( $callback( $object->{$this->obj_id_key} ) ); } } } <?php namespace WP_CLI; use WP_CLI; use WP_CLI\Utils; /** * Base class for WP-CLI commands that deal with terms * * @package wp-cli */ abstract class CommandWithTerms extends \WP_CLI_Command { /** * @var string $object_type WordPress' expected name for the object. */ protected $obj_type; /** * @var string $object_id WordPress' object id. */ protected $obj_id; /** * @var array $obj_fields Default fields to display for each object. */ protected $obj_fields = array( "term_id", "name", "slug", "taxonomy" ); /** * List all terms associated with an object. * * <id> * : ID for the object. * * <taxonomy>... * : One or more taxonomies to list. * * [--field=<field>] * : Prints the value of a single field for each term. * * [--fields=<fields>] * : Limit the output to specific row fields. * * [--format=<format>] * : Accepted values: table, csv, json, count, ids. Default: table * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each term: * * * term_id * * name * * slug * * taxonomy * * These fields are optionally available: * * * term_taxonomy_id * * description * * term_group * * parent * * count * * @subcommand list */ public function list_( $args, $assoc_args ) { $defaults = array( 'format' => 'table', ); $assoc_args = array_merge( $defaults, $assoc_args ); $object_id = array_shift( $args ); $taxonomy_names = $args; $taxonomy_args = array(); $this->set_obj_id( $object_id ); foreach ( $taxonomy_names as $taxonomy ) { $this->taxonomy_exists( $taxonomy ); } if ( $assoc_args['format'] == 'ids' ) { $taxonomy_args['fields'] = 'ids'; } $items = wp_get_object_terms( $object_id, $taxonomy_names, $taxonomy_args ); $formatter = $this->get_formatter( $assoc_args ); $formatter->display_items( $items ); } /** * Remove a term from an object. * * ## OPTIONS * * <id> * : The ID of the object. * * <taxonomy> * : The name of the term's taxonomy. * * [<term>...] * : The name of the term or terms to be removed from the object. * * [--by=<field>] * : Explicitly handle the term value as a slug or id. * --- * options: * - slug * - id * --- * * [--all] * : Remove all terms from the object. */ public function remove( $args, $assoc_args ) { $object_id = array_shift( $args ); $taxonomy = array_shift( $args ); $terms = $args; $this->set_obj_id( $object_id ); $this->taxonomy_exists( $taxonomy ); if ( $field = Utils\get_flag_value( $assoc_args, 'by' ) ) { $terms = $this->prepare_terms( $field, $terms, $taxonomy ); } if ( Utils\get_flag_value( $assoc_args, 'all' ) ) { // No need to specify terms while removing all terms. if ( $terms ) { WP_CLI::error( 'No need to specify terms while removing all terms.' ); } // Remove all set categories from post. $result = wp_delete_object_term_relationships( $object_id, $taxonomy ); $message = 'Removed all terms.'; if ( 'category' === $taxonomy ) { // Set default category to post. $default_category = (int) get_option( 'default_category' ); $default_category = ( ! empty( $default_category ) ) ? $default_category : 1; $default_category = wp_set_object_terms( $object_id, array( $default_category ), $taxonomy, true ); if ( ! is_wp_error( $default_category ) ) { $message = 'Removed all terms and set default term.'; } else { WP_CLI::error( 'Failed to set default term.' ); } } if ( ! is_wp_error( $result ) ) { WP_CLI::success( $message ); } else { WP_CLI::error( 'Failed to remove all terms.' ); } return; } else { // Abort if no terms are specified. if ( ! $terms ) { WP_CLI::error( 'Please specify one or more terms, or use --all.' ); } // Remove term from post. $result = wp_remove_object_terms( $object_id, $terms, $taxonomy ); } $label = count( $terms ) > 1 ? 'terms' : 'term'; if ( ! is_wp_error( $result ) ) { WP_CLI::success( "Removed {$label}." ); } else { WP_CLI::error( "Failed to remove {$label}." ); } } /** * Add a term to an object. * * Append the term to the existing set of terms on the object. * * <id> * : The ID of the object. * * <taxonomy> * : The name of the taxonomy type to be added. * * <term>... * : The slug of the term or terms to be added. * * [--by=<field>] * : Explicitly handle the term value as a slug or id. * --- * options: * - slug * - id * --- */ public function add( $args, $assoc_args ) { $object_id = array_shift( $args ); $taxonomy = array_shift( $args ); $terms = $args; $this->set_obj_id( $object_id ); $this->taxonomy_exists( $taxonomy ); if ( $field = Utils\get_flag_value( $assoc_args, 'by' ) ) { $terms = $this->prepare_terms( $field, $terms, $taxonomy ); } $result = wp_set_object_terms( $object_id, $terms, $taxonomy, true ); $label = count( $terms ) > 1 ? 'terms' : 'term'; if ( ! is_wp_error( $result ) ) { WP_CLI::success( "Added {$label}." ); } else { WP_CLI::error( "Failed to add {$label}." ); } } /** * Set object terms. * * Replaces existing terms on the object. * * <id> * : The ID of the object. * * <taxonomy> * : The name of the taxonomy type to be updated. * * <term>... * : The slug of the term or terms to be updated. * * [--by=<field>] * : Explicitly handle the term value as a slug or id. * --- * options: * - slug * - id * --- */ public function set( $args, $assoc_args ) { $object_id = array_shift( $args ); $taxonomy = array_shift( $args ); $terms = $args; $this->set_obj_id( $object_id ); $this->taxonomy_exists( $taxonomy ); if ( $field = Utils\get_flag_value( $assoc_args, 'by' ) ) { $terms = $this->prepare_terms( $field, $terms, $taxonomy ); } $result = wp_set_object_terms( $object_id, $terms, $taxonomy, false ); $label = count( $terms ) > 1 ? 'terms' : 'term'; if ( ! is_wp_error( $result ) ) { WP_CLI::success( "Set {$label}." ); } else { WP_CLI::error( "Failed to set {$label}." ); } } /** * Check if taxonomy exists * * @param $taxonomy */ protected function taxonomy_exists( $taxonomy ) { $taxonomy_names = get_object_taxonomies( $this->get_object_type() ); if ( ! in_array( $taxonomy, $taxonomy_names ) ) { WP_CLI::error( "Invalid taxonomy {$taxonomy}." ); } } /** * Prepare terms if `--by=<field>` flag is used * * @param array $terms * @param string $field * @param string $taxonomy */ protected function prepare_terms( $field, $terms, $taxonomy ) { if ( 'id' === $field ) { $new_terms = array(); foreach( $terms as $i => $term_id ) { $term = get_term_by( 'term_id', $term_id, $taxonomy ); if ( $term ) { $new_terms[] = $term->slug; } } $terms = $new_terms; } return $terms; } /** * Set obj_id Class variable * * @param string $obj_id */ protected function set_obj_id( $obj_id ) { $this->obj_id = $obj_id; } /** * Get obj_id Class variable * * @return string */ protected function get_obj_id() { return $this->obj_id; } /** * Get obj_type Class variable * * @return string $obj_type */ protected function get_object_type() { return $this->obj_type; } /** * Get Formatter object based on supplied parameters. * * @param array $assoc_args Parameters passed to command. Determines formatting. * * @return WP_CLI\Formatter */ protected function get_formatter( &$assoc_args ) { return new WP_CLI\Formatter( $assoc_args, $this->obj_fields, $this->obj_type ); } } <?php namespace WP_CLI\Entity; class NonExistentKeyException extends \OutOfBoundsException { /* @var \WP_CLI\Entity\RecursiveDataStructureTraverser */ protected $traverser; /** * @param \WP_CLI\Entity\RecursiveDataStructureTraverser $traverser */ public function set_traverser( $traverser ) { $this->traverser = $traverser; } /** * @return \WP_CLI\Entity\RecursiveDataStructureTraverser */ public function get_traverser() { return $this->traverser; } } <?php namespace WP_CLI\Entity; class Utils { /** * Check whether any input is passed to STDIN. * * @return bool */ public static function has_stdin() { $handle = fopen( 'php://stdin', 'r' ); $read = array( $handle ); $write = null; $except = null; $streams = stream_select( $read, $write, $except, 0 ); fclose( $handle ); return 1 === $streams; } } <?php namespace WP_CLI\Entity; class RecursiveDataStructureTraverser { /** * @var mixed The data to traverse set by reference. */ protected $data; /** * @var null|string The key the data belongs to in the parent's data. */ protected $key; /** * @var null|static The parent instance of the traverser. */ protected $parent; /** * RecursiveDataStructureTraverser constructor. * * @param mixed $data The data to read/manipulate by reference. * @param string|int $key The key/property the data belongs to. * @param static $parent */ public function __construct( &$data, $key = null, $parent = null ) { $this->data =& $data; $this->key = $key; $this->parent = $parent; } /** * Get the nested value at the given key path. * * @param string|int|array $key_path * * @return static */ public function get( $key_path ) { return $this->traverse_to( (array) $key_path )->value(); } /** * Get the current data. * * @return mixed */ public function value() { return $this->data; } /** * Update a nested value at the given key path. * * @param string|int|array $key_path * @param mixed $value */ public function update( $key_path, $value ) { $this->traverse_to( (array) $key_path )->set_value( $value ); } /** * Update the current data with the given value. * * This will mutate the variable which was passed into the constructor * as the data is set and traversed by reference. * * @param mixed $value */ public function set_value( $value ) { $this->data = $value; } /** * Unset the value at the given key path. * * @param $key_path */ public function delete( $key_path ) { $this->traverse_to( (array) $key_path )->unset_on_parent(); } /** * Define a nested value while creating keys if they do not exist. * * @param array $key_path * @param mixed $value */ public function insert( $key_path, $value ) { try { $this->update( $key_path, $value ); } catch ( NonExistentKeyException $e ) { $e->get_traverser()->create_key(); $this->insert( $key_path, $value ); } } /** * Delete the key on the parent's data that references this data. */ public function unset_on_parent() { $this->parent->delete_by_key( $this->key ); } /** * Delete the given key from the data. * * @param $key */ public function delete_by_key( $key ) { if ( is_array( $this->data ) ) { unset( $this->data[ $key ] ); } else { unset( $this->data->$key ); } } /** * Get an instance of the traverser for the given hierarchical key. * * @param array $key_path Hierarchical key path within the current data to traverse to. * * @throws NonExistentKeyException * * @return static */ public function traverse_to( array $key_path ) { $current = array_shift( $key_path ); if ( null === $current ) { return $this; } if ( ! $this->exists( $current ) ) { $exception = new NonExistentKeyException( "No data exists for key \"$current\"" ); $exception->set_traverser( new static( $this->data, $current, $this->parent ) ); throw $exception; } foreach ( $this->data as $key => &$key_data ) { if ( $key === $current ) { $traverser = new static( $key_data, $key, $this ); return $traverser->traverse_to( $key_path ); } } } /** * Create the key on the current data. * * @throws \UnexpectedValueException */ protected function create_key() { if ( is_array( $this->data ) ) { $this->data[ $this->key ] = null; } elseif ( is_object( $this->data ) ) { $this->data->{$this->key} = null; } else { throw new \UnexpectedValueException( sprintf( 'Cannot create key "%s" on data type %s', $this->key, gettype( $this->data ) ) ); } } /** * Check if the given key exists on the current data. * * @param string $key * * @return bool */ public function exists( $key ) { return ( is_array( $this->data ) && array_key_exists( $key, $this->data ) ) || ( is_object( $this->data ) && property_exists( $this->data, $key ) ); } } <?php use WP_CLI\Entity\RecursiveDataStructureTraverser; use WP_CLI\Utils; /** * Retrieves and sets site options, including plugin and WordPress settings. * * See the [Plugin Settings API](https://developer.wordpress.org/plugins/settings/settings-api/) and the [Theme Options](https://developer.wordpress.org/themes/customize-api/) for more information on adding customized options. * * ## EXAMPLES * * # Get site URL. * $ wp option get siteurl * http://example.com * * # Add option. * $ wp option add my_option foobar * Success: Added 'my_option' option. * * # Update option. * $ wp option update my_option '{"foo": "bar"}' --format=json * Success: Updated 'my_option' option. * * # Delete option. * $ wp option delete my_option * Success: Deleted 'my_option' option. * * @package wp-cli */ class Option_Command extends WP_CLI_Command { /** * Get the value for an option. * * ## OPTIONS * * <key> * : Key for the option. * * [--format=<format>] * : Get value in a particular format. * --- * default: var_export * options: * - var_export * - json * - yaml * --- * * ## EXAMPLES * * # Get option. * $ wp option get home * http://example.com * * # Get blog description. * $ wp option get blogdescription * A random blog description * * # Get blog name * $ wp option get blogname * A random blog name * * # Get admin email. * $ wp option get admin_email * someone@example.com * * # Get option in JSON format. * $ wp option get active_plugins --format=json * {"0":"dynamically-dynamic-sidebar\/dynamically-dynamic-sidebar.php","1":"monster-widget\/monster-widget.php","2":"show-current-template\/show-current-template.php","3":"theme-check\/theme-check.php","5":"wordpress-importer\/wordpress-importer.php"} */ public function get( $args, $assoc_args ) { list( $key ) = $args; $value = get_option( $key ); if ( false === $value ) { WP_CLI::halt( 1 ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Add a new option value. * * Errors if the option already exists. * * ## OPTIONS * * <key> * : The name of the option to add. * * [<value>] * : The value of the option to add. If ommited, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- * * [--autoload=<autoload>] * : Should this option be automatically loaded. * --- * options: * - 'yes' * - 'no' * --- * * ## EXAMPLES * * # Create an option by reading a JSON file. * $ wp option add my_option --format=json < config.json * Success: Added 'my_option' option. */ public function add( $args, $assoc_args ) { $key = $args[0]; $value = WP_CLI::get_value_from_arg_or_stdin( $args, 1 ); $value = WP_CLI::read_value( $value, $assoc_args ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'autoload' ) === 'no' ) { $autoload = 'no'; } else { $autoload = 'yes'; } if ( !add_option( $key, $value, '', $autoload ) ) { WP_CLI::error( "Could not add option '$key'. Does it already exist?" ); } else { WP_CLI::success( "Added '$key' option." ); } } /** * List options and their values. * * ## OPTIONS * * [--search=<pattern>] * : Use wildcards ( * and ? ) to match option name. * * [--exclude=<pattern>] * : Pattern to exclude. Use wildcards ( * and ? ) to match option name. * * [--autoload=<value>] * : Match only autoload options when value is on, and only not-autoload option when off. * * [--transients] * : List only transients. Use `--no-transients` to ignore all transients. * * [--field=<field>] * : Prints the value of a single field. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : The serialization format for the value. total_bytes displays the total size of matching options in bytes. * --- * default: table * options: * - table * - json * - csv * - count * - yaml * - total_bytes * --- * * [--orderby=<fields>] * : Set orderby which field. * --- * default: option_id * options: * - option_id * - option_name * - option_value * --- * * [--order=<order>] * : Set ascending or descending order. * --- * default: asc * options: * - asc * - desc * --- * * ## AVAILABLE FIELDS * * This field will be displayed by default for each matching option: * * * option_name * * option_value * * These fields are optionally available: * * * autoload * * size_bytes * * ## EXAMPLES * * # Get the total size of all autoload options. * $ wp option list --autoload=on --format=total_bytes * 33198 * * # Find biggest transients. * $ wp option list --search="*_transient_*" --fields=option_name,size_bytes | sort -n -k 2 | tail * option_name size_bytes * _site_transient_timeout_theme_roots 10 * _site_transient_theme_roots 76 * _site_transient_update_themes 181 * _site_transient_update_core 808 * _site_transient_update_plugins 6645 * * # List all options beginning with "i2f_". * $ wp option list --search="i2f_*" * +-------------+--------------+ * | option_name | option_value | * +-------------+--------------+ * | i2f_version | 0.1.0 | * +-------------+--------------+ * * # Delete all options beginning with "theme_mods_". * $ wp option list --search="theme_mods_*" --field=option_name | xargs -I % wp option delete % * Success: Deleted 'theme_mods_twentysixteen' option. * Success: Deleted 'theme_mods_twentyfifteen' option. * Success: Deleted 'theme_mods_twentyfourteen' option. * * @subcommand list */ public function list_( $args, $assoc_args ) { global $wpdb; $pattern = '%'; $exclude = ''; $fields = array( 'option_name', 'option_value' ); $size_query = ",LENGTH(option_value) AS `size_bytes`"; $autoload_query = ''; $sort = Utils\get_flag_value( $assoc_args, 'order' ); if ( isset( $assoc_args['search'] ) ) { $pattern = self::esc_like( $assoc_args['search'] ); // substitute wildcards $pattern = str_replace( '*', '%', $pattern ); $pattern = str_replace( '?', '_', $pattern ); } if ( isset( $assoc_args['exclude'] ) ) { $exclude = self::esc_like( $assoc_args['exclude'] ); $exclude = str_replace( '*', '%', $exclude ); $exclude = str_replace( '?', '_', $exclude ); } if ( isset( $assoc_args['fields'] ) ) { $fields = explode( ',', $assoc_args['fields'] ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'total_bytes' ) { $fields = array( 'size_bytes' ); $size_query = ",SUM(LENGTH(option_value)) AS `size_bytes`"; } if ( isset( $assoc_args['autoload'] ) ) { if ( 'on' === $assoc_args['autoload'] ) { $autoload_query = " AND autoload='yes'"; } elseif ( 'off' === $assoc_args['autoload'] ) { $autoload_query = " AND autoload='no'"; } else { WP_CLI::error( "Value of '--autoload' should be on or off." ); } } $transients_query = ''; if ( true === Utils\get_flag_value( $assoc_args, 'transients', null ) ) { $transients_query = " AND option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'"; } else if ( false === Utils\get_flag_value( $assoc_args, 'transients', null ) ) { $transients_query = " AND option_name NOT LIKE '\_transient\_%' AND option_name NOT LIKE '\_site\_transient\_%'"; } $where = ''; if ( $pattern ) { $where .= $wpdb->prepare( "WHERE `option_name` LIKE %s", $pattern ); } if ( $exclude ) { $where .= $wpdb->prepare( " AND `option_name` NOT LIKE %s", $exclude ); } $where .= $autoload_query . $transients_query; $results = $wpdb->get_results( "SELECT `option_name`,`option_value`,`autoload`" . $size_query . " FROM `$wpdb->options` {$where}" ); $orderby = \WP_CLI\Utils\get_flag_value( $assoc_args, 'orderby' ); $order = \WP_CLI\Utils\get_flag_value( $assoc_args, 'order' ); // Sort result. if ( 'option_id' !== $orderby ) { usort( $results, function ( $a, $b ) use ( $orderby, $order ) { // Sort array. return 'asc' === $order ? $a->$orderby > $b->$orderby : $a->$orderby < $b->$orderby; }); } elseif ( 'option_id' === $orderby && 'desc' === $order ) { // Sort by default descending. krsort( $results ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'total_bytes' ) { WP_CLI::line( $results[0]->size_bytes ); } else { $formatter = new \WP_CLI\Formatter( $assoc_args, $fields ); $formatter->display_items( $results ); } } /** * Update an option value. * * ## OPTIONS * * <key> * : The name of the option to update. * * [<value>] * : The new value. If ommited, the value is read from STDIN. * * [--autoload=<autoload>] * : Requires WP 4.2. Should this option be automatically loaded. * --- * options: * - 'yes' * - 'no' * --- * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- * * ## EXAMPLES * * # Update an option by reading from a file. * $ wp option update my_option < value.txt * Success: Updated 'my_option' option. * * # Update one option on multiple sites using xargs. * $ wp site list --field=url | xargs -n1 -I {} sh -c 'wp --url={} option update my_option my_value' * Success: Updated 'my_option' option. * Success: Updated 'my_option' option. * * # Update site blog name. * $ wp option update blogname "Random blog name" * Success: Updated 'blogname' option. * * # Update site blog description. * $ wp option update blogdescription "Some random blog description" * Success: Updated 'blogdescription' option. * * # Update admin email address. * $ wp option update admin_email someone@example.com * Success: Updated 'admin_email' option. * * # Set the default role. * $ wp option update default_role author * Success: Updated 'default_role' option. * * # Set the timezone string. * $ wp option update timezone_string "America/New_York" * Success: Updated 'timezone_string' option. * * @alias set */ public function update( $args, $assoc_args ) { $key = $args[0]; $value = WP_CLI::get_value_from_arg_or_stdin( $args, 1 ); $value = WP_CLI::read_value( $value, $assoc_args ); $autoload = \WP_CLI\Utils\get_flag_value( $assoc_args, 'autoload' ); if ( ! in_array( $autoload, array( 'yes', 'no' ) ) ) { $autoload = null; } $value = sanitize_option( $key, $value ); // Sanitization WordPress normally performs when getting an option if ( in_array( $key, array('siteurl', 'home', 'category_base', 'tag_base') ) ) { $value = untrailingslashit( $value ); } $old_value = sanitize_option( $key, get_option( $key ) ); if ( $value === $old_value && is_null( $autoload ) ) { WP_CLI::success( "Value passed for '$key' option is unchanged." ); } else { if ( update_option( $key, $value, $autoload ) ) { WP_CLI::success( "Updated '$key' option." ); } else { WP_CLI::error( "Could not update option '$key'." ); } } } /** * Delete an option. * * ## OPTIONS * * <key> * : Key for the option. * * ## EXAMPLES * * # Delete an option. * $ wp option delete my_option * Success: Deleted 'my_option' option. */ public function delete( $args ) { list( $key ) = $args; if ( !delete_option( $key ) ) { WP_CLI::error( "Could not delete '$key' option. Does it exist?" ); } else { WP_CLI::success( "Deleted '$key' option." ); } } /** * Get a nested value from an option. * * ## OPTIONS * * <key> * : The option name. * * <key-path>... * : The name(s) of the keys within the value to locate the value to pluck. * * [--format=<format>] * : The output format of the value. * --- * default: plaintext * options: * - plaintext * - json * - yaml * --- */ public function pluck( $args, $assoc_args ) { list( $key ) = $args; $value = get_option( $key ); if ( false === $value ) { WP_CLI::halt( 1 ); } $key_path = array_map( function( $key ) { if ( is_numeric( $key ) && ( $key === (string) intval( $key ) ) ) { return (int) $key; } return $key; }, array_slice( $args, 1 ) ); $traverser = new RecursiveDataStructureTraverser( $value ); try { $value = $traverser->get( $key_path ); } catch ( \Exception $e ) { die( 1 ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Update a nested value in an option. * * ## OPTIONS * * <action> * : Patch action to perform. * --- * options: * - insert * - update * - delete * --- * * <key> * : The option name. * * <key-path>... * : The name(s) of the keys within the value to locate the value to patch. * * [<value>] * : The new value. If omitted, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- */ public function patch( $args, $assoc_args ) { list( $action, $key ) = $args; $key_path = array_map( function( $key ) { if ( is_numeric( $key ) && ( $key === (string) intval( $key ) ) ) { return (int) $key; } return $key; }, array_slice( $args, 2 ) ); if ( 'delete' == $action ) { $patch_value = null; } elseif ( \WP_CLI\Entity\Utils::has_stdin() ) { $stdin_value = WP_CLI::get_value_from_arg_or_stdin( $args, -1 ); $patch_value = WP_CLI::read_value( trim( $stdin_value ), $assoc_args ); } else { // Take the patch value as the last positional argument. Mutates $key_path to be 1 element shorter! $patch_value = WP_CLI::read_value( array_pop( $key_path ), $assoc_args ); } /* Need to make a copy of $current_value here as it is modified by reference */ $old_value = $current_value = sanitize_option( $key, get_option( $key ) ); $traverser = new RecursiveDataStructureTraverser( $current_value ); try { $traverser->$action( $key_path, $patch_value ); } catch ( \Exception $e ) { WP_CLI::error( $e->getMessage() ); } $patched_value = sanitize_option( $key, $traverser->value() ); if ( $patched_value === $old_value ) { WP_CLI::success( "Value passed for '$key' option is unchanged." ); } else { if ( update_option( $key, $patched_value ) ) { WP_CLI::success( "Updated '$key' option." ); } else { WP_CLI::error( "Could not update option '$key'." ); } } } private static function esc_like( $old ) { global $wpdb; // Remove notices in 4.0 and support backwards compatibility if( method_exists( $wpdb, 'esc_like' ) ) { // 4.0 $old = $wpdb->esc_like( $old ); } else { // 3.9 or less $old = like_escape( esc_sql( $old ) ); } return $old; } } <?php /** * Retrieves details on the site's registered post types. * * Get information on WordPress' built-in and the site's [custom post types](https://developer.wordpress.org/plugins/post-types/). * * ## EXAMPLES * * # Get details about a post type * $ wp post-type get page --fields=name,label,hierarchical --format=json * {"name":"page","label":"Pages","hierarchical":true} * * # List post types with 'post' capability type * $ wp post-type list --capability_type=post --fields=name,public * +---------------+--------+ * | name | public | * +---------------+--------+ * | post | 1 | * | attachment | 1 | * | revision | | * | nav_menu_item | | * +---------------+--------+ * * @package wp-cli */ class Post_Type_Command extends WP_CLI_Command { private $fields = array( 'name', 'label', 'description', 'hierarchical', 'public', 'capability_type', ); /** * List registered post types. * * ## OPTIONS * * [--<field>=<value>] * : Filter by one or more fields (see get_post_types() first parameter for a list of available fields). * * [--field=<field>] * : Prints the value of a single field for each post type. * * [--fields=<fields>] * : Limit the output to specific post type fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each term: * * * name * * label * * description * * hierarchical * * public * * capability_type * * There are no optionally available fields. * * ## EXAMPLES * * # List registered post types * $ wp post-type list --format=csv * name,label,description,hierarchical,public,capability_type * post,Posts,,,1,post * page,Pages,,1,1,page * attachment,Media,,,1,post * revision,Revisions,,,,post * nav_menu_item,"Navigation Menu Items",,,,post * * # List post types with 'post' capability type * $ wp post-type list --capability_type=post --fields=name,public * +---------------+--------+ * | name | public | * +---------------+--------+ * | post | 1 | * | attachment | 1 | * | revision | | * | nav_menu_item | | * +---------------+--------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); $types = get_post_types( $assoc_args, 'objects' ); $formatter->display_items( $types ); } /** * Get details about a registered post type. * * ## OPTIONS * * <post-type> * : Post type slug * * [--field=<field>] * : Instead of returning the whole taxonomy, returns the value of a single field. * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Get details about the 'page' post type. * $ wp post-type get page --fields=name,label,hierarchical --format=json * {"name":"page","label":"Pages","hierarchical":true} */ public function get( $args, $assoc_args ) { $post_type = get_post_type_object( $args[0] ); if ( ! $post_type ) { WP_CLI::error( "Post type {$args[0]} doesn't exist." ); } if ( empty( $assoc_args['fields'] ) ) { $default_fields = array_merge( $this->fields, array( 'labels', 'cap' ) ); $assoc_args['fields'] = $default_fields; } $data = array( 'name' => $post_type->name, 'label' => $post_type->label, 'description' => $post_type->description, 'hierarchical' => $post_type->hierarchical, 'public' => $post_type->public, 'capability_type' => $post_type->capability_type, 'labels' => $post_type->labels, 'cap' => $post_type->cap, ); $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $data ); } private function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'post-type' ); } } <?php use WP_CLI\Utils; /** * Manages taxonomy terms and term meta, with create, delete, and list commands. * * See reference for [taxonomies and their terms](https://codex.wordpress.org/Taxonomies). * * ## EXAMPLES * * # Create a new term. * $ wp term create category Apple --description="A type of fruit" * Success: Created category 199. * * # Get details about a term. * $ wp term get category 199 --format=json --fields=term_id,name,slug,count * {"term_id":199,"name":"Apple","slug":"apple","count":1} * * # Update an existing term. * $ wp term update category 15 --name=Apple * Success: Term updated. * * # Get the term's URL. * $ wp term list post_tag --include=123 --field=url * http://example.com/tag/tips-and-tricks * * # Delete post category * $ wp term delete category 15 * Success: Deleted category 15. * * # Recount posts assigned to each categories and tags * $ wp term recount category post_tag * Success: Updated category term count * Success: Updated post_tag term count * * @package wp-cli */ class Term_Command extends WP_CLI_Command { private $fields = array( 'term_id', 'term_taxonomy_id', 'name', 'slug', 'description', 'parent', 'count', ); /** * List terms in a taxonomy. * * ## OPTIONS * * <taxonomy>... * : List terms of one or more taxonomies * * [--<field>=<value>] * : Filter by one or more fields (see get_terms() $args parameter for a list of fields). * * [--field=<field>] * : Prints the value of a single field for each term. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each term: * * * term_id * * term_taxonomy_id * * name * * slug * * description * * parent * * count * * These fields are optionally available: * * * url * * ## EXAMPLES * * # List post categories * $ wp term list category --format=csv * term_id,term_taxonomy_id,name,slug,description,parent,count * 2,2,aciform,aciform,,0,1 * 3,3,antiquarianism,antiquarianism,,0,1 * 4,4,arrangement,arrangement,,0,1 * 5,5,asmodeus,asmodeus,,0,1 * * # List post tags * $ wp term list post_tag --fields=name,slug * +-----------+-------------+ * | name | slug | * +-----------+-------------+ * | 8BIT | 8bit | * | alignment | alignment-2 | * | Articles | articles | * | aside | aside | * +-----------+-------------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { foreach ( $args as $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) { WP_CLI::error( "Taxonomy $taxonomy doesn't exist." ); } } $formatter = $this->get_formatter( $assoc_args ); $defaults = array( 'hide_empty' => false, ); $assoc_args = array_merge( $defaults, $assoc_args ); if ( ! empty( $assoc_args['term_id'] ) ) { $term = get_term_by( 'id', $assoc_args['term_id'], $args[0] ); $terms = array( $term ); } else if ( ! empty( $assoc_args['include'] ) && ! empty( $assoc_args['orderby'] ) && 'include' === $assoc_args['orderby'] && Utils\wp_version_compare( '4.7', '<' ) ) { $terms = array(); $term_ids = explode( ',', $assoc_args['include'] ); foreach( $term_ids as $term_id ) { $term = get_term_by( 'id', $term_id, $args[0] ); if ( $term && ! is_wp_error( $term ) ) { $terms[] = $term; } else { WP_CLI::warning( sprintf( "Invalid term %s.", $term_id ) ); } } } else { $terms = get_terms( $args, $assoc_args ); } $terms = array_map( function( $term ){ $term->count = (int)$term->count; $term->parent = (int)$term->parent; $term->url = get_term_link( $term ); return $term; }, $terms ); if ( 'ids' == $formatter->format ) { $terms = wp_list_pluck( $terms, 'term_id' ); echo implode( ' ', $terms ); } else { $formatter->display_items( $terms ); } } /** * Create a new term. * * ## OPTIONS * * <taxonomy> * : Taxonomy for the new term. * * <term> * : A name for the new term. * * [--slug=<slug>] * : A unique slug for the new term. Defaults to sanitized version of name. * * [--description=<description>] * : A description for the new term. * * [--parent=<term-id>] * : A parent for the new term. * * [--porcelain] * : Output just the new term id. * * ## EXAMPLES * * # Create a new category "Apple" with a description. * $ wp term create category Apple --description="A type of fruit" * Success: Created category 199. */ public function create( $args, $assoc_args ) { list( $taxonomy, $term ) = $args; $defaults = array( 'slug' => sanitize_title( $term ), 'description' => '', 'parent' => 0, ); $assoc_args = wp_parse_args( $assoc_args, $defaults ); $porcelain = \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ); unset( $assoc_args['porcelain'] ); // Compatibility for < WP 4.0 if ( $assoc_args['parent'] > 0 && ! term_exists( (int) $assoc_args['parent'] ) ) { WP_CLI::error( 'Parent term does not exist.' ); } $assoc_args = wp_slash( $assoc_args ); $term = wp_slash( $term ); $ret = wp_insert_term( $term, $taxonomy, $assoc_args ); if ( is_wp_error( $ret ) ) { WP_CLI::error( $ret->get_error_message() ); } else { if ( $porcelain ) WP_CLI::line( $ret['term_id'] ); else WP_CLI::success( sprintf( "Created %s %d.", $taxonomy, $ret['term_id'] ) ); } } /** * Get details about a term. * * ## OPTIONS * * <taxonomy> * : Taxonomy of the term to get * * <term> * : ID or slug of the term to get * * [--by=<field>] * : Explicitly handle the term value as a slug or id. * --- * default: id * options: * - slug * - id * --- * * [--field=<field>] * : Instead of returning the whole term, returns the value of a single field. * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Get details about a category with id 199. * $ wp term get category 199 --format=json * {"term_id":199,"name":"Apple","slug":"apple","term_group":0,"term_taxonomy_id":199,"taxonomy":"category","description":"A type of fruit","parent":0,"count":0,"filter":"raw"} * * # Get details about a category with slug apple. * $ wp term get category apple --by=slug --format=json * {"term_id":199,"name":"Apple","slug":"apple","term_group":0,"term_taxonomy_id":199,"taxonomy":"category","description":"A type of fruit","parent":0,"count":0,"filter":"raw"} */ public function get( $args, $assoc_args ) { list( $taxonomy, $term ) = $args; $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term, $taxonomy ); if ( ! $term ) { WP_CLI::error( "Term doesn't exist." ); } if ( empty( $assoc_args['fields'] ) ) { $term_array = get_object_vars( $term ); $assoc_args['fields'] = array_keys( $term_array ); } $term->count = (int) $term->count; $term->parent = (int) $term->parent; $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $term ); } /** * Update an existing term. * * ## OPTIONS * * <taxonomy> * : Taxonomy of the term to update. * * <term> * : ID or slug for the term to update. * * [--by=<field>] * : Explicitly handle the term value as a slug or id. * --- * default: id * options: * - slug * - id * --- * * [--name=<name>] * : A new name for the term. * * [--slug=<slug>] * : A new slug for the term. * * [--description=<description>] * : A new description for the term. * * [--parent=<term-id>] * : A new parent for the term. * * ## EXAMPLES * * # Change category with id 15 to use the name "Apple" * $ wp term update category 15 --name=Apple * Success: Term updated. * * # Change category with slug apple to use the name "Apple" * $ wp term update category apple --by=slug --name=Apple * Success: Term updated. */ public function update( $args, $assoc_args ) { list( $taxonomy, $term ) = $args; $defaults = array( 'name' => null, 'slug' => null, 'description' => null, 'parent' => null, ); $assoc_args = wp_parse_args( $assoc_args, $defaults ); foreach( $assoc_args as $key => $value ) { if ( is_null( $value ) ) unset( $assoc_args[$key] ); } $assoc_args = wp_slash( $assoc_args ); list( $taxonomy, $term ) = $args; $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term, $taxonomy ); if ( ! $term ) { WP_CLI::error( "Term doesn't exist." ); } // Update term. $ret = wp_update_term( $term->term_id, $taxonomy, $assoc_args ); if ( is_wp_error( $ret ) ) WP_CLI::error( $ret->get_error_message() ); else WP_CLI::success( "Term updated." ); } /** * Delete an existing term. * * Errors if the term doesn't exist, or there was a problem in deleting it. * * ## OPTIONS * * <taxonomy> * : Taxonomy of the term to delete. * * <term>... * : One or more IDs or slugs of terms to delete. * * [--by=<field>] * : Explicitly handle the term value as a slug or id. * --- * default: id * options: * - slug * - id * --- * * ## EXAMPLES * * # Delete post category by id * $ wp term delete category 15 * Deleted category 15. * Success: Deleted 1 of 1 terms. * * # Delete post category by slug * $ wp term delete category apple --by=slug * Deleted category 15. * Success: Deleted 1 of 1 terms. * * # Delete all post tags * $ wp term list post_tag --field=term_id | xargs wp term delete post_tag * Deleted post_tag 159. * Deleted post_tag 160. * Deleted post_tag 161. * Success: Deleted 3 of 3 terms. */ public function delete( $args, $assoc_args ) { $taxonomy = array_shift( $args ); $successes = $errors = 0; foreach ( $args as $term_id ) { $term = $term_id; // Get term by specified argument otherwise get term by id. if ( 'slug' === ( $field = Utils\get_flag_value( $assoc_args, 'by' ) ) ) { // Get term by slug. $term = get_term_by( $field, $term, $taxonomy ); // If term not found, then show error message and skip the iteration. if ( ! $term ) { WP_CLI::warning( sprintf( "%s %s doesn't exist.", $taxonomy, $term_id ) ); continue; } // Get the term id; $term_id = $term->term_id; } $ret = wp_delete_term( $term_id, $taxonomy ); if ( is_wp_error( $ret ) ) { WP_CLI::warning( $ret ); $errors++; } else if ( $ret ) { WP_CLI::log( sprintf( "Deleted %s %d.", $taxonomy, $term_id ) ); $successes++; } else { WP_CLI::warning( sprintf( "%s %s doesn't exist.", $taxonomy, $term_id ) ); } } Utils\report_batch_operation_results( 'term', 'delete', count( $args ), $successes, $errors ); } /** * Generate some terms. * * Creates a specified number of new terms with dummy data. * * ## OPTIONS * * <taxonomy> * : The taxonomy for the generated terms. * * [--count=<number>] * : How many terms to generate? * --- * default: 100 * --- * * [--max_depth=<number>] * : Generate child terms down to a certain depth. * --- * default: 1 * --- * * [--format=<format>] * : Render output in a particular format. * --- * default: progress * options: * - progress * - ids * --- * * ## EXAMPLES * * # Generate post categories. * $ wp term generate category --count=10 * Generating terms 100% [=========] 0:02 / 0:02 * * # Add meta to every generated term. * $ wp term generate category --format=ids --count=3 | xargs -d ' ' -I % wp term meta add % foo bar * Success: Added custom field. * Success: Added custom field. * Success: Added custom field. */ public function generate( $args, $assoc_args ) { global $wpdb; list ( $taxonomy ) = $args; $defaults = array( 'count' => 100, 'max_depth' => 1, ); extract( array_merge( $defaults, $assoc_args ), EXTR_SKIP ); if ( !taxonomy_exists( $taxonomy ) ) { WP_CLI::error( sprintf( "'%s' is not a registered taxonomy.", $taxonomy ) ); } $label = get_taxonomy( $taxonomy )->labels->singular_name; $slug = sanitize_title_with_dashes( $label ); $hierarchical = get_taxonomy( $taxonomy )->hierarchical; $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'progress' ); $notify = false; if ( 'progress' === $format ) { $notify = \WP_CLI\Utils\make_progress_bar( 'Generating terms', $count ); } $previous_term_id = 0; $current_parent = 0; $current_depth = 1; $max_id = (int) $wpdb->get_var( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy ORDER BY term_taxonomy_id DESC LIMIT 1" ); $suspend_cache_invalidation = wp_suspend_cache_invalidation( true ); $created = array(); for ( $i = $max_id + 1; $i <= $max_id + $count; $i++ ) { if ( $hierarchical ) { if ( $previous_term_id && $this->maybe_make_child() && $current_depth < $max_depth ) { $current_parent = $previous_term_id; $current_depth++; } else if ( $this->maybe_reset_depth() ) { $current_parent = 0; $current_depth = 1; } } $args = array( 'parent' => $current_parent, 'slug' => $slug . "-$i", ); $name = "$label $i"; $term = wp_insert_term( $name, $taxonomy, $args ); if ( is_wp_error( $term ) ) { WP_CLI::warning( $term ); } else { $created[] = $term['term_id']; $previous_term_id = $term['term_id']; if ( 'ids' === $format ) { echo $term['term_id']; if ( $i < $max_id + $count ) { echo ' '; } } } if ( 'progress' === $format ) { $notify->tick(); } } wp_suspend_cache_invalidation( $suspend_cache_invalidation ); clean_term_cache( $created, $taxonomy ); if ( 'progress' === $format ) { $notify->finish(); } } /** * Recalculate number of posts assigned to each term. * * In instances where manual updates are made to the terms assigned to * posts in the database, the number of posts associated with a term * can become out-of-sync with the actual number of posts. * * This command runs wp_update_term_count() on the taxonomy's terms * to bring the count back to the correct value. * * ## OPTIONS * * <taxonomy>... * : One or more taxonomies to recalculate. * * ## EXAMPLES * * # Recount posts assigned to each categories and tags * $ wp term recount category post_tag * Success: Updated category term count. * Success: Updated post_tag term count. * * # Recount all listed taxonomies * $ wp taxonomy list --field=name | xargs wp term recount * Success: Updated category term count. * Success: Updated post_tag term count. * Success: Updated nav_menu term count. * Success: Updated link_category term count. * Success: Updated post_format term count. */ public function recount( $args ) { foreach( $args as $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) { WP_CLI::warning( sprintf( "Taxonomy %s does not exist.", $taxonomy ) ); } else { $terms = get_terms( $taxonomy, array( 'hide_empty' => false ) ); $term_taxonomy_ids = wp_list_pluck( $terms, 'term_taxonomy_id' ); wp_update_term_count( $term_taxonomy_ids, $taxonomy ); WP_CLI::success( sprintf( "Updated %s term count.", $taxonomy ) ); } } } private function maybe_make_child() { // 50% chance of making child term return ( mt_rand(1, 2) == 1 ); } private function maybe_reset_depth() { // 10% chance of reseting to root depth return ( mt_rand(1, 10) == 7 ); } private function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'term' ); } } <?php use WP_CLI\Utils; /** * Lists, creates, assigns, and deletes the active theme's navigation menus. * * See the [Navigation Menus](https://developer.wordpress.org/themes/functionality/navigation-menus/) reference in the Theme Handbook. * * ## EXAMPLES * * # Create a new menu * $ wp menu create "My Menu" * Success: Created menu 200. * * # List existing menus * $ wp menu list * +---------+----------+----------+-----------+-------+ * | term_id | name | slug | locations | count | * +---------+----------+----------+-----------+-------+ * | 200 | My Menu | my-menu | | 0 | * | 177 | Top Menu | top-menu | primary | 7 | * +---------+----------+----------+-----------+-------+ * * # Create a new menu link item * $ wp menu item add-custom my-menu Apple http://apple.com --porcelain * 1922 * * # Assign the 'my-menu' menu to the 'primary' location * $ wp menu location assign my-menu primary * Success: Assigned location to menu. * * @package wp-cli */ class Menu_Command extends WP_CLI_Command { protected $obj_type = 'nav_menu'; protected $obj_fields = array( 'term_id', 'name', 'slug', 'locations', 'count', ); /** * Create a new menu. * * ## OPTIONS * * <menu-name> * : A descriptive name for the menu. * * [--porcelain] * : Output just the new menu id. * * ## EXAMPLES * * $ wp menu create "My Menu" * Success: Created menu 200. */ public function create( $args, $assoc_args ) { $menu_id = wp_create_nav_menu( $args[0] ); if ( is_wp_error( $menu_id ) ) { WP_CLI::error( $menu_id->get_error_message() ); } else { if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { WP_CLI::line( $menu_id ); } else { WP_CLI::success( "Created menu $menu_id." ); } } } /** * Delete one or more menus. * * ## OPTIONS * * <menu>... * : The name, slug, or term ID for the menu(s). * * ## EXAMPLES * * $ wp menu delete "My Menu" * Success: 1 menu deleted. */ public function delete( $args, $_ ) { $count = $errors = 0; foreach( $args as $arg ) { $ret = wp_delete_nav_menu( $arg ); if ( ! $ret || is_wp_error( $ret ) ) { WP_CLI::warning( "Couldn't delete menu '{$arg}'." ); $errors++; } else { WP_CLI::log( "Deleted menu '{$arg}'." ); $count++; } } Utils\report_batch_operation_results( 'menu', 'delete', count( $args ), $count, $errors ); } /** * Get a list of menus. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - ids * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each menu: * * * term_id * * name * * slug * * count * * These fields are optionally available: * * * term_group * * term_taxonomy_id * * taxonomy * * description * * parent * * locations * * ## EXAMPLES * * $ wp menu list * +---------+----------+----------+-----------+-------+ * | term_id | name | slug | locations | count | * +---------+----------+----------+-----------+-------+ * | 200 | My Menu | my-menu | | 0 | * | 177 | Top Menu | top-menu | primary | 7 | * +---------+----------+----------+-----------+-------+ * * @subcommand list */ public function list_( $_, $assoc_args ) { $menus = wp_get_nav_menus(); $menu_locations = get_nav_menu_locations(); foreach( $menus as &$menu ) { $menu->locations = array(); foreach( $menu_locations as $location => $term_id ) { if ( $term_id == $menu->term_id ) { $menu->locations[] = $location; } } // Normalize the data for some output formats. if ( ! isset( $assoc_args['format'] ) || in_array( $assoc_args['format'], array( 'csv', 'table' ) ) ) { $menu->locations = implode( ',', $menu->locations ); } } $formatter = $this->get_formatter( $assoc_args ); if ( 'ids' == $formatter->format ) { $ids = array_map( function($o) { return $o->term_id; }, $menus ); $formatter->display_items( $ids ); } else { $formatter->display_items( $menus ); } } protected function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->obj_fields, $this->obj_type ); } } <?php /** * Manage post terms. * * ## EXAMPLES * * # Set post terms * $ wp post term set 123 test category * Success: Set terms. */ class Post_Term_Command extends \WP_CLI\CommandWithTerms { protected $obj_type = 'post'; public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\Post; } protected function get_object_type() { $post = $this->fetcher->get_check( $this->get_obj_id() ); return $post->post_type; } } <?php use WP_CLI\Utils; /** * Creates, updates, deletes, and moderates comments. * * ## EXAMPLES * * # Create a new comment. * $ wp comment create --comment_post_ID=15 --comment_content="hello blog" --comment_author="wp-cli" * Success: Created comment 932. * * # Update an existing comment. * $ wp comment update 123 --comment_author='That Guy' * Success: Updated comment 123. * * # Delete an existing comment. * $ wp comment delete 1337 --force * Success: Deleted comment 1337. * * # Delete all spam comments. * $ wp comment delete $(wp comment list --status=spam --format=ids) * Success: Deleted comment 264. * Success: Deleted comment 262. * * @package wp-cli */ class Comment_Command extends \WP_CLI\CommandWithDBObject { protected $obj_type = 'comment'; protected $obj_id_key = 'comment_ID'; protected $obj_fields = array( 'comment_ID', 'comment_post_ID', 'comment_date', 'comment_approved', 'comment_author', 'comment_author_email', ); public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\Comment; } /** * Create a new comment. * * ## OPTIONS * * [--<field>=<value>] * : Associative args for the new comment. See wp_insert_comment(). * * [--porcelain] * : Output just the new comment id. * * ## EXAMPLES * * # Create comment. * $ wp comment create --comment_post_ID=15 --comment_content="hello blog" --comment_author="wp-cli" * Success: Created comment 932. */ public function create( $args, $assoc_args ) { $assoc_args = wp_slash( $assoc_args ); parent::_create( $args, $assoc_args, function ( $params ) { if ( isset( $params['comment_post_ID'] ) ) { $post_id = $params['comment_post_ID']; $post = get_post( $post_id ); if ( !$post ) { return new WP_Error( 'no_post', "Can't find post $post_id." ); } } // We use wp_insert_comment() instead of wp_new_comment() to stay at a low level and // avoid wp_die() formatted messages or notifications $comment_id = wp_insert_comment( $params ); if ( !$comment_id ) { return new WP_Error( 'db_error', 'Could not create comment.' ); } return $comment_id; } ); } /** * Update one or more comments. * * ## OPTIONS * * <id>... * : One or more IDs of comments to update. * * --<field>=<value> * : One or more fields to update. See wp_update_comment(). * * ## EXAMPLES * * # Update comment. * $ wp comment update 123 --comment_author='That Guy' * Success: Updated comment 123. */ public function update( $args, $assoc_args ) { $assoc_args = wp_slash( $assoc_args ); parent::_update( $args, $assoc_args, function ( $params ) { if ( !wp_update_comment( $params ) ) { return new WP_Error( 'Could not update comment.' ); } return true; } ); } /** * Generate some number of new dummy comments. * * Creates a specified number of new comments with dummy data. * * ## OPTIONS * * [--count=<number>] * : How many comments to generate? * --- * default: 100 * --- * * [--post_id=<post-id>] * : Assign comments to a specific post. * * [--format=<format>] * : Render output in a particular format. * --- * default: progress * options: * - progress * - ids * --- * * ## EXAMPLES * * # Generate comments for the given post. * $ wp comment generate --format=ids --count=3 --post_id=123 * 138 139 140 * * # Add meta to every generated comment. * $ wp comment generate --format=ids --count=3 | xargs -d ' ' -I % wp comment meta add % foo bar * Success: Added custom field. * Success: Added custom field. * Success: Added custom field. */ public function generate( $args, $assoc_args ) { $defaults = array( 'count' => 100, 'post_id' => null, ); $assoc_args = array_merge( $defaults, $assoc_args ); $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'progress' ); $notify = false; if ( 'progress' === $format ) { $notify = \WP_CLI\Utils\make_progress_bar( 'Generating comments', $assoc_args['count'] ); } $comment_count = wp_count_comments(); $total = (int )$comment_count->total_comments; $limit = $total + $assoc_args['count']; for ( $i = $total; $i < $limit; $i++ ) { $comment_id = wp_insert_comment( array( 'comment_content' => "Comment {$i}", 'comment_post_ID' => $assoc_args['post_id'], ) ); if ( 'progress' === $format ) { $notify->tick(); } else if ( 'ids' === $format ) { if ( 'ids' === $format ) { echo $comment_id; if ( $i < $limit - 1 ) { echo ' '; } } } } if ( 'progress' === $format ) { $notify->finish(); } } /** * Get data of a single comment. * * ## OPTIONS * * <id> * : The comment to get. * * [--field=<field>] * : Instead of returning the whole comment, returns the value of a single field. * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Get comment. * $ wp comment get 21 --field=content * Thanks for all the comments, everyone! */ public function get( $args, $assoc_args ) { $comment_id = (int)$args[0]; $comment = get_comment( $comment_id ); if ( empty( $comment ) ) { WP_CLI::error( "Invalid comment ID." ); } if ( empty( $assoc_args['fields'] ) ) { $comment_array = get_object_vars( $comment ); $assoc_args['fields'] = array_keys( $comment_array ); } $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $comment ); } /** * Get a list of comments. * * ## OPTIONS * * [--<field>=<value>] * : One or more args to pass to WP_Comment_Query. * * [--field=<field>] * : Prints the value of a single field for each comment. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - ids * - csv * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each comment: * * * comment_ID * * comment_post_ID * * comment_date * * comment_approved * * comment_author * * comment_author_email * * These fields are optionally available: * * * comment_author_url * * comment_author_IP * * comment_date_gmt * * comment_content * * comment_karma * * comment_agent * * comment_type * * comment_parent * * user_id * * url * * ## EXAMPLES * * # List comment IDs. * $ wp comment list --field=ID * 22 * 23 * 24 * * # List comments of a post. * $ wp comment list --post_id=1 --fields=ID,comment_date,comment_author * +------------+---------------------+----------------+ * | comment_ID | comment_date | comment_author | * +------------+---------------------+----------------+ * | 1 | 2015-06-20 09:00:10 | Mr WordPress | * +------------+---------------------+----------------+ * * # List approved comments. * $ wp comment list --number=3 --status=approve --fields=ID,comment_date,comment_author * +------------+---------------------+----------------+ * | comment_ID | comment_date | comment_author | * +------------+---------------------+----------------+ * | 1 | 2015-06-20 09:00:10 | Mr WordPress | * | 30 | 2013-03-14 12:35:07 | John Doe | * | 29 | 2013-03-14 11:56:08 | Jane Doe | * +------------+---------------------+----------------+ * * @subcommand list */ public function list_( $_, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); if ( 'ids' == $formatter->format ) { $assoc_args['fields'] = 'comment_ID'; } $assoc_args = self::process_csv_arguments_to_arrays( $assoc_args ); if ( 'count' === $formatter->format ) { $assoc_args['count'] = true; } if ( ! empty( $assoc_args['comment__in'] ) && ! empty( $assoc_args['orderby'] ) && 'comment__in' === $assoc_args['orderby'] && Utils\wp_version_compare( '4.4', '<' ) ) { $comments = array(); foreach( $assoc_args['comment__in'] as $comment_id ) { $comment = get_comment( $comment_id ); if ( $comment ) { $comments[] = $comment; } else { WP_CLI::warning( sprintf( "Invalid comment %s.", $comment_id ) ); } } } else { $query = new WP_Comment_Query(); $comments = $query->query( $assoc_args ); } if ( 'count' === $formatter->format ) { echo $comments; } else { if ( 'ids' == $formatter->format ) { $comments = wp_list_pluck( $comments, 'comment_ID' ); } elseif ( is_array( $comments ) ) { $comments = array_map( function( $comment ){ $comment->url = get_comment_link( $comment->comment_ID ); return $comment; }, $comments ); } $formatter->display_items( $comments ); } } /** * Delete a comment. * * ## OPTIONS * * <id>... * : One or more IDs of comments to delete. * * [--force] * : Skip the trash bin. * * ## EXAMPLES * * # Delete comment. * $ wp comment delete 1337 --force * Success: Deleted comment 1337. * * # Delete multiple comments. * $ wp comment delete 1337 2341 --force * Success: Deleted comment 1337. * Success: Deleted comment 2341. */ public function delete( $args, $assoc_args ) { parent::_delete( $args, $assoc_args, function ( $comment_id, $assoc_args ) { $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $status = wp_get_comment_status( $comment_id ); $r = wp_delete_comment( $comment_id, $force ); if ( $r ) { if ( $force || 'trash' === $status ) { return array( 'success', "Deleted comment $comment_id." ); } else { return array( 'success', "Trashed comment $comment_id." ); } } else { return array( 'error', "Failed deleting comment $comment_id." ); } } ); } private function call( $args, $status, $success, $failure ) { $comment_id = absint( $args ); $func = sprintf( 'wp_%s_comment', $status ); if ( $func( $comment_id ) ) { WP_CLI::success( "$success comment $comment_id." ); } else { WP_CLI::error( "$failure comment $comment_id." ); } } private function set_status( $args, $status, $success ) { $comment = $this->fetcher->get_check( $args ); $r = wp_set_comment_status( $comment->comment_ID, $status, true ); if ( is_wp_error( $r ) ) { WP_CLI::error( $r ); } else { WP_CLI::success( "$success comment $comment->comment_ID." ); } } /** * Trash a comment. * * ## OPTIONS * * <id>... * : The IDs of the comments to trash. * * ## EXAMPLES * * # Trash comment. * $ wp comment trash 1337 * Success: Trashed comment 1337. */ public function trash( $args, $assoc_args ) { foreach( $args as $id ) { $this->call( $id, __FUNCTION__, 'Trashed', 'Failed trashing' ); } } /** * Untrash a comment. * * ## OPTIONS * * <id>... * : The IDs of the comments to untrash. * * ## EXAMPLES * * # Untrash comment. * $ wp comment untrash 1337 * Success: Untrashed comment 1337. */ public function untrash( $args, $assoc_args ) { foreach( $args as $id ) { $this->call( $id, __FUNCTION__, 'Untrashed', 'Failed untrashing' ); } } /** * Mark a comment as spam. * * ## OPTIONS * * <id>... * : The IDs of the comments to mark as spam. * * ## EXAMPLES * * # Spam comment. * $ wp comment spam 1337 * Success: Marked as spam comment 1337. */ public function spam( $args, $assoc_args ) { foreach( $args as $id ) { $this->call( $id, __FUNCTION__, 'Marked as spam', 'Failed marking as spam' ); } } /** * Unmark a comment as spam. * * ## OPTIONS * * <id>... * : The IDs of the comments to unmark as spam. * * ## EXAMPLES * * # Unspam comment. * $ wp comment unspam 1337 * Success: Unspammed comment 1337. */ public function unspam( $args, $assoc_args ) { foreach( $args as $id ) { $this->call( $id, __FUNCTION__, 'Unspammed', 'Failed unspamming' ); } } /** * Approve a comment. * * ## OPTIONS * * <id>... * : The IDs of the comments to approve. * * ## EXAMPLES * * # Approve comment. * $ wp comment approve 1337 * Success: Approved comment 1337. */ public function approve( $args, $assoc_args ) { foreach( $args as $id ) { $this->set_status( $id, 'approve', "Approved" ); } } /** * Unapprove a comment. * * ## OPTIONS * * <id>... * : The IDs of the comments to unapprove. * * ## EXAMPLES * * # Unapprove comment. * $ wp comment unapprove 1337 * Success: Unapproved comment 1337. */ public function unapprove( $args, $assoc_args ) { foreach( $args as $id ) { $this->set_status( $id, 'hold', "Unapproved" ); } } /** * Count comments, on whole blog or on a given post. * * ## OPTIONS * * [<post-id>] * : The ID of the post to count comments in. * * ## EXAMPLES * * # Count comments on whole blog. * $ wp comment count * approved: 33 * spam: 3 * trash: 1 * post-trashed: 0 * all: 34 * moderated: 1 * total_comments: 37 * * # Count comments in a post. * $ wp comment count 42 * approved: 19 * spam: 0 * trash: 0 * post-trashed: 0 * all: 19 * moderated: 0 * total_comments: 19 */ public function count( $args, $assoc_args ) { $post_id = \WP_CLI\Utils\get_flag_value( $args, 0, 0 ); $count = wp_count_comments( $post_id ); // Move total_comments to the end of the object $total = $count->total_comments; unset( $count->total_comments ); $count->total_comments = $total; foreach ( $count as $status => $count ) { WP_CLI::line( str_pad( "$status:", 17 ) . $count ); } } /** * Recalculate the comment_count value for one or more posts. * * ## OPTIONS * * <id>... * : IDs for one or more posts to update. * * ## EXAMPLES * * # Recount comment for the post. * $ wp comment recount 123 * Updated post 123 comment count to 67. */ public function recount( $args ) { foreach( $args as $id ) { wp_update_comment_count( $id ); $post = get_post( $id ); if ( $post ) { WP_CLI::log( sprintf( "Updated post %d comment count to %d.", $post->ID, $post->comment_count ) ); } else { WP_CLI::warning( sprintf( "Post %d doesn't exist.", $post->ID ) ); } } } /** * Get status of a comment. * * ## OPTIONS * * <id> * : The ID of the comment to check. * * ## EXAMPLES * * # Get status of comment. * $ wp comment status 1337 * approved */ public function status( $args, $assoc_args ) { list( $comment_id ) = $args; $status = wp_get_comment_status( $comment_id ); if ( false === $status ) { WP_CLI::error( "Could not check status of comment $comment_id." ); } else { WP_CLI::line( $status ); } } /** * Verify whether a comment exists. * * Displays a success message if the comment does exist. * * ## OPTIONS * * <id> * : The ID of the comment to check. * * ## EXAMPLES * * # Check whether comment exists. * $ wp comment exists 1337 * Success: Comment with ID 1337 exists. */ public function exists( $args ) { if ( $this->fetcher->get( $args[0] ) ) { WP_CLI::success( "Comment with ID $args[0] exists." ); } } } <?php /** * Manage a menu's assignment to locations. * * ## EXAMPLES * * # List available menu locations * $ wp menu location list * +----------+-------------------+ * | location | description | * +----------+-------------------+ * | primary | Primary Menu | * | social | Social Links Menu | * +----------+-------------------+ * * # Assign the 'primary-menu' menu to the 'primary' location * $ wp menu location assign primary-menu primary * Success: Assigned location to menu. * * # Remove the 'primary-menu' menu from the 'primary' location * $ wp menu location remove primary-menu primary * Success: Removed location from menu. */ class Menu_Location_Command extends WP_CLI_Command { /** * List locations for the current theme. * * ## OPTIONS * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * - ids * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each location: * * * name * * description * * ## EXAMPLES * * $ wp menu location list * +----------+-------------------+ * | location | description | * +----------+-------------------+ * | primary | Primary Menu | * | social | Social Links Menu | * +----------+-------------------+ * * @subcommand list */ public function list_( $_, $assoc_args ) { $locations = get_registered_nav_menus(); $location_objs = array(); foreach( $locations as $location => $description ) { $location_obj = new \stdClass; $location_obj->location = $location; $location_obj->description = $description; $location_objs[] = $location_obj; } $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'location', 'description' ) ); if ( 'ids' == $formatter->format ) { $ids = array_map( function($o) { return $o->location; }, $location_objs ); $formatter->display_items( $ids ); } else { $formatter->display_items( $location_objs ); } } /** * Assign a location to a menu. * * ## OPTIONS * * <menu> * : The name, slug, or term ID for the menu. * * <location> * : Location's slug. * * ## EXAMPLES * * $ wp menu location assign primary-menu primary * Success: Assigned location primary to menu primary-menu. * * @subcommand assign */ public function assign( $args, $_ ) { list( $menu, $location ) = $args; $menu_obj = wp_get_nav_menu_object( $menu ); if ( ! $menu_obj ) { WP_CLI::error( "Invalid menu $menu." ); } $locations = get_registered_nav_menus(); if ( ! array_key_exists( $location, $locations ) ) { WP_CLI::error( "Invalid location $location." ); } $locations = get_nav_menu_locations(); $locations[ $location ] = $menu_obj->term_id; set_theme_mod( 'nav_menu_locations', $locations ); WP_CLI::success( "Assigned location $location to menu $menu." ); } /** * Remove a location from a menu. * * ## OPTIONS * * <menu> * : The name, slug, or term ID for the menu. * * <location> * : Location's slug. * * ## EXAMPLES * * $ wp menu location remove primary-menu primary * Success: Removed location from menu. * * @subcommand remove */ public function remove( $args, $_ ) { list( $menu, $location ) = $args; $menu = wp_get_nav_menu_object( $menu ); if ( ! $menu || is_wp_error( $menu ) ) { WP_CLI::error( "Invalid menu." ); } $locations = get_nav_menu_locations(); if ( \WP_CLI\Utils\get_flag_value( $locations, $location ) != $menu->term_id ) { WP_CLI::error( "Menu isn't assigned to location." ); } $locations[ $location ] = 0; set_theme_mod( 'nav_menu_locations', $locations ); WP_CLI::success( "Removed location from menu." ); } } <?php use WP_CLI\Utils; /** * List, add, and delete items associated with a menu. * * ## EXAMPLES * * # Add an existing post to an existing menu * $ wp menu item add-post sidebar-menu 33 --title="Custom Test Post" * Success: Menu item added. * * # Create a new menu link item * $ wp menu item add-custom sidebar-menu Apple http://apple.com * Success: Menu item added. * * # Delete menu item * $ wp menu item delete 45 * Success: 1 menu item deleted. */ class Menu_Item_Command extends WP_CLI_Command { protected $obj_fields = array( 'db_id', 'type', 'title', 'link', 'position', ); /** * Get a list of items associated with a menu. * * ## OPTIONS * * <menu> * : The name, slug, or term ID for the menu. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - ids * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each menu item: * * * db_id * * type * * title * * link * * position * * These fields are optionally available: * * * menu_item_parent * * object_id * * object * * type * * type_label * * target * * attr_title * * description * * classes * * xfn * * ## EXAMPLES * * $ wp menu item list main-menu * +-------+-----------+-------------+---------------------------------+----------+ * | db_id | type | title | link | position | * +-------+-----------+-------------+---------------------------------+----------+ * | 5 | custom | Home | http://example.com | 1 | * | 6 | post_type | Sample Page | http://example.com/sample-page/ | 2 | * +-------+-----------+-------------+---------------------------------+----------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { $items = wp_get_nav_menu_items( $args[0] ); if ( false === $items || is_wp_error( $items ) ) { WP_CLI::error( "Invalid menu." ); } // Correct position inconsistency and // protected `url` param in WP-CLI $items = array_map( function( $item ) use ( $assoc_args ) { $item->position = $item->menu_order; $item->link = $item->url; return $item; }, $items ); if ( ! empty( $assoc_args['format'] ) && 'ids' == $assoc_args['format'] ) { $items = array_map( function( $item ) { return $item->db_id; }, $items ); } $formatter = $this->get_formatter( $assoc_args ); $formatter->display_items( $items ); } /** * Add a post as a menu item. * * ## OPTIONS * * <menu> * : The name, slug, or term ID for the menu. * * <post-id> * : Post ID to add to the menu. * * [--title=<title>] * : Set a custom title for the menu item. * * [--link=<link>] * : Set a custom url for the menu item. * * [--description=<description>] * : Set a custom description for the menu item. * * [--attr-title=<attr-title>] * : Set a custom title attribute for the menu item. * * [--target=<target>] * : Set a custom link target for the menu item. * * [--classes=<classes>] * : Set a custom link classes for the menu item. * * [--position=<position>] * : Specify the position of this menu item. * * [--parent-id=<parent-id>] * : Make this menu item a child of another menu item. * * [--porcelain] * : Output just the new menu item id. * * ## EXAMPLES * * $ wp menu item add-post sidebar-menu 33 --title="Custom Test Post" * Success: Menu item added. * * @subcommand add-post */ public function add_post( $args, $assoc_args ) { $assoc_args['object-id'] = $args[1]; unset( $args[1] ); $post = get_post( $assoc_args['object-id'] ); if ( ! $post ) { WP_CLI::error( "Invalid post." ); } $assoc_args['object'] = $post->post_type; $this->add_or_update_item( 'add', 'post_type', $args, $assoc_args ); } /** * Add a taxonomy term as a menu item. * * ## OPTIONS * * <menu> * : The name, slug, or term ID for the menu. * * <taxonomy> * : Taxonomy of the term to be added. * * <term-id> * : Term ID of the term to be added. * * [--title=<title>] * : Set a custom title for the menu item. * * [--link=<link>] * : Set a custom url for the menu item. * * [--description=<description>] * : Set a custom description for the menu item. * * [--attr-title=<attr-title>] * : Set a custom title attribute for the menu item. * * [--target=<target>] * : Set a custom link target for the menu item. * * [--classes=<classes>] * : Set a custom link classes for the menu item. * * [--position=<position>] * : Specify the position of this menu item. * * [--parent-id=<parent-id>] * : Make this menu item a child of another menu item. * * [--porcelain] * : Output just the new menu item id. * * ## EXAMPLES * * $ wp menu item add-term sidebar-menu post_tag 24 * Success: Menu item added. * * @subcommand add-term */ public function add_term( $args, $assoc_args ) { $assoc_args['object'] = $args[1]; unset( $args[1] ); $assoc_args['object-id'] = $args[2]; unset( $args[2] ); if ( ! get_term_by( 'id', $assoc_args['object-id'], $assoc_args['object'] ) ) { WP_CLI::error( "Invalid term." ); } $this->add_or_update_item( 'add', 'taxonomy', $args, $assoc_args ); } /** * Add a custom menu item. * * ## OPTIONS * * <menu> * : The name, slug, or term ID for the menu. * * <title> * : Title for the link. * * <link> * : Target URL for the link. * * [--description=<description>] * : Set a custom description for the menu item. * * [--attr-title=<attr-title>] * : Set a custom title attribute for the menu item. * * [--target=<target>] * : Set a custom link target for the menu item. * * [--classes=<classes>] * : Set a custom link classes for the menu item. * * [--position=<position>] * : Specify the position of this menu item. * * [--parent-id=<parent-id>] * : Make this menu item a child of another menu item. * * [--porcelain] * : Output just the new menu item id. * * ## EXAMPLES * * $ wp menu item add-custom sidebar-menu Apple http://apple.com * Success: Menu item added. * * @subcommand add-custom */ public function add_custom( $args, $assoc_args ) { $assoc_args['title'] = $args[1]; unset( $args[1] ); $assoc_args['link'] = $args[2]; unset( $args[2] ); $this->add_or_update_item( 'add', 'custom', $args, $assoc_args ); } /** * Update a menu item. * * ## OPTIONS * * <db-id> * : Database ID for the menu item. * * [--title=<title>] * : Set a custom title for the menu item. * * [--link=<link>] * : Set a custom url for the menu item. * * [--description=<description>] * : Set a custom description for the menu item. * * [--attr-title=<attr-title>] * : Set a custom title attribute for the menu item. * * [--target=<target>] * : Set a custom link target for the menu item. * * [--classes=<classes>] * : Set a custom link classes for the menu item. * * [--position=<position>] * : Specify the position of this menu item. * * [--parent-id=<parent-id>] * : Make this menu item a child of another menu item. * * ## EXAMPLES * * $ wp menu item update 45 --title=WordPress --link='http://wordpress.org' --target=_blank --position=2 * Success: Menu item updated. * * @subcommand update */ public function update( $args, $assoc_args ) { // Shuffle the position of these. $args[1] = $args[0]; $terms = get_the_terms( $args[1], 'nav_menu' ); if ( $terms && ! is_wp_error( $terms ) ) { $args[0] = (int)$terms[0]->term_id; } else { $args[0] = 0; } $type = get_post_meta( $args[1], '_menu_item_type', true ); $this->add_or_update_item( 'update', $type, $args, $assoc_args ); } /** * Delete one or more items from a menu. * * ## OPTIONS * * <db-id>... * : Database ID for the menu item(s). * * ## EXAMPLES * * $ wp menu item delete 45 * Success: 1 menu item deleted. * * @subcommand delete */ public function delete( $args, $_ ) { global $wpdb; $count = $errors = 0; foreach( $args as $arg ) { $parent_menu_id = (int) get_post_meta( $arg, '_menu_item_menu_item_parent', true ); $ret = wp_delete_post( $arg, true ); if ( ! $ret ) { WP_CLI::warning( "Couldn't delete menu item {$arg}." ); $errors++; } else if ( $parent_menu_id ) { $children = $wpdb->get_results( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_menu_item_menu_item_parent' AND meta_value=%s", (int) $arg ) ); if ( $children ) { $children_query = $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = %d WHERE meta_key = '_menu_item_menu_item_parent' AND meta_value=%s", $parent_menu_id, (int) $arg ); $wpdb->query( $children_query ); foreach( $children as $child ) { clean_post_cache( $child ); } } } if ( false != $ret ) { $count++; } } Utils\report_batch_operation_results( 'menu item', 'delete', count( $args ), $count, $errors ); } /** * Worker method to create new items or update existing ones. */ private function add_or_update_item( $method, $type, $args, $assoc_args ) { $menu = $args[0]; $menu_item_db_id = \WP_CLI\Utils\get_flag_value( $args, 1, 0 ); $menu = wp_get_nav_menu_object( $menu ); if ( ! $menu || is_wp_error( $menu ) ) { WP_CLI::error( "Invalid menu." ); } // `url` is protected in WP-CLI, so we use `link` instead $assoc_args['url'] = \WP_CLI\Utils\get_flag_value( $assoc_args, 'link' ); // Need to persist the menu item data. See https://core.trac.wordpress.org/ticket/28138 if ( 'update' == $method ) { $menu_item_obj = get_post( $menu_item_db_id ); $menu_item_obj = wp_setup_nav_menu_item( $menu_item_obj ); // Correct the menu position if this was the first item. See https://core.trac.wordpress.org/ticket/28140 $position = ( 0 === $menu_item_obj->menu_order ) ? 1 : $menu_item_obj->menu_order; $default_args = array( 'position' => $position, 'title' => $menu_item_obj->title, 'url' => $menu_item_obj->url, 'description' => $menu_item_obj->description, 'object' => $menu_item_obj->object, 'object-id' => $menu_item_obj->object_id, 'parent-id' => $menu_item_obj->menu_item_parent, 'attr-title' => $menu_item_obj->attr_title, 'target' => $menu_item_obj->target, 'classes' => implode( ' ', $menu_item_obj->classes ), // stored in the database as array 'xfn' => $menu_item_obj->xfn, 'status' => $menu_item_obj->post_status, ); } else { $default_args = array( 'position' => 0, 'title' => '', 'url' => '', 'description' => '', 'object' => '', 'object-id' => 0, 'parent-id' => 0, 'attr-title' => '', 'target' => '', 'classes' => '', 'xfn' => '', // Core oddly defaults to 'draft' for create, // and 'publish' for update // Easiest to always work with publish 'status' => 'publish', ); } $menu_item_args = array(); foreach( $default_args as $key => $default_value ) { // wp_update_nav_menu_item() has a weird argument prefix $new_key = 'menu-item-' . $key; $menu_item_args[ $new_key ] = \WP_CLI\Utils\get_flag_value( $assoc_args, $key, $default_value ); } $menu_item_args['menu-item-type'] = $type; $ret = wp_update_nav_menu_item( $menu->term_id, $menu_item_db_id, $menu_item_args ); if ( is_wp_error( $ret ) ) { WP_CLI::error( $ret->get_error_message() ); } else if ( ! $ret ) { if ( 'add' == $method ) { WP_CLI::error( "Couldn't add menu item." ); } else if ( 'update' == $method ) { WP_CLI::error( "Couldn't update menu item." ); } } else { /** * Set the menu * * wp_update_nav_menu_item() *should* take care of this, but * depends on wp_insert_post()'s "tax_input" argument, which * is ignored if the user can't edit the taxonomy * * @see https://core.trac.wordpress.org/ticket/27113 */ if ( ! is_object_in_term( $ret, 'nav_menu', (int) $menu->term_id ) ) { wp_set_object_terms( $ret, array( (int)$menu->term_id ), 'nav_menu' ); } if ( 'add' == $method && ! empty( $assoc_args['porcelain'] ) ) { WP_CLI::line( $ret ); } else { if ( 'add' == $method ) { WP_CLI::success( "Menu item added." ); } else if ( 'update' == $method ) { WP_CLI::success( "Menu item updated." ); } } } } protected function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->obj_fields ); } } <?php /** * Manage term custom fields. * * ## EXAMPLES * * # Set term meta * $ wp term meta set 123 bio "Mary is a WordPress developer." * Success: Updated custom field 'bio'. * * # Get term meta * $ wp term meta get 123 bio * Mary is a WordPress developer. * * # Update term meta * $ wp term meta update 123 bio "Mary is an awesome WordPress developer." * Success: Updated custom field 'bio'. * * # Delete term meta * $ wp term meta delete 123 bio * Success: Deleted custom field. */ class Term_Meta_Command extends \WP_CLI\CommandWithMeta { protected $meta_type = 'term'; /** * Check that the term ID exists * * @param int */ protected function check_object_id( $object_id ) { $term = get_term( $object_id ); if ( ! $term ) { WP_CLI::error( "Could not find the term with ID {$object_id}." ); } return $term->term_id; } } <?php /** * Manage post custom fields. * * ## EXAMPLES * * # Set post meta * $ wp post meta set 123 _wp_page_template about.php * Success: Updated custom field '_wp_page_template'. * * # Get post meta * $ wp post meta get 123 _wp_page_template * about.php * * # Update post meta * $ wp post meta update 123 _wp_page_template contact.php * Success: Updated custom field '_wp_page_template'. * * # Delete post meta * $ wp post meta delete 123 _wp_page_template * Success: Deleted custom field. */ class Post_Meta_Command extends \WP_CLI\CommandWithMeta { protected $meta_type = 'post'; /** * Check that the post ID exists * * @param int */ protected function check_object_id( $object_id ) { $fetcher = new \WP_CLI\Fetchers\Post; $post = $fetcher->get_check( $object_id ); return $post->ID; } } <?php /** * Performs site-wide operations on a multisite install. * * ## EXAMPLES * * # Create site * $ wp site create --slug=example * Success: Site 3 created: www.example.com/example/ * * # Output a simple list of site URLs * $ wp site list --field=url * http://www.example.com/ * http://www.example.com/subdir/ * * # Delete site * $ wp site delete 123 * Are you sure you want to delete the 'http://www.example.com/example' site? [y/n] y * Success: The site at 'http://www.example.com/example' was deleted. * * @package wp-cli */ class Site_Command extends \WP_CLI\CommandWithDBObject { protected $obj_type = 'site'; protected $obj_id_key = 'blog_id'; public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\Site; } /** * Delete comments. */ private function _empty_comments() { global $wpdb; // Empty comments and comment cache $comment_ids = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->comments" ); foreach ( $comment_ids as $comment_id ) { wp_cache_delete( $comment_id, 'comment' ); wp_cache_delete( $comment_id, 'comment_meta' ); } $wpdb->query( "TRUNCATE $wpdb->comments" ); $wpdb->query( "TRUNCATE $wpdb->commentmeta" ); } /** * Delete all posts. */ private function _empty_posts() { global $wpdb; // Empty posts and post cache $posts_query = "SELECT ID FROM $wpdb->posts"; $posts = new WP_CLI\Iterators\Query( $posts_query, 10000 ); $taxonomies = get_taxonomies(); while ( $posts->valid() ) { $post_id = $posts->current()->ID; wp_cache_delete( $post_id, 'posts' ); wp_cache_delete( $post_id, 'post_meta' ); foreach ( $taxonomies as $taxonomy ) wp_cache_delete( $post_id, "{$taxonomy}_relationships" ); wp_cache_delete( $wpdb->blogid . '-' . $post_id, 'global-posts' ); $posts->next(); } $wpdb->query( "TRUNCATE $wpdb->posts" ); $wpdb->query( "TRUNCATE $wpdb->postmeta" ); } /** * Delete terms, taxonomies, and tax relationships. */ private function _empty_taxonomies() { global $wpdb; // Empty taxonomies and terms $terms = $wpdb->get_results( "SELECT term_id, taxonomy FROM $wpdb->term_taxonomy" ); $ids = array(); $taxonomies = array(); foreach ( (array) $terms as $term ) { $taxonomies[] = $term->taxonomy; $ids[] = $term->term_id; wp_cache_delete( $term->term_id, $term->taxonomy ); } $taxonomies = array_unique( $taxonomies ); $cleaned = array(); foreach ( $taxonomies as $taxonomy ) { if ( isset( $cleaned[$taxonomy] ) ) continue; $cleaned[$taxonomy] = true; wp_cache_delete( 'all_ids', $taxonomy ); wp_cache_delete( 'get', $taxonomy ); delete_option( "{$taxonomy}_children" ); } $wpdb->query( "TRUNCATE $wpdb->terms" ); $wpdb->query( "TRUNCATE $wpdb->term_taxonomy" ); $wpdb->query( "TRUNCATE $wpdb->term_relationships" ); if ( ! empty( $wpdb->termmeta ) ) { $wpdb->query( "TRUNCATE $wpdb->termmeta" ); } } /** * Insert default terms. */ private function _insert_default_terms() { global $wpdb; // Default category $cat_name = __( 'Uncategorized' ); /* translators: Default category slug */ $cat_slug = sanitize_title( _x( 'Uncategorized', 'Default category slug' ) ); if ( global_terms_enabled() ) { $cat_id = $wpdb->get_var( $wpdb->prepare( "SELECT cat_ID FROM {$wpdb->sitecategories} WHERE category_nicename = %s", $cat_slug ) ); if ( $cat_id == null ) { $wpdb->insert( $wpdb->sitecategories, array('cat_ID' => 0, 'cat_name' => $cat_name, 'category_nicename' => $cat_slug, 'last_updated' => current_time('mysql', true)) ); $cat_id = $wpdb->insert_id; } update_option('default_category', $cat_id); } else { $cat_id = 1; } $wpdb->insert( $wpdb->terms, array('term_id' => $cat_id, 'name' => $cat_name, 'slug' => $cat_slug, 'term_group' => 0) ); $wpdb->insert( $wpdb->term_taxonomy, array('term_id' => $cat_id, 'taxonomy' => 'category', 'description' => '', 'parent' => 0, 'count' => 1)); } /** * Empty a site of its content (posts, comments, terms, and meta). * * Truncates posts, comments, and terms tables to empty a site of its * content. Doesn't affect site configuration (options) or users. * * If running a persistent object cache, make sure to flush the cache * after emptying the site, as the cache values will be invalid otherwise. * * To also empty custom database tables, you'll need to hook into command * execution: * * ``` * WP_CLI::add_hook( 'after_invoke:site empty', function(){ * global $wpdb; * foreach( array( 'p2p', 'p2pmeta' ) as $table ) { * $table = $wpdb->$table; * $wpdb->query( "TRUNCATE $table" ); * } * }); * ``` * * ## OPTIONS * * [--uploads] * : Also delete *all* files in the site's uploads directory. * * [--yes] * : Proceed to empty the site without a confirmation prompt. * * ## EXAMPLES * * $ wp site empty * Are you sure you want to empty the site at http://www.example.com of all posts, comments, and terms? [y/n] y * Success: The site at 'http://www.example.com' was emptied. * * @subcommand empty */ public function _empty( $args, $assoc_args ) { $upload_message = ''; if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'uploads' ) ) { $upload_message = ', and delete its uploads directory'; } WP_CLI::confirm( "Are you sure you want to empty the site at '" . site_url() . "' of all posts, comments, and terms" . $upload_message . "?", $assoc_args ); $this->_empty_posts(); $this->_empty_comments(); $this->_empty_taxonomies(); $this->_insert_default_terms(); if ( ! empty( $upload_message ) ) { $upload_dir = wp_upload_dir(); $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $upload_dir['basedir'], RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ); $files_to_unlink = $directories_to_delete = array(); foreach ( $files as $fileinfo ) { $realpath = $fileinfo->getRealPath(); // Don't clobber subsites when operating on the main site if ( is_main_site() && false !== stripos( $realpath, '/sites/' ) ) { continue; } if ( $fileinfo->isDir() ) { $directories_to_delete[] = $realpath; } else { $files_to_unlink[] = $realpath; } } foreach( $files_to_unlink as $file ) { unlink( $file ); } foreach( $directories_to_delete as $directory ) { rmdir( $directory ); } rmdir( $upload_dir['basedir'] ); } WP_CLI::success( "The site at '" . site_url() . "' was emptied." ); } /** * Delete a site in a multisite install. * * ## OPTIONS * * [<site-id>] * : The id of the site to delete. If not provided, you must set the --slug parameter. * * [--slug=<slug>] * : Path of the blog to be deleted. Subdomain on subdomain installs, directory on subdirectory installs. * * [--yes] * : Answer yes to the confirmation message. * * [--keep-tables] * : Delete the blog from the list, but don't drop it's tables. * * ## EXAMPLES * * $ wp site delete 123 * Are you sure you want to delete the http://www.example.com/example site? [y/n] y * Success: The site at 'http://www.example.com/example' was deleted. */ function delete( $args, $assoc_args ) { if ( !is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } if ( isset( $assoc_args['slug'] ) ) { $blog = get_blog_details( trim( $assoc_args['slug'], '/' ) ); } else { if ( empty( $args ) ) { WP_CLI::error( 'Need to specify a blog id.' ); } $blog_id = $args[0]; if ( is_main_site( $blog_id ) ) { WP_CLI::error( 'You cannot delete the root site.' ); } $blog = get_blog_details( $blog_id ); } if ( ! $blog ) { WP_CLI::error( 'Site not found.' ); } $site_url = trailingslashit( $blog->siteurl ); WP_CLI::confirm( "Are you sure you want to delete the '$site_url' site?", $assoc_args ); wpmu_delete_blog( $blog->blog_id, ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'keep-tables' ) ); WP_CLI::success( "The site at '$site_url' was deleted." ); } /** * Create a site in a multisite install. * * ## OPTIONS * * --slug=<slug> * : Path for the new site. Subdomain on subdomain installs, directory on subdirectory installs. * * [--title=<title>] * : Title of the new site. Default: prettified slug. * * [--email=<email>] * : Email for Admin user. User will be created if none exists. Assignement to Super Admin if not included. * * [--network_id=<network-id>] * : Network to associate new site with. Defaults to current network (typically 1). * * [--private] * : If set, the new site will be non-public (not indexed) * * [--porcelain] * : If set, only the site id will be output on success. * * ## EXAMPLES * * $ wp site create --slug=example * Success: Site 3 created: http://www.example.com/example/ */ public function create( $_, $assoc_args ) { if ( !is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } global $wpdb, $current_site; $base = $assoc_args['slug']; $title = \WP_CLI\Utils\get_flag_value( $assoc_args, 'title', ucfirst( $base ) ); $email = empty( $assoc_args['email'] ) ? '' : $assoc_args['email']; // Network if ( !empty( $assoc_args['network_id'] ) ) { $network = $this->_get_network( $assoc_args['network_id'] ); if ( false === $network ) { WP_CLI::error( sprintf( 'Network with id %d does not exist.', $assoc_args['network_id'] ) ); } } else { $network = $current_site; } $public = ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'private' ); // Sanitize if ( preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) { $base = strtolower( $base ); } // If not a subdomain install, make sure the domain isn't a reserved word if ( !is_subdomain_install() ) { $subdirectory_reserved_names = apply_filters( 'subdirectory_reserved_names', array( 'page', 'comments', 'blog', 'files', 'feed' ) ); if ( in_array( $base, $subdirectory_reserved_names ) ) { WP_CLI::error( 'The following words are reserved and cannot be used as blog names: ' . implode( ', ', $subdirectory_reserved_names ) ); } } // Check for valid email, if not, use the first Super Admin found // Probably a more efficient way to do this so we dont query for the // User twice if super admin $email = sanitize_email( $email ); if ( empty( $email ) || !is_email( $email ) ) { $super_admins = get_super_admins(); $email = ''; if ( !empty( $super_admins ) && is_array( $super_admins ) ) { // Just get the first one $super_login = $super_admins[0]; $super_user = get_user_by( 'login', $super_login ); if ( $super_user ) { $email = $super_user->user_email; } } } if ( is_subdomain_install() ) { $newdomain = $base . '.' . preg_replace( '|^www\.|', '', $current_site->domain ); $path = $current_site->path; $url = $newdomain; } else { $newdomain = $current_site->domain; $path = $current_site->path . $base . '/'; $url = $newdomain . $path; } $user_id = email_exists( $email ); if ( !$user_id ) { // Create a new user with a random password $password = wp_generate_password( 12, false ); $user_id = wpmu_create_user( $base, $password, $email ); if ( false == $user_id ) { WP_CLI::error( "Can't create user." ); } else { wp_new_user_notification( $user_id, $password ); } } $wpdb->hide_errors(); $title = wp_slash( $title ); $id = wpmu_create_blog( $newdomain, $path, $title, $user_id, array( 'public' => $public ), $network->id ); $wpdb->show_errors(); if ( !is_wp_error( $id ) ) { if ( !is_super_admin( $user_id ) && !get_user_option( 'primary_blog', $user_id ) ) { update_user_option( $user_id, 'primary_blog', $id, true ); } // Prevent mailing admins of new sites // @TODO argument to pass in? // $content_mail = sprintf(__( "New site created by WP Command Line Interface\n\nAddress: %2s\nName: %3s"), get_site_url($id), stripslashes($title)); // wp_mail(get_site_option('admin_email'), sprintf(__('[%s] New Site Created'), $current_site->site_name), $content_mail, 'From: "Site Admin" <'.get_site_option( 'admin_email').'>'); } else { WP_CLI::error( $id->get_error_message() ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { WP_CLI::line( $id ); } else { $site_url = trailingslashit( get_site_url( $id ) ); WP_CLI::success( "Site $id created: $site_url" ); } } /** * Get network data for a given id. * * @param int $network_id * @return bool|array False if no network found with given id, array otherwise */ private function _get_network( $network_id ) { global $wpdb; // Load network data $networks = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->site WHERE id = %d", $network_id ) ); if ( !empty( $networks ) ) { // Only care about domain and path which are set here return $networks[0]; } return false; } /** * List all sites in a multisite install. * * ## OPTIONS * * [--network=<id>] * : The network to which the sites belong. * * [--<field>=<value>] * : Filter by one or more fields (see "Available Fields" section). However, * 'url' isn't an available filter, because it's created from domain + path. * * [--site__in=<value>] * : Only list the sites with these blog_id values (comma-separated). * * [--field=<field>] * : Prints the value of a single field for each site. * * [--fields=<fields>] * : Comma-separated list of fields to show. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - count * - ids * - json * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each site: * * * blog_id * * url * * last_updated * * registered * * These fields are optionally available: * * * site_id * * domain * * path * * public * * archived * * mature * * spam * * deleted * * lang_id * * ## EXAMPLES * * # Output a simple list of site URLs * $ wp site list --field=url * http://www.example.com/ * http://www.example.com/subdir/ * * @subcommand list */ public function list_( $_, $assoc_args ) { if ( !is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } global $wpdb; if ( isset( $assoc_args['fields'] ) ) { $assoc_args['fields'] = preg_split( '/,[ \t]*/', $assoc_args['fields'] ); } $defaults = array( 'format' => 'table', 'fields' => array( 'blog_id', 'url', 'last_updated', 'registered' ), ); $assoc_args = array_merge( $defaults, $assoc_args ); $where = array(); $append = ''; $site_cols = array( 'blog_id', 'last_updated', 'registered', 'site_id', 'domain', 'path', 'public', 'archived', 'mature', 'spam', 'deleted', 'lang_id' ); foreach( $site_cols as $col ) { if ( isset( $assoc_args[ $col ] ) ) { $where[ $col ] = $assoc_args[ $col ]; } } if ( isset( $assoc_args['site__in'] ) ) { $where['blog_id'] = explode( ',', $assoc_args['site__in'] ); $append = "ORDER BY FIELD( blog_id, " . implode( ',', array_map( 'intval', $where['blog_id'] ) ) . " )"; } if ( isset( $assoc_args['network'] ) ) { $where['site_id'] = $assoc_args['network']; } $iterator_args = array( 'table' => $wpdb->blogs, 'where' => $where, 'append' => $append, ); $it = new \WP_CLI\Iterators\Table( $iterator_args ); $it = \WP_CLI\Utils\iterator_map( $it, function( $blog ) { $blog->url = trailingslashit( get_site_url( $blog->blog_id ) ); return $blog; } ); if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) { $sites = iterator_to_array( $it ); $ids = wp_list_pluck( $sites, 'blog_id' ); $formatter = new \WP_CLI\Formatter( $assoc_args, null, 'site' ); $formatter->display_items( $ids ); } else { $formatter = new \WP_CLI\Formatter( $assoc_args, null, 'site' ); $formatter->display_items( $it ); } } /** * Archive one or more sites. * * ## OPTIONS * * <id>... * : One or more IDs of sites to archive. * * ## EXAMPLES * * $ wp site archive 123 * Success: Site 123 archived. */ public function archive( $args ) { $this->update_site_status( $args, 'archived', 1 ); } /** * Unarchive one or more sites. * * ## OPTIONS * * <id>... * : One or more IDs of sites to unarchive. * * ## EXAMPLES * * $ wp site unarchive 123 * Success: Site 123 unarchived. */ public function unarchive( $args ) { $this->update_site_status( $args, 'archived', 0 ); } /** * Activate one or more sites. * * ## OPTIONS * * <id>... * : One or more IDs of sites to activate. * * ## EXAMPLES * * $ wp site activate 123 * Success: Site 123 activated. */ public function activate( $args ) { $this->update_site_status( $args, 'deleted', 0 ); } /** * Deactivate one or more sites. * * ## OPTIONS * * <id>... * : One or more IDs of sites to deactivate. * * ## EXAMPLES * * $ wp site deactivate 123 * Success: Site 123 deactivated. */ public function deactivate( $args ) { $this->update_site_status( $args, 'deleted', 1 ); } /** * Mark one or more sites as spam. * * ## OPTIONS * * <id>... * : One or more IDs of sites to be marked as spam. * * ## EXAMPLES * * $ wp site spam 123 * Success: Site 123 marked as spam. */ public function spam( $args ) { $this->update_site_status( $args, 'spam', 1 ); } /** * Remove one or more sites from spam. * * ## OPTIONS * * <id>... * : One or more IDs of sites to remove from spam. * * ## EXAMPLES * * $ wp site unspam 123 * Success: Site 123 removed from spam. * * @subcommand unspam */ public function unspam( $args ) { $this->update_site_status( $args, 'spam', 0 ); } /** * Set one or more sites as mature. * * ## OPTIONS * * <id>... * : One or more IDs of sites to set as mature. * * ## EXAMPLES * * $ wp site mature 123 * Success: Site 123 marked as mature. */ public function mature( $args ) { $this->update_site_status( $args, 'mature', 1 ); } /** * Set one or more sites as unmature. * * ## OPTIONS * * <id>... * : One or more IDs of sites to set as unmature. * * ## EXAMPLES * * $ wp site general 123 * Success: Site 123 marked as unmature. */ public function unmature( $args ) { $this->update_site_status( $args, 'mature', 0 ); } /** * Set one or more sites as public. * * ## OPTIONS * * <id>... * : One or more IDs of sites to set as public. * * ## EXAMPLES * * $ wp site public 123 * Success: Site 123 marked as public. * * @subcommand public */ public function set_public( $args ) { $this->update_site_status( $args, 'public', 1 ); } /** * Set one or more sites as private. * * ## OPTIONS * * <id>... * : One or more IDs of sites to set as private. * * ## EXAMPLES * * $ wp site private 123 * Success: Site 123 marked as private. * * @subcommand private */ public function set_private( $args ) { $this->update_site_status( $args, 'public', 0 ); } private function update_site_status( $ids, $pref, $value ) { if ( $pref == 'archived' && $value == 1 ) { $action = 'archived'; } else if ( $pref == 'archived' && $value == 0) { $action = 'unarchived'; } else if ( $pref == 'deleted' && $value == 1 ) { $action = 'deactivated'; } else if ( $pref == 'deleted' && $value == 0 ) { $action = 'activated'; } else if ( $pref == 'spam' && $value == 1 ) { $action = 'marked as spam'; } else if ( $pref == 'spam' && $value == 0 ) { $action = 'removed from spam'; } else if ( $pref == 'public' && $value == 1 ) { $action = 'marked as public'; } else if ( $pref == 'public' && $value == 0 ) { $action = 'marked as private'; } else if ( $pref == 'mature' && $value == 1 ) { $action = 'marked as mature'; } else if ( $pref == 'mature' && $value == 0 ) { $action = 'marked as unmature'; } foreach ( $ids as $site_id ) { $site = $this->fetcher->get_check( $site_id ); if ( is_main_site( $site->blog_id ) ) { WP_CLI::warning( "You are not allowed to change the main site." ); continue; } $old_value = get_blog_status( $site->blog_id, $pref ); if ( $value == $old_value ) { WP_CLI::warning( "Site {$site->blog_id} already {$action}." ); continue; } update_blog_status( $site->blog_id, $pref, $value ); WP_CLI::success( "Site {$site->blog_id} {$action}." ); } } } <?php /** * Manage network custom fields. * * ## EXAMPLES * * # Get a list of super-admins * $ wp network meta get 1 site_admins * array ( * 0 => 'supervisor', * ) */ class Network_Meta_Command extends \WP_CLI\CommandWithMeta { protected $meta_type = 'site'; } <?php /** * Manage user custom fields. * * ## EXAMPLES * * # Add user meta * $ wp user meta add 123 bio "Mary is an WordPress developer." * Success: Added custom field. * * # List user meta * $ wp user meta list 123 --keys=nickname,description,wp_capabilities * +---------+-----------------+--------------------------------+ * | user_id | meta_key | meta_value | * +---------+-----------------+--------------------------------+ * | 123 | nickname | supervisor | * | 123 | description | Mary is a WordPress developer. | * | 123 | wp_capabilities | {"administrator":true} | * +---------+-----------------+--------------------------------+ * * # Update user meta * $ wp user meta update 123 bio "Mary is an awesome WordPress developer." * Success: Updated custom field 'bio'. * * # Delete user meta * $ wp user meta delete 123 bio * Success: Deleted custom field. */ class User_Meta_Command extends \WP_CLI\CommandWithMeta { protected $meta_type = 'user'; public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\User; } /** * List all metadata associated with a user. * * ## OPTIONS * * <user> * : The user login, user email, or user ID of the user to get metadata for. * * [--keys=<keys>] * : Limit output to metadata of specific keys. * * [--fields=<fields>] * : Limit the output to specific row fields. Defaults to id,meta_key,meta_value. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - count * - yaml * --- * * [--orderby=<fields>] * : Set orderby which field. * --- * default: id * options: * - id * - meta_key * - meta_value * --- * * [--order=<order>] * : Set ascending or descending order. * --- * default: asc * options: * - asc * - desc * --- * * ## EXAMPLES * * # List user meta * $ wp user meta list 123 --keys=nickname,description,wp_capabilities * +---------+-----------------+--------------------------------+ * | user_id | meta_key | meta_value | * +---------+-----------------+--------------------------------+ * | 123 | nickname | supervisor | * | 123 | description | Mary is a WordPress developer. | * | 123 | wp_capabilities | {"administrator":true} | * +---------+-----------------+--------------------------------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { $args = $this->replace_login_with_user_id( $args ); parent::list_( $args, $assoc_args ); } /** * Get meta field value. * * ## OPTIONS * * <user> * : The user login, user email, or user ID of the user to get metadata for. * * <key> * : The metadata key. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Get user meta * $ wp user meta get 123 bio * Mary is an WordPress developer. */ public function get( $args, $assoc_args ) { $args = $this->replace_login_with_user_id( $args ); parent::get( $args, $assoc_args ); } /** * Delete a meta field. * * ## OPTIONS * * <user> * : The user login, user email, or user ID of the user to delete metadata from. * * <key> * : The metadata key. * * [<value>] * : The value to delete. If omitted, all rows with key will deleted. * * ## EXAMPLES * * # Delete user meta * $ wp user meta delete 123 bio * Success: Deleted custom field. */ public function delete( $args, $assoc_args ) { $args = $this->replace_login_with_user_id( $args ); parent::delete( $args, $assoc_args ); } /** * Add a meta field. * * ## OPTIONS * * <user> * : The user login, user email, or user ID of the user to add metadata for. * * <key> * : The metadata key. * * <value> * : The new metadata value. * * [--format=<format>] * : The serialization format for the value. Default is plaintext. * * ## EXAMPLES * * # Add user meta * $ wp user meta add 123 bio "Mary is an WordPress developer." * Success: Added custom field. */ public function add( $args, $assoc_args ) { $args = $this->replace_login_with_user_id( $args ); parent::add( $args, $assoc_args ); } /** * Update a meta field. * * ## OPTIONS * * <user> * : The user login, user email, or user ID of the user to update metadata for. * * <key> * : The metadata key. * * <value> * : The new metadata value. * * [--format=<format>] * : The serialization format for the value. Default is plaintext. * * ## EXAMPLES * * # Update user meta * $ wp user meta update 123 bio "Mary is an awesome WordPress developer." * Success: Updated custom field 'bio'. * * @alias set */ public function update( $args, $assoc_args ) { $args = $this->replace_login_with_user_id( $args ); parent::update( $args, $assoc_args ); } /** * Replace user_login value with user ID * user meta is a special case that also supports user_login * * @param array * @return array */ private function replace_login_with_user_id( $args ) { $user = $this->fetcher->get_check( $args[0] ); $args[0] = $user->ID; return $args; } } <?php /** * Manage a user's sessions. * * ## EXAMPLES * * # List a user's sessions. * $ wp user session list admin@example.com --format=csv * login_time,expiration_time,ip,ua * "2016-01-01 12:34:56","2016-02-01 12:34:56",127.0.0.1,"Mozilla/5.0..." * * # Destroy the most recent session of the given user. * $ wp user session destroy admin * Success: Destroyed session. 3 sessions remaining. * * @package wp-cli */ class User_Session_Command extends WP_CLI_Command { private $fields = array( 'token', 'login_time', 'expiration_time', 'ip', 'ua', ); public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\User; } /** * Destroy a session for the given user. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * [<token>] * : The token of the session to destroy. Defaults to the most recently created session. * * [--all] * : Destroy all of the user's sessions. * * ## EXAMPLES * * # Destroy the most recent session of the given user. * $ wp user session destroy admin * Success: Destroyed session. 3 sessions remaining. * * # Destroy a specific session of the given user. * $ wp user session destroy admin e073ad8540a9c2... * Success: Destroyed session. 2 sessions remaining. * * # Destroy all the sessions of the given user. * $ wp user session destroy admin --all * Success: Destroyed all sessions. * * # Destroy all sessions for all users. * $ wp user list --field=ID | xargs -n 1 wp user session destroy --all * Success: Destroyed all sessions. * Success: Destroyed all sessions. */ public function destroy( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); $token = \WP_CLI\Utils\get_flag_value( $args, 1, null ); $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); $manager = WP_Session_Tokens::get_instance( $user->ID ); if ( $token && $all ) { WP_CLI::error( 'The --all flag cannot be specified along with a session token.' ); } if ( $all ) { $manager->destroy_all(); WP_CLI::success( 'Destroyed all sessions.' ); return; } $sessions = $this->get_all_sessions( $manager ); if ( ! $token ) { if ( empty( $sessions ) ) { WP_CLI::success( 'No sessions to destroy.' ); } $last = end( $sessions ); $token = $last['token']; } if ( ! isset( $sessions[ $token ] ) ) { WP_CLI::error( 'Session not found.' ); } $this->destroy_session( $manager, $token ); $remaining = count( $manager->get_all() ); WP_CLI::success( sprintf( 'Destroyed session. %s remaining.', $remaining ) ); } /** * List sessions for the given user. * * Note: The `token` field does not return the actual token, but a hash of * it. The real token is not persisted and can only be found in the * corresponding cookies on the client side. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * [--fields=<fields>] * : Limit the output to specific fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * - count * - ids * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each session: * * * token * * login_time * * expiration_time * * ip * * ua * * These fields are optionally available: * * * expiration * * login * * ## EXAMPLES * * # List a user's sessions. * $ wp user session list admin@example.com --format=csv * login_time,expiration_time,ip,ua * "2016-01-01 12:34:56","2016-02-01 12:34:56",127.0.0.1,"Mozilla/5.0..." * * @subcommand list */ public function list_( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); $formatter = $this->get_formatter( $assoc_args ); $manager = WP_Session_Tokens::get_instance( $user->ID ); $sessions = $this->get_all_sessions( $manager ); if ( 'ids' == $formatter->format ) { echo implode( ' ', array_keys( $sessions ) ); } else { $formatter->display_items( $sessions ); } } protected function get_all_sessions( WP_Session_Tokens $manager ) { // Make the private session data accessible to WP-CLI $get_sessions = new ReflectionMethod( $manager, 'get_sessions' ); $get_sessions->setAccessible( true ); $sessions = $get_sessions->invoke( $manager ); array_walk( $sessions, function( & $session, $token ) { $session['token'] = $token; $session['login_time'] = date( 'Y-m-d H:i:s', $session['login'] ); $session['expiration_time'] = date( 'Y-m-d H:i:s', $session['expiration'] ); } ); return $sessions; } protected function destroy_session( WP_Session_Tokens $manager, $token ) { $update_session = new ReflectionMethod( $manager, 'update_session' ); $update_session->setAccessible( true ); return $update_session->invoke( $manager, $token, null ); } private function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->fields ); } } <?php use WP_CLI\Entity\RecursiveDataStructureTraverser; use WP_CLI\Utils; /** * Manage site options in a multisite install. * * ## EXAMPLES * * # Get site registration * $ wp site option get registration * none * * # Add site option * $ wp site option add my_option foobar * Success: Added 'my_option' site option. * * # Update site option * $ wp site option update my_option '{"foo": "bar"}' --format=json * Success: Updated 'my_option' site option. * * # Delete site option * $ wp site option delete my_option * Success: Deleted 'my_option' site option. */ class Site_Option_Command extends WP_CLI_Command { /** * Get a site option. * * ## OPTIONS * * <key> * : Key for the site option. * * [--format=<format>] * : Get value in a particular format. * --- * default: var_export * options: * - var_export * - json * - yaml * --- * * ## EXAMPLES * * # Get site upload filetypes * $ wp site option get upload_filetypes * jpg jpeg png gif mov avi mpg */ public function get( $args, $assoc_args ) { list( $key ) = $args; $value = get_site_option( $key ); if ( false === $value ) { WP_CLI::halt(1); } WP_CLI::print_value( $value, $assoc_args ); } /** * Add a site option. * * ## OPTIONS * * <key> * : The name of the site option to add. * * [<value>] * : The value of the site option to add. If ommited, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- * * ## EXAMPLES * * # Create a site option by reading a JSON file * $ wp site option add my_option --format=json < config.json * Success: Added 'my_option' site option. */ public function add( $args, $assoc_args ) { $key = $args[0]; $value = WP_CLI::get_value_from_arg_or_stdin( $args, 1 ); $value = WP_CLI::read_value( $value, $assoc_args ); if ( ! add_site_option( $key, $value ) ) { WP_CLI::error( "Could not add site option '$key'. Does it already exist?" ); } else { WP_CLI::success( "Added '$key' site option." ); } } /** * List site options. * * ## OPTIONS * * [--search=<pattern>] * : Use wildcards ( * and ? ) to match option name. * * [--site_id=<id>] * : Limit options to those of a particular site id. * * [--field=<field>] * : Prints the value of a single field. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : The serialization format for the value. total_bytes displays the total size of matching options in bytes. * --- * default: table * options: * - table * - json * - csv * - count * - yaml * - total_bytes * --- * * ## AVAILABLE FIELDS * * This field will be displayed by default for each matching option: * * * meta_key * * meta_value * * These fields are optionally available: * * * meta_id * * site_id * * size_bytes * * ## EXAMPLES * * # List all site options begining with "i2f_" * $ wp site option list --search="i2f_*" * +-------------+--------------+ * | meta_key | meta_value | * +-------------+--------------+ * | i2f_version | 0.1.0 | * +-------------+--------------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { global $wpdb; $pattern = '%'; $fields = array( 'meta_key', 'meta_value' ); $size_query = ",LENGTH(meta_value) AS `size_bytes`"; if ( isset( $assoc_args['search'] ) ) { $pattern = self::esc_like( $assoc_args['search'] ); // substitute wildcards $pattern = str_replace( '*', '%', $pattern ); $pattern = str_replace( '?', '_', $pattern ); } if ( isset( $assoc_args['fields'] ) ) { $fields = explode( ',', $assoc_args['fields'] ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'total_bytes' ) { $fields = array( 'size_bytes' ); $size_query = ",SUM(LENGTH(meta_value)) AS `size_bytes`"; } $query = $wpdb->prepare( "SELECT `meta_id`, `site_id`, `meta_key`,`meta_value`" . $size_query . " FROM `$wpdb->sitemeta` WHERE `meta_key` LIKE %s", $pattern ); if ( $site_id = Utils\get_flag_value( $assoc_args, 'site_id' ) ) { $query .= $wpdb->prepare( ' AND site_id=%d', $site_id ); } $results = $wpdb->get_results( $query ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'total_bytes' ) { WP_CLI::line( $results[0]->size_bytes ); } else { $formatter = new \WP_CLI\Formatter( $assoc_args, $fields ); $formatter->display_items( $results ); } } /** * Update a site option. * * ## OPTIONS * * <key> * : The name of the site option to update. * * [<value>] * : The new value. If ommited, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- * * ## EXAMPLES * * # Update a site option by reading from a file * $ wp site option update my_option < value.txt * Success: Updated 'my_option' site option. * * @alias set */ public function update( $args, $assoc_args ) { $key = $args[0]; $value = WP_CLI::get_value_from_arg_or_stdin( $args, 1 ); $value = WP_CLI::read_value( $value, $assoc_args ); $value = sanitize_option( $key, $value ); $old_value = sanitize_option( $key, get_site_option( $key ) ); if ( $value === $old_value ) { WP_CLI::success( "Value passed for '$key' site option is unchanged." ); } else { if ( update_site_option( $key, $value ) ) { WP_CLI::success( "Updated '$key' site option." ); } else { WP_CLI::error( "Could not update site option '$key'." ); } } } /** * Delete a site option. * * ## OPTIONS * * <key> * : Key for the site option. * * ## EXAMPLES * * $ wp site option delete my_option * Success: Deleted 'my_option' site option. */ public function delete( $args ) { list( $key ) = $args; if ( ! delete_site_option( $key ) ) { WP_CLI::error( "Could not delete '$key' site option. Does it exist?" ); } else { WP_CLI::success( "Deleted '$key' site option." ); } } /** * Get a nested value from an option. * * ## OPTIONS * * <key> * : The option name. * * <key-path>... * : The name(s) of the keys within the value to locate the value to pluck. * * [--format=<format>] * : The output format of the value. * --- * default: plaintext * options: * - plaintext * - json * - yaml */ public function pluck( $args, $assoc_args ) { list( $key ) = $args; $value = get_site_option( $key ); if ( false === $value ) { WP_CLI::halt( 1 ); } $key_path = array_map( function( $key ) { if ( is_numeric( $key ) && ( $key === (string) intval( $key ) ) ) { return (int) $key; } return $key; }, array_slice( $args, 1 ) ); $traverser = new RecursiveDataStructureTraverser( $value ); try { $value = $traverser->get( $key_path ); } catch ( \Exception $e ) { die( 1 ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Update a nested value in an option. * * ## OPTIONS * * <action> * : Patch action to perform. * --- * options: * - insert * - update * - delete * --- * * <key> * : The option name. * * <key-path>... * : The name(s) of the keys within the value to locate the value to patch. * * [<value>] * : The new value. If omitted, the value is read from STDIN. * * [--format=<format>] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- */ public function patch( $args, $assoc_args ) { list( $action, $key ) = $args; $key_path = array_map( function( $key ) { if ( is_numeric( $key ) && ( $key === (string) intval( $key ) ) ) { return (int) $key; } return $key; }, array_slice( $args, 2 ) ); if ( 'delete' == $action ) { $patch_value = null; } elseif ( \WP_CLI\Entity\Utils::has_stdin() ) { $stdin_value = WP_CLI::get_value_from_arg_or_stdin( $args, -1 ); $patch_value = WP_CLI::read_value( trim( $stdin_value ), $assoc_args ); } else { // Take the patch value as the last positional argument. Mutates $key_path to be 1 element shorter! $patch_value = WP_CLI::read_value( array_pop( $key_path ), $assoc_args ); } /* Need to make a copy of $current_value here as it is modified by reference */ $old_value = $current_value = sanitize_option( $key, get_site_option( $key ) ); $traverser = new RecursiveDataStructureTraverser( $current_value ); try { $traverser->$action( $key_path, $patch_value ); } catch ( \Exception $e ) { WP_CLI::error( $e->getMessage() ); } $patched_value = sanitize_option( $key, $traverser->value() ); if ( $patched_value === $old_value ) { WP_CLI::success( "Value passed for '$key' site option is unchanged." ); } else { if ( update_site_option( $key, $patched_value ) ) { WP_CLI::success( "Updated '$key' site option." ); } else { WP_CLI::error( "Could not update site option '$key'." ); } } } private static function esc_like( $old ) { global $wpdb; // Remove notices in 4.0 and support backwards compatibility if( method_exists( $wpdb, 'esc_like' ) ) { // 4.0 $old = $wpdb->esc_like( $old ); } else { // 3.9 or less $old = like_escape( esc_sql( $old ) ); } return $old; } } <?php /** * Manage user terms. * * ## EXAMPLES * * # Set user terms * $ wp user term set 123 test category * Success: Set terms. */ class User_Term_Command extends \WP_CLI\CommandWithTerms { protected $obj_type = 'user'; } <?php use \WP_CLI\Utils; /** * Manages users, along with their roles, capabilities, and meta. * * See references for [Roles and Capabilities](https://codex.wordpress.org/Roles_and_Capabilities) and [WP User class](https://codex.wordpress.org/Class_Reference/WP_User). * * ## EXAMPLES * * # List user IDs * $ wp user list --field=ID * 1 * * # Create a new user. * $ wp user create bob bob@example.com --role=author * Success: Created user 3. * Password: k9**&I4vNH(& * * # Update an existing user. * $ wp user update 123 --display_name=Mary --user_pass=marypass * Success: Updated user 123. * * # Delete user 123 and reassign posts to user 567 * $ wp user delete 123 --reassign=567 * Success: Removed user 123 from http://example.com * * @package wp-cli */ class User_Command extends \WP_CLI\CommandWithDBObject { protected $obj_type = 'user'; protected $obj_fields = array( 'ID', 'user_login', 'display_name', 'user_email', 'user_registered', 'roles' ); private $cap_fields = array( 'name' ); public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\User; $this->sitefetcher = new \WP_CLI\Fetchers\Site; } /** * List users. * * Display WordPress users based on all arguments supported by * [WP_User_Query()](https://developer.wordpress.org/reference/classes/wp_user_query/prepare_query/). * * ## OPTIONS * * [--role=<role>] * : Only display users with a certain role. * * [--<field>=<value>] * : Control output by one or more arguments of WP_User_Query(). * * [--network] * : List all users in the network for multisite. * * [--field=<field>] * : Prints the value of a single field for each user. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each user: * * * ID * * user_login * * display_name * * user_email * * user_registered * * roles * * These fields are optionally available: * * * user_pass * * user_nicename * * user_url * * user_activation_key * * user_status * * spam * * deleted * * caps * * cap_key * * allcaps * * filter * * url * * ## EXAMPLES * * # List user IDs * $ wp user list --field=ID * 1 * * # List users with administrator role * $ wp user list --role=administrator --format=csv * ID,user_login,display_name,user_email,user_registered,roles * 1,supervisor,supervisor,supervisor@gmail.com,"2016-06-03 04:37:00",administrator * * # List users with only given fields * $ wp user list --fields=display_name,user_email --format=json * [{"display_name":"supervisor","user_email":"supervisor@gmail.com"}] * * # List users ordered by the 'last_activity' meta value. * $ wp user list --meta_key=last_activity --orderby=meta_value_num * * @subcommand list */ public function list_( $args, $assoc_args ) { if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ) ) { if ( ! is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } $assoc_args['blog_id'] = 0; if ( isset( $assoc_args['fields'] ) ) { $fields = explode( ',', $assoc_args['fields'] ); $assoc_args['fields'] = array_diff( $fields, array( 'roles' ) ); } else { $assoc_args['fields'] = array_diff( $this->obj_fields, array( 'roles' ) ); } } $formatter = $this->get_formatter( $assoc_args ); if ( in_array( $formatter->format, array( 'ids', 'count' ) ) ) { $assoc_args['fields'] = 'ids'; } else { $assoc_args['fields'] = 'all_with_meta'; } $assoc_args['count_total'] = false; $assoc_args = self::process_csv_arguments_to_arrays( $assoc_args ); $users = get_users( $assoc_args ); if ( 'ids' == $formatter->format ) { echo implode( ' ', $users ); } else if ( 'count' === $formatter->format ) { $formatter->display_items( $users ); } else { $it = WP_CLI\Utils\iterator_map( $users, function ( $user ) { if ( !is_object( $user ) ) return $user; $user->roles = implode( ',', $user->roles ); $user->url = get_author_posts_url( $user->ID, $user->user_nicename ); return $user; } ); $formatter->display_items( $it ); } } /** * Get details about a user. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * [--field=<field>] * : Instead of returning the whole user, returns the value of a single field. * * [--fields=<fields>] * : Get a specific subset of the user's fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Get user * $ wp user get 12 --field=login * supervisor * * # Get user and export to JSON file * $ wp user get bob --format=json > bob.json */ public function get( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); $user_data = $user->to_array(); $user_data['roles'] = implode( ', ', $user->roles ); $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $user_data ); } /** * Delete one or more users from the current site. * * On multisite, `wp user delete` only removes the user from the current * site. Include `--network` to also remove the user from the database, but * make sure to reassign their posts prior to deleting the user. * * ## OPTIONS * * <user>... * : The user login, user email, or user ID of the user(s) to delete. * * [--network] * : On multisite, delete the user from the entire network. * * [--reassign=<user-id>] * : User ID to reassign the posts to. * * [--yes] * : Answer yes to any confirmation prompts. * * ## EXAMPLES * * # Delete user 123 and reassign posts to user 567 * $ wp user delete 123 --reassign=567 * Success: Removed user 123 from http://example.com * * # Delete all contributors and reassign their posts to user 2 * $ wp user delete $(wp user list --role=contributor --field=ID) --reassign=2 * Success: Removed user 813 from http://example.com * Success: Removed user 578 from http://example.com */ public function delete( $args, $assoc_args ) { $network = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ) && is_multisite(); $reassign = \WP_CLI\Utils\get_flag_value( $assoc_args, 'reassign' ); if ( $network && $reassign ) { WP_CLI::error('Reassigning content to a different user is not supported on multisite.'); } if ( !$reassign ) { WP_CLI::confirm( '--reassign parameter not passed. All associated posts will be deleted. Proceed?', $assoc_args ); } $users = $this->fetcher->get_many( $args ); parent::_delete( $users, $assoc_args, function ( $user ) use ( $network, $reassign ) { $user_id = $user->ID; if ( $network ) { $r = wpmu_delete_user( $user_id ); $message = "Deleted user $user_id."; } else { $r = wp_delete_user( $user_id, $reassign ); $message = "Removed user $user_id from " . home_url() . "."; } if ( $r ) { return array( 'success', $message ); } else { return array( 'error', "Failed deleting user $user_id." ); } } ); } /** * Create a new user. * * ## OPTIONS * * <user-login> * : The login of the user to create. * * <user-email> * : The email address of the user to create. * * [--role=<role>] * : The role of the user to create. Default: default role * * [--user_pass=<password>] * : The user password. Default: randomly generated * * [--user_registered=<yyyy-mm-dd-hh-ii-ss>] * : The date the user registered. Default: current date * * [--display_name=<name>] * : The display name. * * [--user_nicename=<nice_name>] * : A string that contains a URL-friendly name for the user. The default is the user's username. * * [--user_url=<url>] * : A string containing the user's URL for the user's web site. * * [--user_email=<email>] * : A string containing the user's email address. * * [--nickname=<nickname>] * : The user's nickname, defaults to the user's username. * * [--first_name=<first_name>] * : The user's first name. * * [--last_name=<last_name>] * : The user's last name. * * [--description=<description>] * : A string containing content about the user. * * [--rich_editing=<rich_editing>] * : A string for whether to enable the rich editor or not. False if not empty. * * [--send-email] * : Send an email to the user with their new account details. * * [--porcelain] * : Output just the new user id. * * ## EXAMPLES * * # Create user * $ wp user create bob bob@example.com --role=author * Success: Created user 3. * Password: k9**&I4vNH(& * * # Create user without showing password upon success * $ wp user create ann ann@example.com --porcelain * 4 */ public function create( $args, $assoc_args ) { $user = new stdClass; list( $user->user_login, $user->user_email ) = $args; $assoc_args = wp_slash( $assoc_args ); if ( username_exists( $user->user_login ) ) { WP_CLI::error( "The '{$user->user_login}' username is already registered." ); } if ( !is_email( $user->user_email ) ) { WP_CLI::error( "The '{$user->user_email}' email address is invalid." ); } $user->user_registered = \WP_CLI\Utils\get_flag_value( $assoc_args, 'user_registered', strftime( "%F %T", current_time('timestamp') ) ); $user->display_name = \WP_CLI\Utils\get_flag_value( $assoc_args, 'display_name', false ); $user->first_name = \WP_CLI\Utils\get_flag_value( $assoc_args, 'first_name', false ); $user->last_name = \WP_CLI\Utils\get_flag_value( $assoc_args, 'last_name', false ); if ( isset( $assoc_args['user_pass'] ) ) { $user->user_pass = $assoc_args['user_pass']; } else { $user->user_pass = wp_generate_password(24); $generated_pass = true; } $user->role = \WP_CLI\Utils\get_flag_value( $assoc_args, 'role', get_option('default_role') ); self::validate_role( $user->role ); if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'send-email' ) ) { add_filter( 'send_password_change_email', '__return_false' ); add_filter( 'send_email_change_email', '__return_false' ); } if ( is_multisite() ) { $ret = wpmu_validate_user_signup( $user->user_login, $user->user_email ); if ( is_wp_error( $ret['errors'] ) && ! empty( $ret['errors']->errors ) ) { WP_CLI::error( $ret['errors'] ); } $user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email ); if ( ! $user_id ) { WP_CLI::error( "Unknown error creating new user." ); } $user->ID = $user_id; $user_id = wp_update_user( $user ); if ( is_wp_error( $user_id ) ) { WP_CLI::error( $user_id ); } } else { $user_id = wp_insert_user( $user ); } if ( ! $user_id || is_wp_error( $user_id ) ) { if ( ! $user_id ) { $user_id = 'Unknown error creating new user.'; } WP_CLI::error( $user_id ); } else { if ( false === $user->role ) { delete_user_option( $user_id, 'capabilities' ); delete_user_option( $user_id, 'user_level' ); } } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'send-email' ) ) { self::wp_new_user_notification( $user_id, $user->user_pass ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { WP_CLI::line( $user_id ); } else { WP_CLI::success( "Created user $user_id." ); if ( isset( $generated_pass ) ) WP_CLI::line( "Password: $user->user_pass" ); } } /** * Update an existing user. * * ## OPTIONS * * <user>... * : The user login, user email or user ID of the user(s) to update. * * [--user_pass=<password>] * : A string that contains the plain text password for the user. * * [--user_login=<login>] * : A string that contains the user's username for logging in. * * [--user_nicename=<nice_name>] * : A string that contains a URL-friendly name for the user. The default is the user's username. * * [--user_url=<url>] * : A string containing the user's URL for the user's web site. * * [--user_email=<email>] * : A string containing the user's email address. * * [--display_name=<display_name>] * : A string that will be shown on the site. Defaults to user's username. * * [--nickname=<nickname>] * : The user's nickname, defaults to the user's username. * * [--first_name=<first_name>] * : The user's first name. * * [--last_name=<last_name>] * : The user's last name. * * [--description=<description>] * : A string containing content about the user. * * [--rich_editing=<rich_editing>] * : A string for whether to enable the rich editor or not. False if not empty. * * [--user_registered=<yyyy-mm-dd-hh-ii-ss>] * : The date the user registered. * * [--role=<role>] * : A string used to set the user's role. * * --<field>=<value> * : One or more fields to update. For accepted fields, see wp_update_user(). * * ## EXAMPLES * * # Update user * $ wp user update 123 --display_name=Mary --user_pass=marypass * Success: Updated user 123. */ public function update( $args, $assoc_args ) { if ( isset( $assoc_args['user_login'] ) ) { WP_CLI::warning( "User logins can't be changed." ); unset( $assoc_args['user_login'] ); } $user_ids = array(); foreach ( $this->fetcher->get_many( $args ) as $user ) { $user_ids[] = $user->ID; } $assoc_args = wp_slash( $assoc_args ); parent::_update( $user_ids, $assoc_args, 'wp_update_user' ); } /** * Generate some users. * * Creates a specified number of new users with dummy data. * * ## OPTIONS * * [--count=<number>] * : How many users to generate? * --- * default: 100 * --- * * [--role=<role>] * : The role of the generated users. Default: default role from WP * * [--format=<format>] * : Render output in a particular format. * --- * default: progress * options: * - progress * - ids * --- * * ## EXAMPLES * * # Add meta to every generated users. * $ wp user generate --format=ids --count=3 | xargs -d ' ' -I % wp user meta add % foo bar * Success: Added custom field. * Success: Added custom field. * Success: Added custom field. */ public function generate( $args, $assoc_args ) { global $blog_id; $defaults = array( 'count' => 100, 'role' => get_option('default_role'), ); $assoc_args = array_merge( $defaults, $assoc_args ); $role = $assoc_args['role']; if ( ! empty( $role ) ) { self::validate_role( $role ); } $user_count = count_users(); $total = $user_count['total_users']; $limit = $assoc_args['count'] + $total; $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'progress' ); $notify = false; if ( 'progress' === $format ) { $notify = \WP_CLI\Utils\make_progress_bar( 'Generating users', $assoc_args['count'] ); } for ( $i = $total; $i < $limit; $i++ ) { $login = sprintf( 'user_%d_%d', $blog_id, $i ); $name = "User $i"; $user_id = wp_insert_user( array( 'user_login' => $login, 'user_pass' => $login, 'nickname' => $name, 'display_name' => $name, 'role' => $role, ) ); if ( false === $role ) { delete_user_option( $user_id, 'capabilities' ); delete_user_option( $user_id, 'user_level' ); } if ( 'progress' === $format ) { $notify->tick(); } else if ( 'ids' === $format ) { echo $user_id; if ( $i < $limit - 1 ) { echo ' '; } } } if ( 'progress' === $format ) { $notify->finish(); } } /** * Set the user role. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * [<role>] * : Make the user have the specified role. If not passed, the default role is * used. * * ## EXAMPLES * * $ wp user set-role 12 author * Success: Added johndoe (12) to http://example.com as author. * * @subcommand set-role */ public function set_role( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); $role = \WP_CLI\Utils\get_flag_value( $args, 1, get_option('default_role') ); self::validate_role( $role ); // Multisite if ( function_exists( 'add_user_to_blog' ) ) add_user_to_blog( get_current_blog_id(), $user->ID, $role ); else $user->set_role( $role ); WP_CLI::success( "Added {$user->user_login} ({$user->ID}) to " . site_url() . " as {$role}." ); } /** * Add a role for a user. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * <role> * : Add the specified role to the user. * * ## EXAMPLES * * $ wp user add-role 12 author * Success: Added 'author' role for johndoe (12). * * @subcommand add-role */ public function add_role( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); $role = $args[1]; self::validate_role( $role ); $user->add_role( $role ); WP_CLI::success( sprintf( "Added '%s' role for %s (%d).", $role, $user->user_login, $user->ID ) ); } /** * Remove a user's role. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * [<role>] * : A specific role to remove. * * ## EXAMPLES * * $ wp user remove-role 12 author * Success: Removed 'author' role for johndoe (12). * * @subcommand remove-role */ public function remove_role( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); if ( isset( $args[1] ) ) { $role = $args[1]; self::validate_role( $role ); $user->remove_role( $role ); WP_CLI::success( sprintf( "Removed '%s' role for %s (%d).", $role, $user->user_login, $user->ID ) ); } else { // Multisite if ( function_exists( 'remove_user_from_blog' ) ) remove_user_from_blog( $user->ID, get_current_blog_id() ); else $user->remove_all_caps(); WP_CLI::success( "Removed {$user->user_login} ({$user->ID}) from " . site_url() . "." ); } } /** * Add a capability to a user. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * <cap> * : The capability to add. * * ## EXAMPLES * * # Add a capability for a user * $ wp user add-cap john create_premium_item * Success: Added 'create_premium_item' capability for john (16). * * # Add a capability for a user * $ wp user add-cap 15 edit_product * Success: Added 'edit_product' capability for johndoe (15). * * @subcommand add-cap */ public function add_cap( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); if ( $user ) { $cap = $args[1]; $user->add_cap( $cap ); WP_CLI::success( sprintf( "Added '%s' capability for %s (%d).", $cap, $user->user_login, $user->ID ) ); } } /** * Remove a user's capability. * * ## OPTIONS * * <user> * : User ID, user email, or user login. * * <cap> * : The capability to be removed. * * ## EXAMPLES * * $ wp user remove-cap 11 publish_newsletters * Success: Removed 'publish_newsletters' cap for supervisor (11). * * @subcommand remove-cap */ public function remove_cap( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); if ( $user ) { $cap = $args[1]; $user->remove_cap( $cap ); WP_CLI::success( sprintf( "Removed '%s' cap for %s (%d).", $cap, $user->user_login, $user->ID ) ); } } /** * List all capabilities for a user. * * ## OPTIONS * * <user> * : User ID, user email, or login. * * [--format=<format>] * : Render output in a particular format. * --- * default: list * options: * - list * - table * - csv * - json * - count * - yaml * --- * * ## EXAMPLES * * $ wp user list-caps 21 * edit_product * create_premium_item * * @subcommand list-caps */ public function list_caps( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); if ( $user ) { $user->get_role_caps(); $user_caps_list = $user->allcaps; $active_user_cap_list = array(); foreach ( $user_caps_list as $cap => $active ) { if ( $active ) { $active_user_cap_list[] = $cap; } } if ( 'list' === $assoc_args['format'] ) { foreach ( $active_user_cap_list as $cap ) { WP_CLI::line( $cap ); } } else { $output_caps = array(); foreach ( $active_user_cap_list as $cap ) { $output_cap = new stdClass; $output_cap->name = $cap; $output_caps[] = $output_cap; } $formatter = new \WP_CLI\Formatter( $assoc_args, $this->cap_fields ); $formatter->display_items( $output_caps ); } } } /** * Import users from a CSV file. * * If the user already exists (matching the email address or login), then * the user is updated unless the `--skip-update` flag is used. * * ## OPTIONS * * <file> * : The local or remote CSV file of users to import. If '-', then reads from STDIN. * * [--send-email] * : Send an email to new users with their account details. * * [--skip-update] * : Don't update users that already exist. * * ## EXAMPLES * * # Import users from local CSV file * $ wp user import-csv /path/to/users.csv * Success: bobjones created * Success: newuser1 created * Success: existinguser created * * # Import users from remote CSV file * $ wp user import-csv http://example.com/users.csv * * Sample users.csv file: * * user_login,user_email,display_name,role * bobjones,bobjones@example.com,Bob Jones,contributor * newuser1,newuser1@example.com,New User,author * existinguser,existinguser@example.com,Existing User,administrator * * @subcommand import-csv */ public function import_csv( $args, $assoc_args ) { $blog_users = get_users(); $filename = $args[0]; if ( 0 === stripos( $filename, 'http://' ) || 0 === stripos( $filename, 'https://' ) ) { $response = wp_remote_head( $filename ); $response_code = (string)wp_remote_retrieve_response_code( $response ); if ( in_array( $response_code[0], array( 4, 5 ) ) ) { WP_CLI::error( "Couldn't access remote CSV file (HTTP {$response_code} response)." ); } } elseif ( '-' === $filename ) { if ( ! WP_CLI\Entity\Utils::has_stdin() ) { \WP_CLI::error( "Unable to read content from STDIN." ); } } elseif ( ! file_exists( $filename ) ) { WP_CLI::error( sprintf( "Missing file: %s", $filename ) ); } // Don't send core's emails during the creation / update process add_filter( 'send_password_change_email', '__return_false' ); add_filter( 'send_email_change_email', '__return_false' ); if ( '-' === $filename && WP_CLI\Entity\Utils::has_stdin() ) { $file_object = new NoRewindIterator( new SplFileObject( "php://stdin" ) ); $file_object->setFlags( SplFileObject::READ_CSV ); $csv_data = array(); $indexes = array(); foreach ( $file_object as $line ) { if ( empty( $line[0] ) ) { continue; } elseif ( empty( $indexes ) ) { $indexes = $line; continue; } foreach ( $indexes as $n => $key ) { $data[ $key ] = $line[ $n ]; } $csv_data[] = $data; } } else { $csv_data = new \WP_CLI\Iterators\CSV( $filename ); } foreach ( $csv_data as $i => $new_user ) { $defaults = array( 'role' => get_option('default_role'), 'user_pass' => wp_generate_password(), 'user_registered' => strftime( "%F %T", time() ), 'display_name' => false, ); $new_user = array_merge( $defaults, $new_user ); $secondary_roles = array(); if ( ! empty( $new_user['roles'] ) ) { $roles = array_map( 'trim', explode( ',', $new_user['roles'] ) ); $invalid_role = false; foreach( $roles as $role ) { if ( is_null( get_role( $role ) ) ) { WP_CLI::warning( "{$new_user['user_login']} has an invalid role." ); $invalid_role = true; break; } } if ( $invalid_role ) { continue; } $new_user['role'] = array_shift( $roles ); $secondary_roles = $roles; } else if ( 'none' === $new_user['role'] ) { $new_user['role'] = false; } elseif ( is_null( get_role( $new_user['role'] ) ) ) { WP_CLI::warning( "{$new_user['user_login']} has an invalid role." ); continue; } // User already exists and we just need to add them to the site if they aren't already there $existing_user = get_user_by( 'email', $new_user['user_email'] ); if ( !$existing_user ) { $existing_user = get_user_by( 'login', $new_user['user_login'] ); } if ( $existing_user && \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-update' ) ) { WP_CLI::log( "{$existing_user->user_login} exists and has been skipped." ); continue; } else if ( $existing_user ) { $new_user['ID'] = $existing_user->ID; $user_id = wp_update_user( $new_user ); if ( !in_array( $existing_user->user_login, wp_list_pluck( $blog_users, 'user_login' ) ) && is_multisite() && $new_user['role'] ) { add_user_to_blog( get_current_blog_id(), $existing_user->ID, $new_user['role'] ); WP_CLI::log( "{$existing_user->user_login} added as {$new_user['role']}." ); } // Create the user } else { unset( $new_user['ID'] ); // Unset else it will just return the ID if ( is_multisite() ) { $ret = wpmu_validate_user_signup( $new_user['user_login'], $new_user['user_email'] ); if ( is_wp_error( $ret['errors'] ) && ! empty( $ret['errors']->errors ) ) { WP_CLI::warning( $ret['errors'] ); continue; } $user_id = wpmu_create_user( $new_user['user_login'], $new_user['user_pass'], $new_user['user_email'] ); if ( ! $user_id ) { WP_CLI::warning( "Unknown error creating new user." ); continue; } $new_user['ID'] = $user_id; $user_id = wp_update_user( $new_user ); if ( is_wp_error( $user_id ) ) { WP_CLI::warning( $user_id ); continue; } } else { $user_id = wp_insert_user( $new_user ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'send-email' ) ) { self::wp_new_user_notification( $user_id, $new_user['user_pass'] ); } } if ( is_wp_error( $user_id ) ) { WP_CLI::warning( $user_id ); continue; } else if ( $new_user['role'] === false ) { delete_user_option( $user_id, 'capabilities' ); delete_user_option( $user_id, 'user_level' ); } $user = get_user_by( 'id', $user_id ); foreach( $secondary_roles as $secondary_role ) { $user->add_role( $secondary_role ); } if ( !empty( $existing_user ) ) { WP_CLI::success( $new_user['user_login'] . " updated." ); } else { WP_CLI::success( $new_user['user_login'] . " created." ); } } } /** * Check whether the role is valid * * @param string */ private static function validate_role( $role ) { if ( ! empty( $role ) && is_null( get_role( $role ) ) ) { WP_CLI::error( sprintf( "Role doesn't exist: %s", $role ) ); } } /** * Acommodate three different behaviors for wp_new_user_notification() * - 4.3.1 and above: expect second argument to be deprecated * - 4.3: Second argument was repurposed as $notify * - Below 4.3: Send the password in the notification * * @param string $user_id * @param string $password */ private static function wp_new_user_notification( $user_id, $password ) { if ( \WP_CLI\Utils\wp_version_compare( '4.3.1', '>=' ) ) { wp_new_user_notification( $user_id, null, 'both' ); } else if ( \WP_CLI\Utils\wp_version_compare( '4.3', '>=' ) ) { wp_new_user_notification( $user_id, 'both' ); } else { wp_new_user_notification( $user_id, $password ); } } /** * Mark one or more users as spam. * * ## OPTIONS * * <id>... * : One or more IDs of users to mark as spam. * * ## EXAMPLES * * $ wp user spam 123 * User 123 marked as spam. * Success: Spamed 1 of 1 users. */ public function spam( $args ) { $this->update_msuser_status( $args, 'spam', '1' ); } /** * Remove one or more users from spam. * * ## OPTIONS * * <id>... * : One or more IDs of users to remove from spam. * * ## EXAMPLES * * $ wp user unspam 123 * User 123 removed from spam. * Success: Unspamed 1 of 1 users. */ public function unspam( $args ) { $this->update_msuser_status( $args, 'spam', '0' ); } /** * Common command for updating user data. */ private function update_msuser_status( $user_ids, $pref, $value ) { // If site is not multisite, then stop execution. if ( ! is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } if ( 'spam' === $pref && '1' === $value ) { $action = 'marked as spam'; $verb = 'spam'; } elseif ( 'spam' === $pref && '0' === $value ) { $action = 'removed from spam'; $verb = 'unspam'; } $successes = $errors = 0; $users = $this->fetcher->get_many( $user_ids ); if ( count( $users ) < count( $user_ids ) ) { $errors = count( $user_ids ) - count( $users ); } foreach ( $user_ids as $user_id ) { $user = get_userdata( $user_id ); // If no user found, then show warning. if ( empty( $user ) ) { WP_CLI::warning( sprintf( 'User %d doesn\'t exist.', esc_html( $user_id ) ) ); continue; } // Super admin should not be marked as spam. if ( is_super_admin( $user->ID ) ) { WP_CLI::warning( sprintf( 'User cannot be modified. The user %d is a network administrator.', esc_html( $user->ID ) ) ); continue; } // Skip if user is already marked as spam and show warning. if ( $value === $user->spam ) { WP_CLI::warning( "User {$user_id} already {$action}." ); continue; } // Make that user's blog as spam too. $blogs = get_blogs_of_user( $user_id, true ); foreach ( (array) $blogs as $details ) { $site = $this->sitefetcher->get_check( $details->site_id ); // Main blog shouldn't a spam ! if ( $details->userblog_id != $site->blog_id ) { update_blog_status( $details->userblog_id, $pref, $value ); } } // Set status and show message. update_user_status( $user_id, $pref, $value ); WP_CLI::log( "User {$user_id} {$action}." ); $successes++; } if ( ! $this->chained_command ) { Utils\report_batch_operation_results( 'user', $verb, count( $user_ids ), $successes, $errors ); } } } <?php /** * Manages posts, content, and meta. * * ## EXAMPLES * * # Create a new post. * $ wp post create --post_type=post --post_title='A sample post' * Success: Created post 123. * * # Update an existing post. * $ wp post update 123 --post_status=draft * Success: Updated post 123. * * # Delete an existing post. * $ wp post delete 123 * Success: Trashed post 123. * * @package wp-cli */ class Post_Command extends \WP_CLI\CommandWithDBObject { protected $obj_type = 'post'; protected $obj_fields = array( 'ID', 'post_title', 'post_name', 'post_date', 'post_status', ); public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\Post; } /** * Create a new post. * * ## OPTIONS * * [<file>] * : Read post content from <file>. If this value is present, the * `--post_content` argument will be ignored. * * Passing `-` as the filename will cause post content to * be read from STDIN. * * [--<field>=<value>] * : Associative args for the new post. See wp_insert_post(). * * [--edit] * : Immediately open system's editor to write or edit post content. * * If content is read from a file, from STDIN, or from the `--post_content` * argument, that text will be loaded into the editor. * * [--porcelain] * : Output just the new post id. * * ## EXAMPLES * * # Create post and schedule for future * $ wp post create --post_type=page --post_title='A future post' --post_status=future --post_date='2020-12-01 07:00:00' * Success: Created post 1921. * * # Create post with content from given file * $ wp post create ./post-content.txt --post_category=201,345 --post_title='Post from file' * Success: Created post 1922. */ public function create( $args, $assoc_args ) { if ( ! empty( $args[0] ) ) { $assoc_args['post_content'] = $this->read_from_file_or_stdin( $args[0] ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'edit' ) ) { $input = \WP_CLI\Utils\get_flag_value( $assoc_args, 'post_content', '' ); if ( $output = $this->_edit( $input, 'WP-CLI: New Post' ) ) $assoc_args['post_content'] = $output; else $assoc_args['post_content'] = $input; } if ( isset( $assoc_args['post_category'] ) ) { $assoc_args['post_category'] = explode( ',', $assoc_args['post_category'] ); } $assoc_args = wp_slash( $assoc_args ); parent::_create( $args, $assoc_args, function ( $params ) { return wp_insert_post( $params, true ); } ); } /** * Update one or more existing posts. * * ## OPTIONS * * <id>... * : One or more IDs of posts to update. * * [<file>] * : Read post content from <file>. If this value is present, the * `--post_content` argument will be ignored. * * Passing `-` as the filename will cause post content to * be read from STDIN. * * --<field>=<value> * : One or more fields to update. See wp_insert_post(). * * [--defer-term-counting] * : Recalculate term count in batch, for a performance boost. * * ## EXAMPLES * * $ wp post update 123 --post_name=something --post_status=draft * Success: Updated post 123. */ public function update( $args, $assoc_args ) { foreach( $args as $key => $arg ) { if ( is_numeric( $arg ) ) { continue; } $assoc_args['post_content'] = $this->read_from_file_or_stdin( $arg ); unset( $args[ $key ] ); break; } if ( isset( $assoc_args['post_category'] ) ) { $assoc_args['post_category'] = explode( ',', $assoc_args['post_category'] ); } $assoc_args = wp_slash( $assoc_args ); parent::_update( $args, $assoc_args, function ( $params ) { return wp_update_post( $params, true ); } ); } /** * Launch system editor to edit post content. * * ## OPTIONS * * <id> * : The ID of the post to edit. * * ## EXAMPLES * * # Launch system editor to edit post * $ wp post edit 123 */ public function edit( $args, $_ ) { $post = $this->fetcher->get_check( $args[0] ); $r = $this->_edit( $post->post_content, "WP-CLI post {$post->ID}" ); if ( $r === false ) \WP_CLI::warning( 'No change made to post content.', 'Aborted' ); else $this->update( $args, array( 'post_content' => $r, ) ); } protected function _edit( $content, $title ) { $content = apply_filters( 'the_editor_content', $content ); $output = \WP_CLI\Utils\launch_editor_for_input( $content, $title ); return ( is_string( $output ) ) ? apply_filters( 'content_save_pre', $output ) : $output; } /** * Get details about a post. * * ## OPTIONS * * <id> * : The ID of the post to get. * * [--field=<field>] * : Instead of returning the whole post, returns the value of a single field. * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Save the post content to a file * $ wp post get 123 --field=content > file.txt */ public function get( $args, $assoc_args ) { $post = $this->fetcher->get_check( $args[0] ); $post_arr = get_object_vars( $post ); unset( $post_arr['filter'] ); if ( empty( $assoc_args['fields'] ) ) { $assoc_args['fields'] = array_keys( $post_arr ); } $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $post_arr ); } /** * Delete an existing post. * * ## OPTIONS * * <id>... * : One or more IDs of posts to delete. * * [--force] * : Skip the trash bin. * * [--defer-term-counting] * : Recalculate term count in batch, for a performance boost. * * ## EXAMPLES * * # Delete post skipping trash * $ wp post delete 123 --force * Success: Deleted post 123. * * # Delete all pages * $ wp post delete $(wp post list --post_type='page' --format=ids) * Success: Trashed post 1164. * Success: Trashed post 1186. * * # Delete all posts in the trash * $ wp post delete $(wp post list --post_status=trash --format=ids) * Success: Trashed post 1268. * Success: Trashed post 1294. */ public function delete( $args, $assoc_args ) { $defaults = array( 'force' => false, ); $assoc_args = array_merge( $defaults, $assoc_args ); parent::_delete( $args, $assoc_args, function ( $post_id, $assoc_args ) { $status = get_post_status( $post_id ); $post_type = get_post_type( $post_id ); $r = wp_delete_post( $post_id, $assoc_args['force'] ); if ( $r ) { $action = $assoc_args['force'] || 'trash' === $status || 'revision' === $post_type ? 'Deleted' : 'Trashed'; return array( 'success', "$action post $post_id." ); } else { return array( 'error', "Failed deleting post $post_id." ); } } ); } /** * Get a list of posts. * * ## OPTIONS * * [--<field>=<value>] * : One or more args to pass to WP_Query. * * [--field=<field>] * : Prints the value of a single field for each post. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each post: * * * ID * * post_title * * post_name * * post_date * * post_status * * These fields are optionally available: * * * post_author * * post_date_gmt * * post_content * * post_excerpt * * comment_status * * ping_status * * post_password * * to_ping * * pinged * * post_modified * * post_modified_gmt * * post_content_filtered * * post_parent * * guid * * menu_order * * post_type * * post_mime_type * * comment_count * * filter * * url * * ## EXAMPLES * * # List post * $ wp post list --field=ID * 568 * 829 * 1329 * 1695 * * # List posts in JSON * $ wp post list --post_type=post --posts_per_page=5 --format=json * [{"ID":1,"post_title":"Hello world!","post_name":"hello-world","post_date":"2015-06-20 09:00:10","post_status":"publish"},{"ID":1178,"post_title":"Markup: HTML Tags and Formatting","post_name":"markup-html-tags-and-formatting","post_date":"2013-01-11 20:22:19","post_status":"draft"}] * * # List all pages * $ wp post list --post_type=page --fields=post_title,post_status * +-------------+-------------+ * | post_title | post_status | * +-------------+-------------+ * | Sample Page | publish | * +-------------+-------------+ * * # List ids of all pages and posts * $ wp post list --post_type=page,post --format=ids * 15 25 34 37 198 * * # List given posts * $ wp post list --post__in=1,3 * +----+--------------+-------------+---------------------+-------------+ * | ID | post_title | post_name | post_date | post_status | * +----+--------------+-------------+---------------------+-------------+ * | 3 | Lorem Ipsum | lorem-ipsum | 2016-06-01 14:34:36 | publish | * | 1 | Hello world! | hello-world | 2016-06-01 14:31:12 | publish | * +----+--------------+-------------+---------------------+-------------+ * * @subcommand list */ public function list_( $_, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); $defaults = array( 'posts_per_page' => -1, 'post_status' => 'any', ); $query_args = array_merge( $defaults, $assoc_args ); $query_args = self::process_csv_arguments_to_arrays( $query_args ); if ( isset( $query_args['post_type'] ) && 'any' !== $query_args['post_type'] ) { $query_args['post_type'] = explode( ',', $query_args['post_type'] ); } if ( 'ids' == $formatter->format ) { $query_args['fields'] = 'ids'; $query = new WP_Query( $query_args ); echo implode( ' ', $query->posts ); } else if ( 'count' === $formatter->format ) { $query_args['fields'] = 'ids'; $query = new WP_Query( $query_args ); $formatter->display_items( $query->posts ); } else { $query = new WP_Query( $query_args ); $posts = array_map( function( $post ) { $post->url = get_permalink( $post->ID ); return $post; }, $query->posts ); $formatter->display_items( $posts ); } } /** * Generate some posts. * * Creates a specified number of new posts with dummy data. * * ## OPTIONS * * [--count=<number>] * : How many posts to generate? * --- * default: 100 * --- * * [--post_type=<type>] * : The type of the generated posts. * --- * default: post * --- * * [--post_status=<status>] * : The status of the generated posts. * --- * default: publish * --- * * [--post_author=<login>] * : The author of the generated posts. * --- * default: * --- * * [--post_date=<yyyy-mm-dd-hh-ii-ss>] * : The date of the generated posts. Default: current date * * [--post_content] * : If set, the command reads the post_content from STDIN. * * [--max_depth=<number>] * : For hierarchical post types, generate child posts down to a certain depth. * --- * default: 1 * --- * * [--format=<format>] * : Render output in a particular format. * --- * default: progress * options: * - progress * - ids * --- * * ## EXAMPLES * * # Generate posts. * $ wp post generate --count=10 --post_type=page --post_date=1999-01-04 * Generating posts 100% [================================================] 0:01 / 0:04 * * # Generate posts with fetched content. * $ curl http://loripsum.net/api/5 | wp post generate --post_content --count=10 * % Total % Received % Xferd Average Speed Time Time Time Current * Dload Upload Total Spent Left Speed * 100 2509 100 2509 0 0 616 0 0:00:04 0:00:04 --:--:-- 616 * Generating posts 100% [================================================] 0:01 / 0:04 * * # Add meta to every generated posts. * $ wp post generate --format=ids | xargs -d ' ' -I % wp post meta add % foo bar * Success: Added custom field. * Success: Added custom field. * Success: Added custom field. */ public function generate( $args, $assoc_args ) { global $wpdb; $defaults = array( 'count' => 100, 'max_depth' => 1, 'post_type' => 'post', 'post_status' => 'publish', 'post_author' => false, 'post_date' => current_time( 'mysql' ), 'post_content' => '', ); extract( array_merge( $defaults, $assoc_args ), EXTR_SKIP ); // @codingStandardsIgnoreStart if ( !post_type_exists( $post_type ) ) { WP_CLI::error( sprintf( "'%s' is not a registered post type.", $post_type ) ); } if ( $post_author ) { $user_fetcher = new \WP_CLI\Fetchers\User; $post_author = $user_fetcher->get_check( $post_author )->ID; } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'post_content' ) ) { $post_content = file_get_contents( 'php://stdin' ); } // Get the total number of posts. $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = %s", $post_type ) ); $label = get_post_type_object( $post_type )->labels->singular_name; $hierarchical = get_post_type_object( $post_type )->hierarchical; $limit = $count + $total; $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'progress' ); $notify = false; if ( 'progress' === $format ) { $notify = \WP_CLI\Utils\make_progress_bar( 'Generating posts', $count ); } $previous_post_id = 0; $current_depth = 1; $current_parent = 0; for ( $i = $total; $i < $limit; $i++ ) { if ( $hierarchical ) { if( $this->maybe_make_child() && $current_depth < $max_depth ) { $current_parent = $previous_post_id; $current_depth++; } else if( $this->maybe_reset_depth() ) { $current_depth = 1; $current_parent = 0; } } $args = array( 'post_type' => $post_type, 'post_title' => "$label $i", 'post_status' => $post_status, 'post_author' => $post_author, 'post_parent' => $current_parent, 'post_name' => "post-$i", 'post_date' => $post_date, 'post_content' => $post_content, ); $post_id = wp_insert_post( $args, true ); if ( is_wp_error( $post_id ) ) { WP_CLI::warning( $post_id ); } else { $previous_post_id = $post_id; if ( 'ids' === $format ) { echo $post_id; if ( $i < $limit - 1 ) { echo ' '; } } } if ( 'progress' === $format ) { $notify->tick(); } } if ( 'progress' === $format ) { $notify->finish(); } // @codingStandardsIgnoreEnd } private function maybe_make_child() { // 50% chance of making child post return ( mt_rand(1, 2) == 1 ); } private function maybe_reset_depth() { // 10% chance of reseting to root depth return ( mt_rand(1, 10) == 7 ); } /** * Read post content from file or STDIN * * @param string $arg Supplied argument * @return string */ private function read_from_file_or_stdin( $arg ) { if ( $arg !== '-' ) { $readfile = $arg; if ( ! file_exists( $readfile ) || ! is_file( $readfile ) ) { \WP_CLI::error( "Unable to read content from '$readfile'." ); } } else { $readfile = 'php://stdin'; } return file_get_contents( $readfile ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'shell', 'Shell_Command' ); <?php namespace WP_CLI; class REPL { private $prompt; public function __construct( $prompt ) { $this->prompt = $prompt; $this->set_history_file(); } public function start() { while ( true ) { $line = $this->prompt(); if ( '' === $line ) continue; $line = rtrim( $line, ';' ) . ';'; if ( self::starts_with( self::non_expressions(), $line ) ) { ob_start(); eval( $line ); $out = ob_get_clean(); if ( 0 < strlen ( $out ) ) { $out = rtrim( $out, "\n" ) . "\n"; } fwrite( STDOUT, $out ); } else { if ( !self::starts_with( 'return', $line ) ) $line = 'return ' . $line; // Write directly to STDOUT, to sidestep any output buffers created by plugins ob_start(); $evl = eval( $line ); $out = ob_get_clean(); if ( 0 < strlen ( $out ) ) { echo rtrim( $out, "\n" ) . "\n"; } echo "=> "; var_dump( $evl ); fwrite( STDOUT, ob_get_clean() ); } } } private static function non_expressions() { return implode( '|', array( 'echo', 'global', 'unset', 'function', 'while', 'for', 'foreach', 'if', 'switch', 'include', 'include\_once', 'require', 'require\_once' ) ); } private function prompt() { $full_line = false; $done = false; do { $prompt = ( !$done && $full_line !== false ) ? '--> ' : $this->prompt; $fp = popen( self::create_prompt_cmd( $prompt, $this->history_file ), 'r' ); $line = fgets( $fp ); if ( !$line ) { break; } $line = rtrim( $line, "\n" ); if ( $line && '\\' == $line[ strlen( $line ) - 1 ] ) { $line = substr( $line, 0, -1 ); } else { $done = true; } $full_line .= $line; } while ( !$done ); if ( $full_line === false ) { return 'exit'; } return $full_line; } private static function create_prompt_cmd( $prompt, $history_path ) { $prompt = escapeshellarg( $prompt ); $history_path = escapeshellarg( $history_path ); $cmd = "set -f; " . "history -r $history_path; " . "LINE=\"\"; " . "read -re -p $prompt LINE; " . "[ $? -eq 0 ] || exit; " . "history -s \"\$LINE\"; " . "history -w $history_path; " . "echo \$LINE; "; return '/bin/bash -c ' . escapeshellarg( $cmd ); } private function set_history_file() { $data = getcwd() . get_current_user(); $this->history_file = \WP_CLI\Utils\get_temp_dir() . 'wp-cli-history-' . md5( $data ); } private static function starts_with( $tokens, $line ) { return preg_match( "/^($tokens)[\(\s]+/", $line ); } } <?php class Shell_Command extends \WP_CLI_Command { /** * Opens an interactive PHP console for running and testing PHP code. * * `wp shell` allows you to evaluate PHP statements and expressions * interactively, from within a WordPress environment. Type a bit of code, * hit enter, and see the code execute right before you. Because WordPress * is loaded, you have access to all the functions, classes and globals * that you can use within a WordPress plugin, for example. * * ## OPTIONS * * [--basic] * : Start in fail-safe mode, even if Boris is available. * * ## EXAMPLES * * # Call get_bloginfo() to get the name of the site. * $ wp shell * wp> get_bloginfo( 'name' ); * => string(6) "WP-CLI" */ public function __invoke( $_, $assoc_args ) { $implementations = array( '\\Psy\\Shell', '\\Boris\\Boris', '\\WP_CLI\\REPL', ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'basic' ) ) { $class = '\\WP_CLI\\REPL'; } else { foreach ( $implementations as $candidate ) { if ( class_exists( $candidate ) ) { $class = $candidate; break; } } } if ( '\\Psy\\Shell' == $class ) { \Psy\Shell::debug(); } else { $repl = new $class( "wp> " ); $repl->start(); } } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'cron', 'Cron_Command' ); WP_CLI::add_command( 'cron event', 'Cron_Event_Command' ); WP_CLI::add_command( 'cron schedule', 'Cron_Schedule_Command' ); <?php /** * Manage WP-Cron events. * * ## EXAMPLES * * # Schedule a new cron event * $ wp cron event schedule cron_test * Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:19:16 GMT. * * # Run all cron events due right now * $ wp cron event run --due-now * Success: Executed a total of 2 cron events. * * # Delete the next scheduled cron event * $ wp cron event delete cron_test * Success: Deleted 2 instances of the cron event 'cron_test'. * * # List scheduled cron events in JSON * $ wp cron event list --fields=hook,next_run --format=json * [{"hook":"wp_version_check","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_plugins","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_themes","next_run":"2016-05-31 10:15:14"}] * * @package wp-cli */ class Cron_Event_Command extends WP_CLI_Command { private $fields = array( 'hook', 'next_run_gmt', 'next_run_relative', 'recurrence', ); private static $time_format = 'Y-m-d H:i:s'; /** * List scheduled cron events. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--<field>=<value>] * : Filter by one or more fields. * * [--field=<field>] * : Prints the value of a single field for each event. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each cron event: * * hook * * next_run_gmt * * next_run_relative * * recurrence * * These fields are optionally available: * * time * * sig * * args * * schedule * * interval * * next_run * * ## EXAMPLES * * # List scheduled cron events * $ wp cron event list * +-------------------+---------------------+---------------------+------------+ * | hook | next_run_gmt | next_run_relative | recurrence | * +-------------------+---------------------+---------------------+------------+ * | wp_version_check | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours | * | wp_update_plugins | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours | * | wp_update_themes | 2016-05-31 22:15:14 | 11 hours 57 minutes | 12 hours | * +-------------------+---------------------+---------------------+------------+ * * # List scheduled cron events in JSON * $ wp cron event list --fields=hook,next_run --format=json * [{"hook":"wp_version_check","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_plugins","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_themes","next_run":"2016-05-31 10:15:14"}] * * @subcommand list */ public function list_( $args, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); $events = self::get_cron_events(); if ( is_wp_error( $events ) ) { $events = array(); } foreach ( $events as $key => $event ) { foreach ( $this->fields as $field ) { if ( ! empty( $assoc_args[ $field ] ) && $event->{$field} !== $assoc_args[ $field ] ) { unset( $events[ $key ] ); break; } } } if ( 'ids' == $formatter->format ) { echo implode( ' ', wp_list_pluck( $events, 'hook' ) ); } else { $formatter->display_items( $events ); } } /** * Schedule a new cron event. * * ## OPTIONS * * <hook> * : The hook name. * * [<next-run>] * : A Unix timestamp or an English textual datetime description compatible with `strtotime()`. Defaults to now. * * [<recurrence>] * : How often the event should recur. See `wp cron schedule list` for available schedule names. Defaults to no recurrence. * * [--<field>=<value>] * : Associative args for the event. * * ## EXAMPLES * * # Schedule a new cron event * $ wp cron event schedule cron_test * Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:19:16 GMT. * * # Schedule new cron event with hourly recurrence * $ wp cron event schedule cron_test now hourly * Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:20:32 GMT. * * # Schedule new cron event and pass associative arguments * $ wp cron event schedule cron_test '+1 hour' --foo=1 --bar=2 * Success: Scheduled event with hook 'cron_test' for 2016-05-31 11:21:35 GMT. */ public function schedule( $args, $assoc_args ) { $hook = $args[0]; $next_run = \WP_CLI\Utils\get_flag_value( $args, 1, 'now' ); $recurrence = \WP_CLI\Utils\get_flag_value( $args, 2, false ); if ( empty( $next_run ) ) { $timestamp = time(); } else if ( is_numeric( $next_run ) ) { $timestamp = absint( $next_run ); } else { $timestamp = strtotime( $next_run ); } if ( ! $timestamp ) { WP_CLI::error( sprintf( "'%s' is not a valid datetime.", $next_run ) ); } if ( ! empty( $recurrence ) ) { $schedules = wp_get_schedules(); if ( ! isset( $schedules[$recurrence] ) ) { WP_CLI::error( sprintf( "'%s' is not a valid schedule name for recurrence.", $recurrence ) ); } $event = wp_schedule_event( $timestamp, $recurrence, $hook, $assoc_args ); } else { $event = wp_schedule_single_event( $timestamp, $hook, $assoc_args ); } if ( false !== $event ) { WP_CLI::success( sprintf( "Scheduled event with hook '%s' for %s GMT.", $hook, date( self::$time_format, $timestamp ) ) ); } else { WP_CLI::error( 'Event not scheduled.' ); } } /** * Run the next scheduled cron event for the given hook. * * ## OPTIONS * * [<hook>...] * : One or more hooks to run. * * [--due-now] * : Run all hooks due right now. * * [--all] * : Run all hooks. * * ## EXAMPLES * * # Run all cron events due right now * $ wp cron event run --due-now * Success: Executed a total of 2 cron events. */ public function run( $args, $assoc_args ) { if ( empty( $args ) && ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'due-now' ) && ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) { WP_CLI::error( 'Please specify one or more cron events, or use --due-now/--all.' ); } $events = self::get_cron_events(); if ( is_wp_error( $events ) ) { WP_CLI::error( $events ); } $hooks = wp_list_pluck( $events, 'hook' ); foreach( $args as $hook ) { if ( ! in_array( $hook, $hooks, true ) ) { WP_CLI::error( sprintf( "Invalid cron event '%s'", $hook ) ); } } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'due-now' ) ) { $due_events = array(); foreach( $events as $event ) { if ( ! empty( $args ) && ! in_array( $event->hook, $args, true ) ) { continue; } if ( time() >= $event->time ) { $due_events[] = $event; } } $events = $due_events; } else if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) { $due_events = array(); foreach( $events as $event ) { if ( in_array( $event->hook, $args ) ) { $due_events[] = $event; } } $events = $due_events; } $executed = 0; foreach ( $events as $event ) { $start = microtime( true ); $result = self::run_event( $event ); $total = round( microtime( true ) - $start, 3 ); $executed++; WP_CLI::log( sprintf( "Executed the cron event '%s' in %ss.", $event->hook, $total ) ); } $message = ( 1 === $executed ) ? 'Executed a total of %d cron event.' : 'Executed a total of %d cron events.'; WP_CLI::success( sprintf( $message, $executed ) ); } /** * Executes an event immediately. * * @param stdClass $event The event * @return bool Whether the event was successfully executed or not. */ protected static function run_event( stdClass $event ) { if ( ! defined( 'DOING_CRON' ) ) { define( 'DOING_CRON', true ); } if ( $event->schedule != false ) { $new_args = array( $event->time, $event->schedule, $event->hook, $event->args ); call_user_func_array( 'wp_reschedule_event', $new_args ); } wp_unschedule_event( $event->time, $event->hook, $event->args ); do_action_ref_array( $event->hook, $event->args ); return true; } /** * Delete the next scheduled cron event for the given hook. * * ## OPTIONS * * <hook> * : The hook name. * * ## EXAMPLES * * # Delete the next scheduled cron event * $ wp cron event delete cron_test * Success: Deleted 2 instances of the cron event 'cron_test'. */ public function delete( $args, $assoc_args ) { $hook = $args[0]; $events = self::get_cron_events(); if ( is_wp_error( $events ) ) { WP_CLI::error( $events ); } $deleted = 0; foreach ( $events as $event ) { if ( $event->hook == $hook ) { $result = self::delete_event( $event ); if ( $result ) { $deleted++; } else { WP_CLI::warning( sprintf( "Failed to the delete the cron event '%s'.", $hook ) ); } } } if ( $deleted ) { $message = ( 1 == $deleted ) ? "Deleted the cron event '%2\$s'." : "Deleted %1\$d instances of the cron event '%2\$s'."; WP_CLI::success( sprintf( $message, $deleted, $hook ) ); } else { WP_CLI::error( sprintf( "Invalid cron event '%s'.", $hook ) ); } } /** * Deletes a cron event. * * @param stdClass $event The event * @return bool Whether the event was successfully deleted or not. */ protected static function delete_event( stdClass $event ) { $crons = _get_cron_array(); if ( ! isset( $crons[$event->time][$event->hook][$event->sig] ) ) { return false; } wp_unschedule_event( $event->time, $event->hook, $event->args ); return true; } /** * Callback function to format a cron event. * * @param stdClass $event The event. * @return stdClass The formatted event object. */ protected static function format_event( stdClass $event ) { $event->next_run = get_date_from_gmt( date( 'Y-m-d H:i:s', $event->time ), self::$time_format ); $event->next_run_gmt = date( self::$time_format, $event->time ); $event->next_run_relative = self::interval( $event->time - time() ); $event->recurrence = ( $event->schedule ) ? self::interval( $event->interval ) : 'Non-repeating'; return $event; } /** * Fetch an array of scheduled cron events. * * @return array|WP_Error An array of event objects, or a WP_Error object if there are no events scheduled. */ protected static function get_cron_events() { $crons = _get_cron_array(); $events = array(); if ( empty( $crons ) ) { return new WP_Error( 'no_events', 'You currently have no scheduled cron events.' ); } foreach ( $crons as $time => $hooks ) { foreach ( $hooks as $hook => $hook_events ) { foreach ( $hook_events as $sig => $data ) { $events[] = (object) array( 'hook' => $hook, 'time' => $time, 'sig' => $sig, 'args' => $data['args'], 'schedule' => $data['schedule'], 'interval' => \WP_CLI\Utils\get_flag_value( $data, 'interval' ), ); } } } $events = array_map( 'Cron_Event_Command::format_event', $events ); return $events; } /** * Convert a time interval into human-readable format. * * Similar to WordPress' built-in `human_time_diff()` but returns two time period chunks instead of just one. * * @param int $since An interval of time in seconds * @return string The interval in human readable format */ private static function interval( $since ) { if ( $since <= 0 ) { return 'now'; } $since = absint( $since ); // array of time period chunks $chunks = array( array( 60 * 60 * 24 * 365 , \_n_noop( '%s year', '%s years' ) ), array( 60 * 60 * 24 * 30 , \_n_noop( '%s month', '%s months' ) ), array( 60 * 60 * 24 * 7, \_n_noop( '%s week', '%s weeks' ) ), array( 60 * 60 * 24 , \_n_noop( '%s day', '%s days' ) ), array( 60 * 60 , \_n_noop( '%s hour', '%s hours' ) ), array( 60 , \_n_noop( '%s minute', '%s minutes' ) ), array( 1 , \_n_noop( '%s second', '%s seconds' ) ), ); // we only want to output two chunks of time here, eg: // x years, xx months // x days, xx hours // so there's only two bits of calculation below: // step one: the first chunk for ( $i = 0, $j = count( $chunks ); $i < $j; $i++ ) { $seconds = $chunks[$i][0]; $name = $chunks[$i][1]; // finding the biggest chunk (if the chunk fits, break) if ( ( $count = floor( $since / $seconds ) ) != 0 ){ break; } } // set output var $output = sprintf( \_n( $name[0], $name[1], $count ), $count ); // step two: the second chunk if ( $i + 1 < $j ) { $seconds2 = $chunks[$i + 1][0]; $name2 = $chunks[$i + 1][1]; if ( ( $count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 ) ) != 0 ) { // add to output var $output .= ' ' . sprintf( \_n( $name2[0], $name2[1], $count2 ), $count2 ); } } return $output; } private function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'event' ); } } <?php /** * Manage WP-Cron events and schedules. * * ## EXAMPLES * * # Test WP Cron spawning system * $ wp cron test * Success: WP-Cron spawning is working as expected. */ class Cron_Command extends WP_CLI_Command { /** * Test the WP Cron spawning system and report back its status. * * This command tests the spawning system by performing the following steps: * * * Checks to see if the `DISABLE_WP_CRON` constant is set; errors if true * because WP-Cron is disabled. * * Checks to see if the `ALTERNATE_WP_CRON` constant is set; warns if true. * * Attempts to spawn WP-Cron over HTTP; warns if non 200 response code is * returned. * * ## EXAMPLES * * # Cron test runs successfully. * $ wp cron test * Success: WP-Cron spawning is working as expected. */ public function test() { if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) { WP_CLI::error( 'The DISABLE_WP_CRON constant is set to true. WP-Cron spawning is disabled.' ); } if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { WP_CLI::warning( 'The ALTERNATE_WP_CRON constant is set to true. WP-Cron spawning is not asynchronous.' ); } $spawn = self::get_cron_spawn(); if ( is_wp_error( $spawn ) ) { WP_CLI::error( sprintf( 'WP-Cron spawn failed with error: %s', $spawn->get_error_message() ) ); } $code = wp_remote_retrieve_response_code( $spawn ); $message = wp_remote_retrieve_response_message( $spawn ); if ( 200 === $code ) { WP_CLI::success( 'WP-Cron spawning is working as expected.' ); } else { WP_CLI::warning( sprintf( 'WP-Cron spawn succeeded but returned HTTP status code: %1$s %2$s', $code, $message ) ); } } /** * Spawn a request to `wp-cron.php` and return the response. * * This function is designed to mimic the functionality in `spawn_cron()` * with the addition of returning the result of the `wp_remote_post()` * request. * * @return WP_Error|array The response or WP_Error on failure. */ protected static function get_cron_spawn() { $sslverify = \WP_CLI\Utils\wp_version_compare( 4.0, '<' ); $doing_wp_cron = sprintf( '%.22F', microtime( true ) ); $cron_request = apply_filters( 'cron_request', array( 'url' => site_url( 'wp-cron.php?doing_wp_cron=' . $doing_wp_cron ), 'key' => $doing_wp_cron, 'args' => array( 'timeout' => 3, 'blocking' => true, 'sslverify' => apply_filters( 'https_local_ssl_verify', $sslverify ) ) ) ); # Enforce a blocking request in case something that's hooked onto the 'cron_request' filter sets it to false $cron_request['args']['blocking'] = true; $result = wp_remote_post( $cron_request['url'], $cron_request['args'] ); return $result; } } <?php /** * Manage WP-Cron schedules. * * ## EXAMPLES * * # List available cron schedules * $ wp cron schedule list * +------------+-------------+----------+ * | name | display | interval | * +------------+-------------+----------+ * | hourly | Once Hourly | 3600 | * | twicedaily | Twice Daily | 43200 | * | daily | Once Daily | 86400 | * +------------+-------------+----------+ */ class Cron_Schedule_Command extends WP_CLI_Command { private $fields = array( 'name', 'display', 'interval', ); /** * List available cron schedules. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--field=<field>] * : Prints the value of a single field for each schedule. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each cron schedule: * * * name * * display * * interval * * There are no additional fields. * * ## EXAMPLES * * # List available cron schedules * $ wp cron schedule list * +------------+-------------+----------+ * | name | display | interval | * +------------+-------------+----------+ * | hourly | Once Hourly | 3600 | * | twicedaily | Twice Daily | 43200 | * | daily | Once Daily | 86400 | * +------------+-------------+----------+ * * # List id of available cron schedule * $ wp cron schedule list --fields=name --format=ids * hourly twicedaily daily * * @subcommand list */ public function list_( $args, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); $schedules = self::get_schedules(); if ( 'ids' == $formatter->format ) { echo implode( ' ', wp_list_pluck( $schedules, 'name' ) ); } else { $formatter->display_items( $schedules ); } } /** * Callback function to format a cron schedule. * * @param array $schedule The schedule. * @param string $name The schedule name. * @return array The formatted schedule. */ protected static function format_schedule( array $schedule, $name ) { $schedule['name'] = $name; return $schedule; } /** * Return a list of the cron schedules sorted according to interval. * * @return array The array of cron schedules. Each schedule is itself an array. */ protected static function get_schedules() { $schedules = wp_get_schedules(); if ( !empty( $schedules ) ) { uasort( $schedules, 'Cron_Schedule_Command::sort' ); $schedules = array_map( 'Cron_Schedule_Command::format_schedule', $schedules, array_keys( $schedules ) ); } return $schedules; } /** * Callback function to sort the cron schedule array by interval. * */ protected static function sort( array $a, array $b ) { return $a['interval'] - $b['interval']; } private function get_formatter( &$assoc_args ) { return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'schedule' ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'core', 'Core_Command' ); <?php use \Composer\Semver\Comparator; use \WP_CLI\Extractor; use \WP_CLI\Utils; /** * Download, install, update and manage a WordPress install. * * ## EXAMPLES * * # Download WordPress core * $ wp core download --locale=nl_NL * Downloading WordPress 4.5.2 (nl_NL)... * md5 hash verified: c5366d05b521831dd0b29dfc386e56a5 * Success: WordPress downloaded. * * # Install WordPress * $ wp core install --url=example.com --title=Example --admin_user=supervisor --admin_password=strongpassword --admin_email=info@example.com * Success: WordPress installed successfully. * * # Display the WordPress version * $ wp core version * 4.5.2 * * @package wp-cli */ class Core_Command extends WP_CLI_Command { /** * Check for WordPress updates via Version Check API. * * Lists the most recent versions when there are updates available, * or success message when up to date. * * ## OPTIONS * * [--minor] * : Compare only the first two parts of the version number. * * [--major] * : Compare only the first part of the version number. * * [--field=<field>] * : Prints the value of a single field for each update. * * [--fields=<fields>] * : Limit the output to specific object fields. Defaults to version,update_type,package_url. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - count * - json * - yaml * --- * * ## EXAMPLES * * $ wp core check-update * +---------+-------------+-------------------------------------------------------------+ * | version | update_type | package_url | * +---------+-------------+-------------------------------------------------------------+ * | 4.5.2 | major | https://downloads.wordpress.org/release/wordpress-4.5.2.zip | * +---------+-------------+-------------------------------------------------------------+ * * @subcommand check-update */ function check_update( $_, $assoc_args ) { $updates = $this->get_updates( $assoc_args ); if ( $updates ) { $updates = array_reverse( $updates ); $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'version', 'update_type', 'package_url' ) ); $formatter->display_items( $updates ); } else if ( empty( $assoc_args['format'] ) || 'table' == $assoc_args['format'] ) { WP_CLI::success( "WordPress is at the latest version." ); } } /** * Download core WordPress files. * * Downloads and extracts WordPress core files to the specified path. Uses * current directory when no path is specified. Downloaded build is verified * to have the correct md5 and then cached to the local filesytem. * Subsequent uses of command will use the local cache if it still exists. * * ## OPTIONS * * [--path=<path>] * : Specify the path in which to install WordPress. Defaults to current * directory. * * [--locale=<locale>] * : Select which language you want to download. * * [--version=<version>] * : Select which version you want to download. Accepts a version number, 'latest' or 'nightly' * * [--skip-content] * : Download the latest version of WP without the default themes and plugins (en_US locale only) * * [--force] * : Overwrites existing files, if present. * * ## EXAMPLES * * $ wp core download --locale=nl_NL * Downloading WordPress 4.5.2 (nl_NL)... * md5 hash verified: c5366d05b521831dd0b29dfc386e56a5 * Success: WordPress downloaded. * * @when before_wp_load */ public function download( $args, $assoc_args ) { $download_dir = ! empty( $assoc_args['path'] ) ? ( rtrim( $assoc_args['path'], '/\\' ) . '/' ) : ABSPATH; $wordpress_present = is_readable( $download_dir . 'wp-load.php' ); if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) && $wordpress_present ) WP_CLI::error( 'WordPress files seem to already be present here.' ); if ( ! is_dir( $download_dir ) ) { if ( ! is_writable( dirname( $download_dir ) ) ) { WP_CLI::error( sprintf( "Insufficient permission to create directory '%s'.", $download_dir ) ); } WP_CLI::log( sprintf( "Creating directory '%s'.", $download_dir ) ); if ( ! @mkdir( $download_dir, 0777, true /*recursive*/ ) ) { $error = error_get_last(); WP_CLI::error( sprintf( "Failed to create directory '%s': %s.", $download_dir, $error['message'] ) ); } } if ( ! is_writable( $download_dir ) ) { WP_CLI::error( sprintf( "'%s' is not writable by current user.", $download_dir ) ); } $locale = \WP_CLI\Utils\get_flag_value( $assoc_args, 'locale', 'en_US' ); if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-content' ) && 'en_US' !== $locale ) { WP_CLI::error( 'Skip content build is only available for the en_US locale.' ); } if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-content' ) && isset( $assoc_args['version'] ) ) { WP_CLI::error( 'Skip content build is only available for the latest version.' ); } $no_content = ''; if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-content' ) ) { $response = \WP_CLI\Utils\http_request( 'GET', 'https://api.wordpress.org/core/version-check/1.7/' ); if ( 200 === $response->status_code && ( $body = json_decode( $response->body ) ) && is_object( $body ) && isset( $body->offers[0]->packages->no_content ) && is_array( $body->offers ) ) { $download_url = $body->offers[0]->packages->no_content; $version = $body->offers[0]->version; $no_content = 'no-content-'; } else { WP_CLI::error( 'Skip content build is not available.' ); } } elseif ( isset( $assoc_args['version'] ) && 'latest' !== $assoc_args['version'] ) { $version = $assoc_args['version']; $version = ( in_array( strtolower( $version ), array( 'trunk', 'nightly' ) ) ? 'nightly' : $version ); //nightly builds are only available in .zip format $ext = ( 'nightly' === $version ? 'zip' : 'tar.gz' ); $download_url = $this->get_download_url( $version, $locale, $ext ); } else { $offer = $this->get_download_offer( $locale ); if ( !$offer ) { WP_CLI::error( "The requested locale ($locale) was not found." ); } $version = $offer['current']; $download_url = str_replace( '.zip', '.tar.gz', $offer['download'] ); } if ( 'nightly' === $version && 'en_US' !== $locale ) { WP_CLI::error( 'Nightly builds are only available for the en_US locale.' ); } $from_version = ''; if ( file_exists( $download_dir . 'wp-includes/version.php' ) ) { $wp_details = self::get_wp_details( $download_dir ); $from_version = $wp_details['wp_version']; } WP_CLI::log( sprintf( 'Downloading WordPress %s (%s)...', $version, $locale ) ); $path_parts = pathinfo( $download_url ); $extension = 'tar.gz'; if ( 'zip' === $path_parts['extension'] ) { $extension = 'zip'; if ( ! class_exists( 'ZipArchive' ) ) { WP_CLI::error( 'Extracting a zip file requires ZipArchive.' ); } } $cache = WP_CLI::get_cache(); $cache_key = "core/wordpress-{$version}-{$no_content}{$locale}.{$extension}"; $cache_file = $cache->has($cache_key); $bad_cache = false; if ( $cache_file ) { WP_CLI::log( "Using cached file '$cache_file'..." ); try{ Extractor::extract( $cache_file, $download_dir ); } catch ( Exception $e ) { WP_CLI::warning( "Extraction failed, downloading a new copy..." ); $bad_cache = true; } } if ( ! $cache_file || $bad_cache ) { // We need to use a temporary file because piping from cURL to tar is flaky // on MinGW (and probably in other environments too). $temp = \WP_CLI\Utils\get_temp_dir() . uniqid('wp_') . ".{$extension}"; $headers = array('Accept' => 'application/json'); $options = array( 'timeout' => 600, // 10 minutes ought to be enough for everybody 'filename' => $temp ); $response = Utils\http_request( 'GET', $download_url, null, $headers, $options ); if ( 404 == $response->status_code ) { WP_CLI::error( "Release not found. Double-check locale or version." ); } else if ( 20 != substr( $response->status_code, 0, 2 ) ) { WP_CLI::error( "Couldn't access download URL (HTTP code {$response->status_code})." ); } if ( 'nightly' !== $version ) { $md5_response = Utils\http_request( 'GET', $download_url . '.md5' ); if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$response->status_code})." ); } $md5_file = md5_file( $temp ); if ( $md5_file === $md5_response->body ) { WP_CLI::log( 'md5 hash verified: ' . $md5_file ); } else { WP_CLI::error( "md5 hash for download ({$md5_file}) is different than the release hash ({$md5_response->body})." ); } } else { WP_CLI::warning( 'md5 hash checks are not available for nightly downloads.' ); } try { Extractor::extract( $temp, $download_dir ); } catch ( Exception $e ) { WP_CLI::error( "Couldn't extract WordPress archive. " . $e->getMessage() ); } if ( 'nightly' !== $version ) { $cache->import( $cache_key, $temp ); } unlink( $temp ); } if ( $wordpress_present ) { $this->cleanup_extra_files( $from_version, $version, $locale ); } WP_CLI::success( 'WordPress downloaded.' ); } private static function _read( $url ) { $headers = array('Accept' => 'application/json'); $response = Utils\http_request( 'GET', $url, null, $headers, array( 'timeout' => 30 ) ); if ( 200 === $response->status_code ) { return $response->body; } else { WP_CLI::error( "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." ); } } private function get_download_offer( $locale ) { $out = unserialize( self::_read( 'https://api.wordpress.org/core/version-check/1.6/?locale=' . $locale ) ); $offer = $out['offers'][0]; if ( $offer['locale'] != $locale ) { return false; } return $offer; } /** * Check if WordPress is installed. * * Determines whether WordPress is installed by checking if the standard * database tables are installed. Doesn't produce output; uses exit codes * to communicate whether WordPress is installed. * * [--network] * : Check if this is a multisite install. * * ## EXAMPLES * * # Check whether WordPress is installed; exit status 0 if installed, otherwise 1 * $ wp core is-installed * $ echo $? * 1 * * # Bash script for checking whether WordPress is installed or not * if ! $(wp core is-installed); then * wp core install * fi * * @subcommand is-installed */ public function is_installed( $_, $assoc_args ) { if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ) ) { if ( is_blog_installed() && is_multisite() ) { WP_CLI::halt( 0 ); } else { WP_CLI::halt( 1 ); } } else if ( is_blog_installed() ) { WP_CLI::halt( 0 ); } else { WP_CLI::halt( 1 ); } } /** * Runs the standard WordPress installation process. * * Creates the WordPress tables in the database using the URL, title, and * default admin user details provided. Performs the famous 5 minute install * in seconds or less. * * Note: if you've installed WordPress in a subdirectory, then you'll need * to `wp option update siteurl` after `wp core install`. For instance, if * WordPress is installed in the `/wp` directory and your domain is wp.dev, * then you'll need to run `wp option update siteurl http://wp.dev/wp` for * your WordPress install to function properly. * * Note: When using custom user tables (e.g. `CUSTOM_USER_TABLE`), the admin * email and password are ignored if the user_login already exists. If the * user_login doesn't exist, a new user will be created. * * ## OPTIONS * * --url=<url> * : The address of the new site. * * --title=<site-title> * : The title of the new site. * * --admin_user=<username> * : The name of the admin user. * * [--admin_password=<password>] * : The password for the admin user. Defaults to randomly generated string. * * --admin_email=<email> * : The email address for the admin user. * * [--skip-email] * : Don't send an email notification to the new admin user. * * ## EXAMPLES * * # Install WordPress in 5 seconds * $ wp core install --url=example.com --title=Example --admin_user=supervisor --admin_password=strongpassword --admin_email=info@example.com * Success: WordPress installed successfully. * * # Install WordPress without disclosing admin_password to bash history * $ wp core install --url=example.com --title=Example --admin_user=supervisor --admin_email=info@example.com --prompt=admin_password < admin_password.txt */ public function install( $args, $assoc_args ) { if ( $this->_install( $assoc_args ) ) { WP_CLI::success( 'WordPress installed successfully.' ); } else { WP_CLI::log( 'WordPress is already installed.' ); } } /** * Transform a single-site install into a WordPress multisite install. * * Creates the multisite database tables, and adds the multisite constants * to wp-config.php. * * For those using WordPress with Apache, remember to update the `.htaccess` * file with the appropriate multisite rewrite rules. * * ## OPTIONS * * [--title=<network-title>] * : The title of the new network. * * [--base=<url-path>] * : Base path after the domain name that each site url will start with. * --- * default: / * --- * * [--subdomains] * : If passed, the network will use subdomains, instead of subdirectories. Doesn't work with 'localhost'. * * ## EXAMPLES * * $ wp core multisite-convert * Set up multisite database tables. * Added multisite constants to wp-config.php. * Success: Network installed. Don't forget to set up rewrite rules. * * @subcommand multisite-convert * @alias install-network */ public function multisite_convert( $args, $assoc_args ) { if ( is_multisite() ) WP_CLI::error( 'This already is a multisite install.' ); $assoc_args = self::_set_multisite_defaults( $assoc_args ); if ( !isset( $assoc_args['title'] ) ) { $assoc_args['title'] = sprintf( _x('%s Sites', 'Default network name' ), get_option( 'blogname' ) ); } if ( $this->_multisite_convert( $assoc_args ) ) { WP_CLI::success( "Network installed. Don't forget to set up rewrite rules (and a .htaccess file, if using Apache)." ); } } /** * Install WordPress multisite from scratch. * * Creates the WordPress tables in the database using the URL, title, and * default admin user details provided. Then, creates the multisite tables * in the database and adds multisite constants to the wp-config.php. * * For those using WordPress with Apache, remember to update the `.htaccess` * file with the appropriate multisite rewrite rules. * * ## OPTIONS * * [--url=<url>] * : The address of the new site. * * [--base=<url-path>] * : Base path after the domain name that each site url in the network will start with. * --- * default: / * --- * * [--subdomains] * : If passed, the network will use subdomains, instead of subdirectories. Doesn't work with 'localhost'. * * --title=<site-title> * : The title of the new site. * * --admin_user=<username> * : The name of the admin user. * --- * default: admin * --- * * [--admin_password=<password>] * : The password for the admin user. Defaults to randomly generated string. * * --admin_email=<email> * : The email address for the admin user. * * [--skip-email] * : Don't send an email notification to the new admin user. * * [--skip-config] * : Don't add multisite constants to wp-config.php. * * ## EXAMPLES * * $ wp core multisite-install --title="Welcome to the WordPress" \ * > --admin_user="admin" --admin_password="password" \ * > --admin_email="user@example.com" * Single site database tables already present. * Set up multisite database tables. * Added multisite constants to wp-config.php. * Success: Network installed. Don't forget to set up rewrite rules. * * @subcommand multisite-install */ public function multisite_install( $args, $assoc_args ) { if ( $this->_install( $assoc_args ) ) { WP_CLI::log( 'Created single site database tables.' ); } else { WP_CLI::log( 'Single site database tables already present.' ); } $assoc_args = self::_set_multisite_defaults( $assoc_args ); $assoc_args['title'] = sprintf( _x('%s Sites', 'Default network name' ), $assoc_args['title'] ); // Overwrite runtime args, to avoid mismatches. $consts_to_args = array( 'SUBDOMAIN_INSTALL' => 'subdomains', 'PATH_CURRENT_SITE' => 'base', 'SITE_ID_CURRENT_SITE' => 'site_id', 'BLOG_ID_CURRENT_SITE' => 'blog_id', ); foreach ( $consts_to_args as $const => $arg ) { if ( defined( $const ) ) { $assoc_args[ $arg ] = constant( $const ); } } if ( !$this->_multisite_convert( $assoc_args ) ) { return; } // Do the steps that were skipped by populate_network(), // which checks is_multisite(). if ( is_multisite() ) { $site_user = get_user_by( 'email', $assoc_args['admin_email'] ); self::add_site_admins( $site_user ); $domain = self::get_clean_basedomain(); self::create_initial_blog( $assoc_args['site_id'], $assoc_args['blog_id'], $domain, $assoc_args['base'], $assoc_args['subdomains'], $site_user ); } WP_CLI::success( "Network installed. Don't forget to set up rewrite rules (and a .htaccess file, if using Apache)." ); } private static function _set_multisite_defaults( $assoc_args ) { $defaults = array( 'subdomains' => false, 'base' => '/', 'site_id' => 1, 'blog_id' => 1, ); return array_merge( $defaults, $assoc_args ); } private function _install( $assoc_args ) { if ( is_blog_installed() ) { return false; } if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-email' ) && ! function_exists( 'wp_new_blog_notification' ) ) { function wp_new_blog_notification() { // Silence is golden } } require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); extract( wp_parse_args( $assoc_args, array( 'title' => '', 'admin_user' => '', 'admin_email' => '', 'admin_password' => '' ) ), EXTR_SKIP ); // Support prompting for the `--url=<url>`, // which is normally a runtime argument if ( isset( $assoc_args['url'] ) ) { WP_CLI::set_url( $assoc_args['url'] ); } $public = true; $password = empty($admin_password) ? wp_generate_password(18) : $admin_password; // @codingStandardsIgnoreStart if ( !is_email( $admin_email ) ) { WP_CLI::error( "The '{$admin_email}' email address is invalid." ); } $result = wp_install( $title, $admin_user, $admin_email, $public, '', $password ); if ( is_wp_error( $result ) ) { WP_CLI::error( 'Installation failed (' . WP_CLI::error_to_string($result) . ').' ); } // @codingStandardsIgnoreEnd if ( ! empty( $GLOBALS['wpdb']->last_error ) ) { WP_CLI::error( 'Installation produced database errors, and may have partially or completely failed.' ); } if ( empty( $admin_password ) ) { WP_CLI::log( "Admin password: {$result['password']}" ); } // Confirm the uploads directory exists $upload_dir = wp_upload_dir(); if ( ! empty( $upload_dir['error'] ) ) { WP_CLI::warning( $upload_dir['error'] ); } return true; } private function _multisite_convert( $assoc_args ) { global $wpdb; require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); $domain = self::get_clean_basedomain(); if ( 'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) { WP_CLI::error( "Multisite with subdomains cannot be configured when domain is 'localhost'." ); } // need to register the multisite tables manually for some reason foreach ( $wpdb->tables( 'ms_global' ) as $table => $prefixed_table ) $wpdb->$table = $prefixed_table; install_network(); $result = populate_network( $assoc_args['site_id'], $domain, get_option( 'admin_email' ), $assoc_args['title'], $assoc_args['base'], $assoc_args['subdomains'] ); if ( true === $result ) { WP_CLI::log( 'Set up multisite database tables.' ); } else if ( is_wp_error( $result ) ) { switch ( $result->get_error_code() ) { case 'siteid_exists': WP_CLI::log( $result->get_error_message() ); return false; case 'no_wildcard_dns': WP_CLI::warning( __( 'Wildcard DNS may not be configured correctly.' ) ); break; default: WP_CLI::error( $result ); } } // delete_site_option() cleans the alloptions cache to prevent dupe option delete_site_option( 'upload_space_check_disabled' ); update_site_option( 'upload_space_check_disabled', 1 ); if ( !is_multisite() ) { $subdomain_export = Utils\get_flag_value( $assoc_args, 'subdomains' ) ? 'true' : 'false'; $ms_config = <<<EOT define( 'WP_ALLOW_MULTISITE', true ); define( 'MULTISITE', true ); define( 'SUBDOMAIN_INSTALL', {$subdomain_export} ); \$base = '{$assoc_args['base']}'; define( 'DOMAIN_CURRENT_SITE', '{$domain}' ); define( 'PATH_CURRENT_SITE', '{$assoc_args['base']}' ); define( 'SITE_ID_CURRENT_SITE', 1 ); define( 'BLOG_ID_CURRENT_SITE', 1 ); EOT; $wp_config_path = Utils\locate_wp_config(); if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-config' ) ) { WP_CLI::log( "Addition of multisite constants to 'wp-config.php' skipped. You need to add them manually:" . PHP_EOL . $ms_config ); } elseif ( is_writable( $wp_config_path ) && self::modify_wp_config( $ms_config ) ) { WP_CLI::log( "Added multisite constants to 'wp-config.php'." ); } else { WP_CLI::warning( "Multisite constants could not be written to 'wp-config.php'. You may need to add them manually:" . PHP_EOL . $ms_config ); } } return true; } // copied from populate_network() private static function create_initial_blog( $network_id, $blog_id, $domain, $path, $subdomain_install, $site_user ) { global $wpdb, $current_site, $wp_rewrite; $current_site = new stdClass; $current_site->domain = $domain; $current_site->path = $path; $current_site->site_name = ucfirst( $domain ); $wpdb->insert( $wpdb->blogs, array( 'site_id' => $network_id, 'domain' => $domain, 'path' => $path, 'registered' => current_time( 'mysql' ) ) ); $current_site->blog_id = $blog_id = $wpdb->insert_id; update_user_meta( $site_user->ID, 'source_domain', $domain ); update_user_meta( $site_user->ID, 'primary_blog', $blog_id ); if ( $subdomain_install ) $wp_rewrite->set_permalink_structure( '/%year%/%monthnum%/%day%/%postname%/' ); else $wp_rewrite->set_permalink_structure( '/blog/%year%/%monthnum%/%day%/%postname%/' ); flush_rewrite_rules(); } // copied from populate_network() private static function add_site_admins( $site_user ) { $site_admins = array( $site_user->user_login ); $users = get_users( array( 'fields' => array( 'ID', 'user_login' ) ) ); if ( $users ) { foreach ( $users as $user ) { if ( is_super_admin( $user->ID ) && !in_array( $user->user_login, $site_admins ) ) $site_admins[] = $user->user_login; } } update_site_option( 'site_admins', $site_admins ); } private static function modify_wp_config( $content ) { $wp_config_path = Utils\locate_wp_config(); $token = "/* That's all, stop editing!"; $config_contents = file_get_contents( $wp_config_path ); if ( false === strpos( $config_contents, $token ) ) { return false; } list( $before, $after ) = explode( $token, $config_contents ); $content = PHP_EOL . PHP_EOL . trim( $content ) . PHP_EOL . PHP_EOL; file_put_contents( $wp_config_path, $before . $content . $token . $after ); return true; } private static function get_clean_basedomain() { $domain = preg_replace( '|https?://|', '', get_option( 'siteurl' ) ); if ( $slash = strpos( $domain, '/' ) ) $domain = substr( $domain, 0, $slash ); return $domain; } /** * Display the WordPress version. * * ## OPTIONS * * [--extra] * : Show extended version information. * * ## EXAMPLES * * # Display the WordPress version * $ wp core version * 4.5.2 * * # Display WordPress version along with other information * $ wp core version --extra * WordPress version: 4.5.2 * Database revision: 36686 * TinyMCE version: 4.310 (4310-20160418) * Package language: en_US * * @when before_wp_load */ public function version( $args = array(), $assoc_args = array() ) { $details = self::get_wp_details(); // @codingStandardsIgnoreStart if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'extra' ) ) { if ( preg_match( '/(\d)(\d+)-/', $details['tinymce_version'], $match ) ) { $human_readable_tiny_mce = $match[1] . '.' . $match[2]; } else { $human_readable_tiny_mce = ''; } echo \WP_CLI\Utils\mustache_render( 'versions.mustache', array( 'wp-version' => $details['wp_version'], 'db-version' => $details['wp_db_version'], 'local-package' => ( empty( $details['wp_local_package'] ) ? 'en_US' : $details['wp_local_package'] ), 'mce-version' => ( $human_readable_tiny_mce ? "$human_readable_tiny_mce ({$details['tinymce_version']})" : $details['tinymce_version'] ) ) ); } else { WP_CLI::line( $details['wp_version'] ); } // @codingStandardsIgnoreEnd } /** * Get version information from `wp-includes/version.php`. * * @return array { * @type string $wp_version The WordPress version. * @type int $wp_db_version The WordPress DB revision. * @type string $tinymce_version The TinyMCE version. * @type string $wp_local_package The TinyMCE version. * } */ private static function get_wp_details( $abspath = ABSPATH ) { $versions_path = $abspath . 'wp-includes/version.php'; if ( ! is_readable( $versions_path ) ) { WP_CLI::error( "This does not seem to be a WordPress install.\n" . "Pass --path=`path/to/wordpress` or run `wp core download`." ); } $version_content = file_get_contents( $versions_path, null, null, 6, 2048 ); $vars = array( 'wp_version', 'wp_db_version', 'tinymce_version', 'wp_local_package' ); $result = array(); foreach ( $vars as $var_name ) { $result[ $var_name ] = self::find_var( $var_name, $version_content ); } return $result; } /** * Search for the value assigned to variable `$var_name` in PHP code `$code`. * * This is equivalent to matching the `\$VAR_NAME = ([^;]+)` regular expression and returning * the first match either as a `string` or as an `integer` (depending if it's surrounded by * quotes or not). * * @param string $var_name Variable name to search for. * @param string $code PHP code to search in. * * @return int|string|null */ private static function find_var( $var_name, $code ) { $start = strpos( $code, '$' . $var_name . ' = ' ); if ( ! $start ) { return null; } $start = $start + strlen( $var_name ) + 3; $end = strpos( $code, ";", $start ); $value = substr( $code, $start, $end - $start ); if ( $value[0] = "'" ) { return trim( $value, "'" ); } else { return intval( $value ); } } /** * Security copy of the core function with Requests - Gets the checksums for the given version of WordPress. * * @param string $version Version string to query. * @param string $locale Locale to query. * @return bool|array False on failure. An array of checksums on success. */ private static function get_core_checksums( $version, $locale ) { $url = 'https://api.wordpress.org/core/checksums/1.0/?' . http_build_query( compact( 'version', 'locale' ), null, '&' ); $options = array( 'timeout' => 30 ); $headers = array( 'Accept' => 'application/json' ); $response = Utils\http_request( 'GET', $url, null, $headers, $options ); if ( ! $response->success || 200 != $response->status_code ) return false; $body = trim( $response->body ); $body = json_decode( $body, true ); if ( ! is_array( $body ) || ! isset( $body['checksums'] ) || ! is_array( $body['checksums'] ) ) return false; return $body['checksums']; } /** * Update WordPress to a newer version. * * Defaults to updating WordPress to the latest version. * * If you see "Error: Another update is currently in progress.", you may * need to run `wp option delete core_updater.lock` after verifying another * update isn't actually running. * * ## OPTIONS * * [<zip>] * : Path to zip file to use, instead of downloading from wordpress.org. * * [--minor] * : Only perform updates for minor releases (e.g. update from WP 4.3 to 4.3.3 instead of 4.4.2). * * [--version=<version>] * : Update to a specific version, instead of to the latest version. Alternatively accepts 'nightly'. * * [--force] * : Update even when installed WP version is greater than the requested version. * * [--locale=<locale>] * : Select which language you want to download. * * ## EXAMPLES * * # Update WordPress * $ wp core update * Updating to version 4.5.2 (en_US)... * Downloading update from https://downloads.wordpress.org/release/wordpress-4.5.2-no-content.zip... * Unpacking the update... * Cleaning up files... * No files found that need cleaned up * Success: WordPress updated successfully. * * # Update WordPress to latest version of 3.8 release * $ wp core update --version=3.8 ../latest.zip * Updating to version 3.8 ()... * Unpacking the update... * Cleaning up files... * File removed: wp-admin/js/tags-box.js * ... * File removed: wp-admin/js/updates.min. * 377 files cleaned up * Success: WordPress updated successfully. * * # Update WordPress to 3.1 forcefully * $ wp core update --version=3.1 --force * Updating to version 3.1 (en_US)... * Downloading update from https://wordpress.org/wordpress-3.1.zip... * Unpacking the update... * Warning: Failed to fetch checksums. Please cleanup files manually. * Success: WordPress updated successfully. * * @alias upgrade */ public function update( $args, $assoc_args ) { global $wp_version; $update = $from_api = null; $upgrader = 'WP_CLI\\CoreUpgrader'; if ( 'trunk' === Utils\get_flag_value( $assoc_args, 'version' ) ) { $assoc_args['version'] = 'nightly'; } if ( ! empty( $args[0] ) ) { // ZIP path or URL is given $upgrader = 'WP_CLI\\NonDestructiveCoreUpgrader'; $version = \WP_CLI\Utils\get_flag_value( $assoc_args, 'version' ); $update = (object) array( 'response' => 'upgrade', 'current' => $version, 'download' => $args[0], 'packages' => (object) array ( 'partial' => null, 'new_bundled' => null, 'no_content' => null, 'full' => $args[0], ), 'version' => $version, 'locale' => null ); } else if ( empty( $assoc_args['version'] ) ) { // Update to next release wp_version_check(); $from_api = get_site_transient( 'update_core' ); if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'minor' ) ) { foreach( $from_api->updates as $offer ) { $sem_ver = Utils\get_named_sem_ver( $offer->version, $wp_version ); if ( ! $sem_ver || 'patch' !== $sem_ver ) { continue; } $update = $offer; break; } if ( empty( $update ) ) { WP_CLI::success( 'WordPress is at the latest minor release.' ); return; } } else { if ( ! empty( $from_api->updates ) ) { list( $update ) = $from_api->updates; } } } else if ( \WP_CLI\Utils\wp_version_compare( $assoc_args['version'], '<' ) || 'nightly' === $assoc_args['version'] || \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) ) { // Specific version is given $version = $assoc_args['version']; $locale = \WP_CLI\Utils\get_flag_value( $assoc_args, 'locale', get_locale() ); $new_package = $this->get_download_url($version, $locale); $update = (object) array( 'response' => 'upgrade', 'current' => $assoc_args['version'], 'download' => $new_package, 'packages' => (object) array ( 'partial' => null, 'new_bundled' => null, 'no_content' => null, 'full' => $new_package, ), 'version' => $version, 'locale' => $locale ); } if ( ! empty( $update ) && ( $update->version != $wp_version || \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) ) ) { require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); if ( $update->version ) { WP_CLI::log( "Updating to version {$update->version} ({$update->locale})..." ); } else { WP_CLI::log( "Starting update..." ); } $from_version = $wp_version; $GLOBALS['wp_cli_update_obj'] = $update; $result = Utils\get_upgrader( $upgrader )->upgrade( $update ); unset( $GLOBALS['wp_cli_update_obj'] ); if ( is_wp_error($result) ) { $msg = WP_CLI::error_to_string( $result ); if ( 'up_to_date' != $result->get_error_code() ) { WP_CLI::error( $msg ); } else { WP_CLI::success( $msg ); } } else { $to_version = ''; if ( file_exists( ABSPATH . 'wp-includes/version.php' ) ) { $wp_details = self::get_wp_details(); $to_version = $wp_details['wp_version']; } $locale = \WP_CLI\Utils\get_flag_value( $assoc_args, 'locale', get_locale() ); $this->cleanup_extra_files( $from_version, $to_version, $locale ); WP_CLI::success( 'WordPress updated successfully.' ); } } else { WP_CLI::success( 'WordPress is up to date.' ); } } /** * Run the WordPress database update procedure. * * [--network] * : Update databases for all sites on a network * * [--dry-run] * : Compare database versions without performing the update. * * ## EXAMPLES * * # Update the WordPress database * $ wp core update-db * Success: WordPress database upgraded successfully from db version 36686 to 35700. * * # Update databases for all sites on a network * $ wp core update-db --network * WordPress database upgraded successfully from db version 35700 to 29630 on example.com/ * Success: WordPress database upgraded on 123/123 sites * * @subcommand update-db */ function update_db( $_, $assoc_args ) { global $wpdb, $wp_db_version, $wp_current_db_version; $network = Utils\get_flag_value( $assoc_args, 'network' ); if ( $network && ! is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } $dry_run = Utils\get_flag_value( $assoc_args, 'dry-run' ); if ( $dry_run ) { WP_CLI::log( 'Performing a dry run, with no database modification.' ); } if ( $network ) { $iterator_args = array( 'table' => $wpdb->blogs, 'where' => array( 'spam' => 0, 'deleted' => 0, 'archived' => 0 ), ); $it = new \WP_CLI\Iterators\Table( $iterator_args ); $success = $total = 0; $site_ids = array(); foreach( $it as $blog ) { $total++; $site_ids[] = $blog->site_id; $url = $blog->domain . $blog->path; $cmd = "--url={$url} core update-db"; if ( $dry_run ) { $cmd .= ' --dry-run'; } $process = WP_CLI::runcommand( $cmd, array( 'return' => 'all' ) ); if ( 0 == $process->return_code ) { // See if we can parse the stdout if ( preg_match( '#Success: (.+)#', $process->stdout, $matches ) ) { $message = rtrim( $matches[1], '.' ); $message = "{$message} on {$url}"; } else { $message = "Database upgraded successfully on {$url}"; } WP_CLI::log( $message ); $success++; } else { WP_CLI::warning( "Database failed to upgrade on {$url}" ); } } if ( ! $dry_run && $total && $success == $total ) { foreach( array_unique( $site_ids ) as $site_id ) { update_metadata( 'site', $site_id, 'wpmu_upgrade_site', $wp_db_version ); } } WP_CLI::success( sprintf( 'WordPress database upgraded on %d/%d sites.', $success, $total ) ); } else { require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); $wp_current_db_version = __get_option( 'db_version' ); if ( $wp_db_version != $wp_current_db_version ) { if ( $dry_run ) { WP_CLI::success( "WordPress database will be upgraded from db version {$wp_current_db_version} to {$wp_db_version}." ); } else { // WP upgrade isn't too fussy about generating MySQL warnings such as "Duplicate key name" during an upgrade so suppress. $wpdb->suppress_errors(); wp_upgrade(); WP_CLI::success( "WordPress database upgraded successfully from db version {$wp_current_db_version} to {$wp_db_version}." ); } } else { WP_CLI::success( "WordPress database already at latest db version {$wp_db_version}." ); } } } /** * Gets download url based on version, locale and desired file type. * * @param $version * @param string $locale * @param string $file_type * @return string */ private function get_download_url( $version, $locale = 'en_US', $file_type = 'zip' ) { if ( 'nightly' === $version ) { if ( 'zip' === $file_type ) { return 'https://wordpress.org/nightly-builds/wordpress-latest.zip'; } else { WP_CLI::error( 'Nightly builds are only available in .zip format.' ); } } if ( 'en_US' === $locale ) { $url = 'https://wordpress.org/wordpress-' . $version . '.' . $file_type; return $url; } else { $url = sprintf( 'https://%s.wordpress.org/wordpress-%s-%s.' . $file_type, substr($locale, 0, 2), $version, $locale ); return $url; } } /** * Returns update information */ private function get_updates( $assoc_args ) { wp_version_check(); $from_api = get_site_transient( 'update_core' ); if ( ! $from_api ) { return array(); } $compare_version = str_replace( '-src', '', $GLOBALS['wp_version'] ); $updates = array( 'major' => false, 'minor' => false, ); foreach ( $from_api->updates as $offer ) { $update_type = Utils\get_named_sem_ver( $offer->version, $compare_version ); if ( ! $update_type ) { continue; } // WordPress follow its own versioning which is roughly equivalent to semver if ( 'minor' === $update_type ) { $update_type = 'major'; } else if ( 'patch' === $update_type ) { $update_type = 'minor'; } if ( ! empty( $updates[ $update_type ] ) && ! Comparator::greaterThan( $offer->version, $updates[ $update_type ]['version'] ) ) { continue; } $updates[ $update_type ] = array( 'version' => $offer->version, 'update_type' => $update_type, 'package_url' => ! empty( $offer->packages->partial ) ? $offer->packages->partial : $offer->packages->full, ); } foreach( $updates as $type => $value ) { if ( empty( $value ) ) { unset( $updates[ $type ] ); } } foreach( array( 'major', 'minor' ) as $type ) { if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, $type ) ) { return ! empty( $updates[ $type ] ) ? array( $updates[ $type ] ) : false; } } return array_values( $updates ); } private function cleanup_extra_files( $version_from, $version_to, $locale ) { if ( ! $version_from || ! $version_to ) { WP_CLI::warning( 'Failed to find WordPress version. Please cleanup files manually.' ); return; } $old_checksums = self::get_core_checksums( $version_from, $locale ? $locale : 'en_US' ); $new_checksums = self::get_core_checksums( $version_to, $locale ? $locale : 'en_US' ); if ( empty( $old_checksums ) || empty( $new_checksums ) ) { WP_CLI::warning( 'Failed to fetch checksums. Please cleanup files manually.' ); return; } $files_to_remove = array_diff( array_keys( $old_checksums ), array_keys( $new_checksums ) ); if ( ! empty( $files_to_remove ) ) { WP_CLI::log( 'Cleaning up files...' ); $count = 0; foreach ( $files_to_remove as $file ) { // wp-content should be considered user data if ( 0 === stripos( $file, 'wp-content' ) ) { continue; } if ( file_exists( ABSPATH . $file ) ) { unlink( ABSPATH . $file ); WP_CLI::log( 'File removed: ' . $file ); $count++; } } if ( $count ) { WP_CLI::log( number_format( $count ) . ' files cleaned up.' ); } else { WP_CLI::log( 'No files found that need cleaned up.' ); } } } } <?php namespace WP_CLI; /** * A Core Upgrader class that leaves packages intact by default. * * @package wp-cli */ class NonDestructiveCoreUpgrader extends CoreUpgrader { function unpack_package($package, $delete_package = false) { return parent::unpack_package( $package, $delete_package ); } } <?php namespace WP_CLI; use WP_CLI; /** * A Core Upgrader class that caches the download, and uses cached if available * * @package wp-cli */ class CoreUpgrader extends \Core_Upgrader { /** * Caches the download, and uses cached if available. * * @access public * * @param string $package The URI of the package. If this is the full path to an * existing local file, it will be returned untouched. * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object. */ public function download_package( $package ) { /** * Filter whether to return the package. * * @since 3.7.0 * * @param bool $reply Whether to bail without returning the package. Default is false. * @param string $package The package file name. * @param object $this The WP_Upgrader instance. */ $reply = apply_filters( 'upgrader_pre_download', false, $package, $this ); if ( false !== $reply ) { return $reply; } // Check if package is a local or remote file. Bail if it's local. if ( ! preg_match( '!^(http|https|ftp)://!i', $package ) && file_exists( $package ) ) { return $package; } if ( empty( $package ) ) { return new \WP_Error( 'no_package', $this->strings['no_package'] ); } $filename = pathinfo( $package, PATHINFO_FILENAME ); $ext = pathinfo( $package, PATHINFO_EXTENSION ); $temp = \WP_CLI\Utils\get_temp_dir() . uniqid( 'wp_' ) . '.' . $ext; $cache = WP_CLI::get_cache(); $update = $GLOBALS['wp_cli_update_obj']; $cache_key = "core/{$filename}-{$update->locale}.{$ext}"; $cache_file = $cache->has( $cache_key ); if ( $cache_file && false === stripos( $package, 'https://wordpress.org/nightly-builds/' ) ) { WP_CLI::log( "Using cached file '$cache_file'..." ); copy( $cache_file, $temp ); return $temp; } else { /* * Download to a temporary file because piping from cURL to tar is flaky * on MinGW (and probably in other environments too). */ $headers = array( 'Accept' => 'application/json' ); $options = array( 'timeout' => 600, // 10 minutes ought to be enough for everybody. 'filename' => $temp, 'halt_on_error' => false, ); $this->skin->feedback( 'downloading_package', $package ); /** @var \Requests_Response|null $req */ try { $req = Utils\http_request( 'GET', $package, null, $headers, $options ); } catch( \Exception $e ) { return new \WP_Error( 'download_failed', $e->getMessage() ); } if ( ! is_null( $req ) && $req->status_code !== 200 ) { return new \WP_Error( 'download_failed', $this->strings['download_failed'] ); } if ( false === stripos( $package, 'https://wordpress.org/nightly-builds/' ) ) { $cache->import( $cache_key, $temp ); } return $temp; } } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'widget', 'Widget_Command' ); WP_CLI::add_command( 'sidebar', 'Sidebar_Command' ); <?php /** * Lists registered sidebars. * * A [sidebar](https://developer.wordpress.org/themes/functionality/sidebars/) is any widgetized area of your theme. * * ## EXAMPLES * * # List sidebars * $ wp sidebar list --fields=name,id --format=csv * name,id * "Widget Area",sidebar-1 * "Inactive Widgets",wp_inactive_widgets */ class Sidebar_Command extends WP_CLI_Command { private $fields = array( 'name', 'id', 'description' ); /** * List registered sidebars. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - ids * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each sidebar: * * * name * * id * * description * * These fields are optionally available: * * * class * * before_widget * * after_widget * * before_title * * after_title * * ## EXAMPLES * * $ wp sidebar list --fields=name,id --format=csv * name,id * "Widget Area",sidebar-1 * "Inactive Widgets",wp_inactive_widgets * * @subcommand list */ public function list_( $args, $assoc_args ) { global $wp_registered_sidebars; \WP_CLI\Utils\wp_register_unused_sidebar(); if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) { $sidebars = wp_list_pluck( $wp_registered_sidebars, 'id' ); } else { $sidebars = $wp_registered_sidebars; } $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields ); $formatter->display_items( $sidebars ); } } <?php use WP_CLI\Utils; /** * Manages widgets, including adding and moving them within sidebars. * * A [widget](https://developer.wordpress.org/themes/functionality/widgets/) adds content and features to a widget area (also called a [sidebar](https://developer.wordpress.org/themes/functionality/sidebars/)). * * ## EXAMPLES * * # List widgets on a given sidebar * $ wp widget list sidebar-1 * +----------+------------+----------+----------------------+ * | name | id | position | options | * +----------+------------+----------+----------------------+ * | meta | meta-6 | 1 | {"title":"Meta"} | * | calendar | calendar-2 | 2 | {"title":"Calendar"} | * +----------+------------+----------+----------------------+ * * # Add a calendar widget to the second position on the sidebar * $ wp widget add calendar sidebar-1 2 * Success: Added widget to sidebar. * * # Update option(s) associated with a given widget * $ wp widget update calendar-1 --title="Calendar" * Success: Widget updated. * * # Delete one or more widgets entirely * $ wp widget delete calendar-2 archive-1 * Success: 2 widgets removed from sidebar. */ class Widget_Command extends WP_CLI_Command { private $fields = array( 'name', 'id', 'position', 'options', ); /** * List widgets associated with a sidebar. * * ## OPTIONS * * <sidebar-id> * : ID for the corresponding sidebar. * * [--fields=<fields>] * : Limit the output to specific object fields. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each widget: * * * name * * id * * position * * options * * There are no optionally available fields. * * ## EXAMPLES * * $ wp widget list sidebar-1 --fields=name,id --format=csv * name,id * meta,meta-5 * search,search-3 * * @subcommand list */ public function list_( $args, $assoc_args ) { list( $sidebar_id ) = $args; $this->validate_sidebar( $sidebar_id ); $output_widgets = $this->get_sidebar_widgets( $sidebar_id ); if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) { $output_widgets = wp_list_pluck( $output_widgets, 'id' ); } $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields ); $formatter->display_items( $output_widgets ); } /** * Add a widget to a sidebar. * * Creates a new widget entry in the database, and associates it with the * sidebar. * * ## OPTIONS * * <name> * : Widget name. * * <sidebar-id> * : ID for the corresponding sidebar. * * [<position>] * : Widget's current position within the sidebar. Defaults to last * * [--<field>=<value>] * : Widget option to add, with its new value * * ## EXAMPLES * * # Add a new calendar widget to sidebar-1 with title "Calendar" * $ wp widget add calendar sidebar-1 2 --title="Calendar" * Success: Added widget to sidebar. * * @subcommand add */ public function add( $args, $assoc_args ) { list( $name, $sidebar_id ) = $args; $position = \WP_CLI\Utils\get_flag_value( $args, 2, 1 ) - 1; $this->validate_sidebar( $sidebar_id ); if ( false == ( $widget = $this->get_widget_obj( $name ) ) ) { WP_CLI::error( "Invalid widget type." ); } /** * Adding a widget is as easy as: * 1. Creating a new widget option * 2. Adding the widget to the sidebar * 3. Positioning appropriately */ $widget_options = $option_keys = $this->get_widget_options( $name ); if ( ! isset( $widget_options['_multiwidget'] ) ) { $widget_options['_multiwidget'] = 1; } unset( $option_keys['_multiwidget'] ); $option_keys = array_keys( $option_keys ); $last_key = array_pop( $option_keys ); $option_index = $last_key + 1; $widget_options[ $option_index ] = $this->sanitize_widget_options( $name, $assoc_args, array() ); $this->update_widget_options( $name, $widget_options ); $widget_id = $name . '-' . $option_index; $this->move_sidebar_widget( $widget_id, null, $sidebar_id, null, $position ); WP_CLI::success( "Added widget to sidebar." ); } /** * Update options for an existing widget. * * ## OPTIONS * * <widget-id> * : Unique ID for the widget * * [--<field>=<value>] * : Field to update, with its new value * * ## EXAMPLES * * # Change calendar-1 widget title to "Our Calendar" * $ wp widget update calendar-1 --title="Our Calendar" * Success: Widget updated. * * @subcommand update */ public function update( $args, $assoc_args ) { list( $widget_id ) = $args; if ( ! $this->validate_sidebar_widget( $widget_id ) ) { WP_CLI::error( "Widget doesn't exist." ); } if ( empty( $assoc_args ) ) { WP_CLI::error( "No options specified to update." ); } list( $name, $option_index ) = $this->get_widget_data( $widget_id ); $widget_options = $this->get_widget_options( $name ); $clean_options = $this->sanitize_widget_options( $name, $assoc_args, $widget_options[ $option_index ] ); $widget_options[ $option_index ] = array_merge( (array)$widget_options[ $option_index ], $clean_options ); $this->update_widget_options( $name, $widget_options ); WP_CLI::success( "Widget updated." ); } /** * Move the position of a widget. * * Changes the order of a widget in its existing sidebar, or moves it to a * new sidebar. * * ## OPTIONS * * <widget-id> * : Unique ID for the widget * * [--position=<position>] * : Assign the widget to a new position. * * [--sidebar-id=<sidebar-id>] * : Assign the widget to a new sidebar * * ## EXAMPLES * * # Change position of widget * $ wp widget move recent-comments-2 --position=2 * Success: Widget moved. * * # Move widget to Inactive Widgets * $ wp widget move recent-comments-2 --sidebar-id=wp_inactive_widgets * Success: Widget moved. * * @subcommand move */ public function move( $args, $assoc_args ) { list( $widget_id ) = $args; if ( ! $this->validate_sidebar_widget( $widget_id ) ) { WP_CLI::error( "Widget doesn't exist." ); } if ( empty( $assoc_args['position'] ) && empty( $assoc_args['sidebar-id'] ) ) { WP_CLI::error( "A new position or new sidebar must be specified." ); } list( $name, $option_index, $current_sidebar_id, $current_sidebar_index ) = $this->get_widget_data( $widget_id ); $new_sidebar_id = ! empty( $assoc_args['sidebar-id'] ) ? $assoc_args['sidebar-id'] : $current_sidebar_id; $this->validate_sidebar( $new_sidebar_id ); $new_sidebar_index = ! empty( $assoc_args['position'] ) ? $assoc_args['position'] - 1 : $current_sidebar_index; // Moving between sidebars adds to the top if ( $new_sidebar_id != $current_sidebar_id && $new_sidebar_index == $current_sidebar_index ) { // Human-readable positions are different than numerically indexed array $new_sidebar_index = 0; } $this->move_sidebar_widget( $widget_id, $current_sidebar_id, $new_sidebar_id, $current_sidebar_index, $new_sidebar_index ); WP_CLI::success( "Widget moved." ); } /** * Deactivate one or more widgets from an active sidebar. * * Moves widgets to Inactive Widgets. * * ## OPTIONS * * <widget-id>... * : Unique ID for the widget(s) * * ## EXAMPLES * * # Deactivate the recent-comments-2 widget. * $ wp widget deactivate recent-comments-2 * Success: 1 widget deactivated. * * @subcommand deactivate */ public function deactivate( $args, $assoc_args ) { $count = $errors = 0; foreach( $args as $widget_id ) { if ( ! $this->validate_sidebar_widget( $widget_id ) ) { WP_CLI::warning( "Widget '{$widget_id}' doesn't exist." ); $errors++; continue; } list( $name, $option_index, $sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id ); if ( 'wp_inactive_widgets' == $sidebar_id ) { WP_CLI::warning( sprintf( "'%s' is already deactivated.", $widget_id ) ); continue; } $this->move_sidebar_widget( $widget_id, $sidebar_id, 'wp_inactive_widgets', $sidebar_index, 0 ); $count++; } Utils\report_batch_operation_results( 'widget', 'deactivate', count( $args ), $count, $errors ); } /** * Delete one or more widgets from a sidebar. * * ## OPTIONS * * <widget-id>... * : Unique ID for the widget(s) * * ## EXAMPLES * * # Delete the recent-comments-2 widget from its sidebar. * $ wp widget delete recent-comments-2 * Success: Deleted 1 of 1 widgets. * * @subcommand delete */ public function delete( $args, $assoc_args ) { $count = $errors = 0; foreach( $args as $widget_id ) { if ( ! $this->validate_sidebar_widget( $widget_id ) ) { WP_CLI::warning( "Widget '{$widget_id}' doesn't exist." ); $errors++; continue; } // Remove the widget's settings. list( $name, $option_index, $sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id ); $widget_options = $this->get_widget_options( $name ); unset( $widget_options[ $option_index ] ); $this->update_widget_options( $name, $widget_options ); // Remove the widget from the sidebar. $all_widgets = $this->wp_get_sidebars_widgets(); unset( $all_widgets[ $sidebar_id ][ $sidebar_index ] ); $all_widgets[ $sidebar_id ] = array_values( $all_widgets[ $sidebar_id ] ); update_option( 'sidebars_widgets', $all_widgets ); $count++; } Utils\report_batch_operation_results( 'widget', 'delete', count( $args ), $count, $errors ); } /** * Reset sidebar. * * Removes all widgets from the sidebar and places them in Inactive Widgets. * * ## OPTIONS * * [<sidebar-id>...] * : One or more sidebars to reset. * * [--all] * : If set, all sidebars will be reset. * * ## EXAMPLES * * # Reset a sidebar * $ wp widget reset sidebar-1 * Success: Sidebar 'sidebar-1' reset. * * # Reset multiple sidebars * $ wp widget reset sidebar-1 sidebar-2 * Success: Sidebar 'sidebar-1' reset. * Success: Sidebar 'sidebar-2' reset. * * # Reset all sidebars * $ wp widget reset --all * Success: Sidebar 'sidebar-1' reset. * Success: Sidebar 'sidebar-2' reset. * Success: Sidebar 'sidebar-3' reset. */ public function reset( $args, $assoc_args ) { global $wp_registered_sidebars; $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); // Bail if no arguments and no all flag. if ( ! $all && empty( $args ) ) { WP_CLI::error( 'Please specify one or more sidebars, or use --all.' ); } // Fetch all sidebars if all flag is set. if ( $all ) { $args = array_keys( $wp_registered_sidebars ); } // Sidebar ID wp_inactive_widgets is reserved by WP core for inactive widgets. if ( isset( $args['wp_inactive_widgets'] ) ) { unset( $args['wp_inactive_widgets'] ); } // Check if no registered sidebar. if ( empty( $args ) ) { WP_CLI::error( 'No sidebar registered.' ); } $count = $errors = 0; foreach ( $args as $sidebar_id ) { if ( ! array_key_exists( $sidebar_id, $wp_registered_sidebars ) ) { WP_CLI::warning( sprintf( 'Invalid sidebar: %s', $sidebar_id ) ); $errors++; continue; } $widgets = $this->get_sidebar_widgets( $sidebar_id ); if ( empty( $widgets ) ) { WP_CLI::warning( sprintf( "Sidebar '%s' is already empty.", $sidebar_id ) ); } else { foreach ( $widgets as $widget ) { $widget_id = $widget->id; list( $name, $option_index, $new_sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id ); $this->move_sidebar_widget( $widget_id, $new_sidebar_id, 'wp_inactive_widgets', $sidebar_index, 0 ); } WP_CLI::log( sprintf( "Sidebar '%s' reset.", $sidebar_id ) ); $count++; } } Utils\report_batch_operation_results( 'sidebar', 'reset', count( $args ), $count, $errors ); } /** * Check whether a sidebar is a valid sidebar * * @param string $sidebar_id */ private function validate_sidebar( $sidebar_id ) { global $wp_registered_sidebars; \WP_CLI\Utils\wp_register_unused_sidebar(); if ( ! array_key_exists( $sidebar_id, $wp_registered_sidebars ) ) { WP_CLI::error( "Invalid sidebar." ); } } /** * Check whether the specified widget is on the sidebar * * @param string $widget_id */ private function validate_sidebar_widget( $widget_id ) { $sidebars_widgets = $this->wp_get_sidebars_widgets(); $widget_exists = false; foreach( $sidebars_widgets as $sidebar_id => $widgets ) { if ( in_array( $widget_id, $widgets ) ) { $widget_exists = true; break; } } return $widget_exists; } /** * Get the widgets (and their associated data) for a given sidebar * * @param string $sidebar_id * @return array */ private function get_sidebar_widgets( $sidebar_id ) { $all_widgets = $this->wp_get_sidebars_widgets(); if ( empty( $all_widgets[ $sidebar_id ] ) ) { return array(); } $prepared_widgets = array(); foreach( $all_widgets[ $sidebar_id ] as $key => $widget_id ) { $prepared_widget = new stdClass; $parts = explode( '-', $widget_id ); $option_index = array_pop( $parts ); $widget_name = implode( '-', $parts ); $prepared_widget->name = $widget_name; $prepared_widget->id = $widget_id; $prepared_widget->position = $key + 1; $widget_options = get_option( 'widget_' . $widget_name ); $prepared_widget->options = $widget_options[ $option_index ]; $prepared_widgets[] = $prepared_widget; } return $prepared_widgets; } /** * Re-implementation of wp_get_sidebars_widgets() * because the original has a nasty global component */ private function wp_get_sidebars_widgets() { $sidebars_widgets = get_option( 'sidebars_widgets', array() ); if ( is_array( $sidebars_widgets ) && isset( $sidebars_widgets['array_version'] ) ) { unset( $sidebars_widgets['array_version'] ); } return $sidebars_widgets; } /** * Get the widget's name, option index, sidebar, and sidebar index from its ID * * @param string $widget_id * @return array */ private function get_widget_data( $widget_id ) { $parts = explode( '-', $widget_id ); $option_index = array_pop( $parts ); $name = implode( '-', $parts ); $sidebar_id = false; $sidebar_index = false; $all_widgets = $this->wp_get_sidebars_widgets(); foreach( $all_widgets as $s_id => &$widgets ) { if ( false !== ( $key = array_search( $widget_id, $widgets ) ) ) { $sidebar_id = $s_id; $sidebar_index = $key; break; } } return array( $name, $option_index, $sidebar_id, $sidebar_index ); } /** * Get the options for a given widget * * @param string $name * @return array */ private function get_widget_options( $name ) { return get_option( 'widget_' . $name, array() ); } /** * Update the options for a given widget * * @param string $name * @param mixed */ private function update_widget_options( $name, $value ) { update_option( 'widget_' . $name, $value ); } /** * Reposition a widget within a sidebar or move to another sidebar. * * @param string $widget_id * @param string|null $current_sidebar_id * @param string $new_sidebar_id * @param int|null $current_index * @param int $new_index */ private function move_sidebar_widget( $widget_id, $current_sidebar_id, $new_sidebar_id, $current_index, $new_index ) { $all_widgets = $this->wp_get_sidebars_widgets(); $needs_placement = true; // Existing widget if ( $current_sidebar_id && ! is_null( $current_index ) ) { $widgets = $all_widgets[ $current_sidebar_id ]; if ( $current_sidebar_id !== $new_sidebar_id ) { unset( $widgets[ $current_index ] ); } else { $part = array_splice( $widgets, $current_index, 1 ); array_splice( $widgets, $new_index, 0, $part ); $needs_placement = false; } $all_widgets[ $current_sidebar_id ] = array_values( $widgets ); } if ( $needs_placement ) { $widgets = ! empty( $all_widgets[ $new_sidebar_id ] ) ? $all_widgets[ $new_sidebar_id ] : array(); $before = array_slice( $widgets, 0, $new_index, true ); $after = array_slice( $widgets, $new_index, count( $widgets ), true ); $widgets = array_merge( $before, array( $widget_id ), $after ); $all_widgets[ $new_sidebar_id ] = array_values( $widgets ); } update_option( 'sidebars_widgets', $all_widgets ); } /** * Get a widget's instantiated object based on its name * * @param string $id_base Name of the widget * @return WP_Widget|false */ private function get_widget_obj( $id_base ) { global $wp_widget_factory; $widget = wp_filter_object_list( $wp_widget_factory->widgets, array( 'id_base' => $id_base ) ); if ( empty( $widget ) ) { return false; } return array_pop( $widget ); } /** * Clean up a widget's options based on its update callback * * @param string $id_base Name of the widget * @param mixed $dirty_options * @param mixed $old_options * @return mixed */ private function sanitize_widget_options( $id_base, $dirty_options, $old_options ) { $widget = $this->get_widget_obj( $id_base ); if ( empty( $widget ) ) { return array(); } // No easy way to determine expected array keys for $dirty_options // because Widget API dependent on the form fields return @$widget->update( $dirty_options, $old_options ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'config', 'Config_Command' ); <?php use \WP_CLI\Utils; /** * Manage the wp-config.php file */ class Config_Command extends WP_CLI_Command { private static function get_initial_locale() { include ABSPATH . '/wp-includes/version.php'; // @codingStandardsIgnoreStart if ( isset( $wp_local_package ) ) return $wp_local_package; // @codingStandardsIgnoreEnd return ''; } /** * Generate a wp-config.php file. * * Creates a new wp-config.php with database constants, and verifies that * the database constants are correct. * * ## OPTIONS * * --dbname=<dbname> * : Set the database name. * * --dbuser=<dbuser> * : Set the database user. * * [--dbpass=<dbpass>] * : Set the database user password. * * [--dbhost=<dbhost>] * : Set the database host. * --- * default: localhost * --- * * [--dbprefix=<dbprefix>] * : Set the database table prefix. * --- * default: wp_ * --- * * [--dbcharset=<dbcharset>] * : Set the database charset. * --- * default: utf8 * --- * * [--dbcollate=<dbcollate>] * : Set the database collation. * --- * default: * --- * * [--locale=<locale>] * : Set the WPLANG constant. Defaults to $wp_local_package variable. * * [--extra-php] * : If set, the command copies additional PHP code into wp-config.php from STDIN. * * [--skip-salts] * : If set, keys and salts won't be generated, but should instead be passed via `--extra-php`. * * [--skip-check] * : If set, the database connection is not checked. * * [--force] * : Overwrites existing files, if present. * * ## EXAMPLES * * # Standard wp-config.php file * $ wp config create --dbname=testing --dbuser=wp --dbpass=securepswd --locale=ro_RO * Success: Generated 'wp-config.php' file. * * # Enable WP_DEBUG and WP_DEBUG_LOG * $ wp config create --dbname=testing --dbuser=wp --dbpass=securepswd --extra-php <<PHP * $ define( 'WP_DEBUG', true ); * $ define( 'WP_DEBUG_LOG', true ); * $ PHP * Success: Generated 'wp-config.php' file. * * # Avoid disclosing password to bash history by reading from password.txt * # Using --prompt=dbpass will prompt for the 'dbpass' argument * $ wp config create --dbname=testing --dbuser=wp --prompt=dbpass < password.txt * Success: Generated 'wp-config.php' file. */ public function create( $_, $assoc_args ) { global $wp_version; if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) && Utils\locate_wp_config() ) { WP_CLI::error( "The 'wp-config.php' file already exists." ); } $versions_path = ABSPATH . 'wp-includes/version.php'; include $versions_path; $defaults = array( 'dbhost' => 'localhost', 'dbpass' => '', 'dbprefix' => 'wp_', 'dbcharset' => 'utf8', 'dbcollate' => '', 'locale' => self::get_initial_locale() ); $assoc_args = array_merge( $defaults, $assoc_args ); if ( preg_match( '|[^a-z0-9_]|i', $assoc_args['dbprefix'] ) ) WP_CLI::error( '--dbprefix can only contain numbers, letters, and underscores.' ); // Check DB connection if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-check' ) ) { Utils\run_mysql_command( '/usr/bin/env mysql --no-defaults', array( 'execute' => ';', 'host' => $assoc_args['dbhost'], 'user' => $assoc_args['dbuser'], 'pass' => $assoc_args['dbpass'], ) ); } if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'extra-php' ) === true ) { $assoc_args['extra-php'] = file_get_contents( 'php://stdin' ); } if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-salts' ) ) { try { $assoc_args['keys-and-salts'] = true; $assoc_args['auth-key'] = self::unique_key(); $assoc_args['secure-auth-key'] = self::unique_key(); $assoc_args['logged-in-key'] = self::unique_key(); $assoc_args['nonce-key'] = self::unique_key(); $assoc_args['auth-salt'] = self::unique_key(); $assoc_args['secure-auth-salt'] = self::unique_key(); $assoc_args['logged-in-salt'] = self::unique_key(); $assoc_args['nonce-salt'] = self::unique_key(); } catch ( Exception $e ) { $assoc_args['keys-and-salts'] = false; $assoc_args['keys-and-salts-alt'] = self::_read( 'https://api.wordpress.org/secret-key/1.1/salt/' ); } } if ( \WP_CLI\Utils\wp_version_compare( '4.0', '<' ) ) { $assoc_args['add-wplang'] = true; } else { $assoc_args['add-wplang'] = false; } $command_root = Utils\phar_safe_path( dirname( __DIR__ ) ); $out = Utils\mustache_render( $command_root . '/templates/wp-config.mustache', $assoc_args ); $bytes_written = file_put_contents( ABSPATH . 'wp-config.php', $out ); if ( ! $bytes_written ) { WP_CLI::error( "Could not create new 'wp-config.php' file." ); } else { WP_CLI::success( "Generated 'wp-config.php' file." ); } } /** * Get the path to wp-config.php file. * * ## EXAMPLES * * # Get wp-config.php file path * $ wp config path * /home/person/htdocs/project/wp-config.php * * @when before_wp_load */ public function path() { $path = Utils\locate_wp_config(); if ( $path ) { WP_CLI::line( $path ); } else { WP_CLI::error( "'wp-config.php' not found." ); } } /** * Get variables, constants, and file includes defined in wp-config.php file. * * ## OPTIONS * * [--fields=<fields>] * : Limit the output to specific fields. Defaults to all fields. * * [--constant=<constant>] * : Returns the value of a specific constant defined in the wp-config.php file. * * [--global=<global>] * : Returns the value of a specific global defined in the wp-config.php file. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # List variables and constants defined in wp-config.php file. * $ wp config get --format=table * +------------------+------------------------------------------------------------------+----------+ * | key | value | type | * +------------------+------------------------------------------------------------------+----------+ * | table_prefix | wp_ | variable | * | DB_NAME | wp_cli_test | constant | * | DB_USER | root | constant | * | DB_PASSWORD | root | constant | * | AUTH_KEY | r6+@shP1yO&$)1gdu.hl[/j;7Zrvmt~o;#WxSsa0mlQOi24j2cR,7i+QM/#7S:o^ | constant | * | SECURE_AUTH_KEY | iO-z!_m--YH$Tx2tf/&V,YW*13Z_HiRLqi)d?$o-tMdY+82pK$`T.NYW~iTLW;xp | constant | * +------------------+------------------------------------------------------------------+----------+ * * @when before_wp_load */ public function get( $_, $assoc_args ) { $default_fields = array( 'key', 'value', 'type', ); $defaults = array( 'fields' => implode( ',', $default_fields ), 'format' => 'table', ); $assoc_args = array_merge( $defaults, $assoc_args ); $path = Utils\locate_wp_config(); if ( ! $path ) { WP_CLI::error( "'wp-config.php' not found." ); } $wp_cli_original_defined_constants = get_defined_constants(); $wp_cli_original_defined_vars = get_defined_vars(); $wp_cli_original_includes = get_included_files(); eval( WP_CLI::get_runner()->get_wp_config_code() ); $wp_config_vars = self::get_wp_config_vars( get_defined_vars(), $wp_cli_original_defined_vars, 'variable', array( 'wp_cli_original_defined_vars' ) ); $wp_config_constants = self::get_wp_config_vars( get_defined_constants(), $wp_cli_original_defined_constants, 'constant' ); foreach ( $wp_config_vars as $key => $value ) { if ( 'wp_cli_original_includes' === $value['key'] ) { $key_backup = $key; break; } } unset( $wp_config_vars[ $key_backup ] ); $wp_config_vars = array_values( $wp_config_vars ); $wp_config_includes = array_diff( get_included_files(), $wp_cli_original_includes ); $wp_config_includes_array = array(); foreach ( $wp_config_includes as $key => $value ) { $wp_config_includes_array[] = array( 'key' => basename( $value ), 'value' => $value, 'type' => 'includes', ); } $get_constant = ! empty( $assoc_args['constant'] ); $get_global = ! empty( $assoc_args['global'] ); if ( $get_constant && $get_global ) { WP_CLI::error( 'Cannot request the value of a constant and a global at the same time.' ); } if ( $get_constant || $get_global ) { $value = $this->return_constant_or_global( $assoc_args, $get_constant, $wp_config_constants, $wp_config_vars ); WP_CLI::log( $value ); return; } WP_CLI\Utils\format_items( $assoc_args['format'], array_merge( $wp_config_vars, $wp_config_constants, $wp_config_includes_array ), $assoc_args['fields'] ); } /** * Filter wp-config.php file configurations. * * @param array $list * @param array $previous_list * @param array $exclude_list * @return array */ private static function get_wp_config_vars( $list, $previous_list, $type, $exclude_list = array() ) { $result = array(); foreach ( $list as $key => $val ) { if ( array_key_exists( $key, $previous_list ) || in_array( $key, $exclude_list ) ) { continue; } $out = array(); $out['key'] = $key; $out['value'] = $val; $out['type'] = $type; $result[] = $out; } return $result; } private static function _read( $url ) { $headers = array('Accept' => 'application/json'); $response = Utils\http_request( 'GET', $url, null, $headers, array( 'timeout' => 30 ) ); if ( 200 === $response->status_code ) { return $response->body; } else { WP_CLI::error( "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." ); } } /** * Prints the value of a constant or global defined in the wp-config.php file. * * If the constant or global is not defined in the wp-config.php file then an error will be returned. * * @param array $assoc_args * @param bool $get_constant * @param array $wp_config_constants * @param array $wp_config_vars * * @return string The value of the requested constant or global as defined in the wp-config.php file; if the * requested constant or global is not defined then the function will print an error and exit. */ private function return_constant_or_global( $assoc_args, $get_constant, $wp_config_constants, $wp_config_vars ) { if ( $get_constant ) { $key = $assoc_args['constant']; $type = 'constant'; $look_into = $wp_config_constants; } else { $key = $assoc_args['global']; $type = 'global'; $look_into = $wp_config_vars; } $keys = array_column( $look_into, 'key' ); if ( false !== $index = array_search( $key, $keys ) ) { return $look_into[ $index ]['value']; } $candidate = Utils\get_suggestion( $key, $keys ); if ( empty( $candidate ) ) { WP_CLI::error( "The '{$key}' {$type} is not defined in the wp-config.php file." ); } elseif ( $candidate !== $key ) { WP_CLI::error( "The '{$key}' {$type} is not defined in the wp-config.php file.\nDid you mean '{$candidate}'?" ); } return $look_into[ $candidate ]; } /** * Generate a unique key/salt for the wp-config.php file. * * @throws Exception * * @return string */ private static function unique_key() { if ( ! function_exists( 'random_int' ) ) { throw new Exception( "'random_int' does not exist" ); } $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|'; $key = ''; for ( $i = 0; $i < 64; $i++ ) { $key .= substr( $chars, random_int( 0, strlen( $chars ) - 1 ), 1 ); } return $key; } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'search-replace', 'Search_Replace_Command' ); <?php namespace WP_CLI; class SearchReplacer { private $from, $to; private $recurse_objects; private $regex; private $regex_flags; private $regex_delimiter; private $logging; private $log_data; private $max_recursion; /** * @param string $from String we're looking to replace. * @param string $to What we want it to be replaced with. * @param bool $recurse_objects Should objects be recursively replaced? * @param bool $regex Whether `$from` is a regular expression. * @param string $regex_flags Flags for regular expression. * @param string $regex_delimiter Delimiter for regular expression. * @param bool $logging Whether logging. */ function __construct( $from, $to, $recurse_objects = false, $regex = false, $regex_flags = '', $regex_delimiter = '/', $logging = false ) { $this->from = $from; $this->to = $to; $this->recurse_objects = $recurse_objects; $this->regex = $regex; $this->regex_flags = $regex_flags; $this->regex_delimiter = $regex_delimiter; $this->logging = $logging; $this->clear_log_data(); // Get the XDebug nesting level. Will be zero (no limit) if no value is set $this->max_recursion = intval( ini_get( 'xdebug.max_nesting_level' ) ); } /** * Take a serialised array and unserialise it replacing elements as needed and * unserialising any subordinate arrays and performing the replace on those too. * Ignores any serialized objects unless $recurse_objects is set to true. * * @param array|string $data The data to operate on. * @param bool $serialised Does the value of $data need to be unserialized? * * @return array The original array with all elements replaced as needed. */ function run( $data, $serialised = false ) { return $this->_run( $data, $serialised ); } /** * @param int $recursion_level Current recursion depth within the original data. * @param array $visited_data Data that has been seen in previous recursion iterations. */ private function _run( $data, $serialised, $recursion_level = 0, $visited_data = array() ) { // some unseriliased data cannot be re-serialised eg. SimpleXMLElements try { if ( $this->recurse_objects ) { // If we've reached the maximum recursion level, short circuit if ( $this->max_recursion != 0 && $recursion_level >= $this->max_recursion ) { return $data; } if ( is_array( $data ) || is_object( $data ) ) { // If we've seen this exact object or array before, short circuit if ( in_array( $data, $visited_data, true ) ) { return $data; // Avoid infinite loops when there's a cycle } // Add this data to the list of $visited_data[] = $data; } } if ( is_string( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) { $data = $this->_run( $unserialized, true, $recursion_level + 1 ); } elseif ( is_array( $data ) ) { $keys = array_keys( $data ); foreach ( $keys as $key ) { $data[ $key ]= $this->_run( $data[$key], false, $recursion_level + 1, $visited_data ); } } elseif ( $this->recurse_objects && is_object( $data ) ) { foreach ( $data as $key => $value ) { $data->$key = $this->_run( $value, false, $recursion_level + 1, $visited_data ); } } else if ( is_string( $data ) ) { if ( $this->logging ) { $old_data = $data; } if ( $this->regex ) { $search_regex = $this->regex_delimiter; $search_regex .= $this->from; $search_regex .= $this->regex_delimiter; $search_regex .= $this->regex_flags; $data = preg_replace( $search_regex, $this->to, $data ); } else { $data = str_replace( $this->from, $this->to, $data ); } if ( $this->logging && $old_data !== $data ) { $this->log_data[] = $old_data; } } if ( $serialised ) return serialize( $data ); } catch( Exception $error ) { } return $data; } /** * Gets existing data saved for this run when logging. * @return array Array of data strings, prior to replacements. */ public function get_log_data() { return $this->log_data; } /** * Clears data stored for logging. */ public function clear_log_data() { $this->log_data = array(); } } <?php class Search_Replace_Command extends WP_CLI_Command { private $dry_run; private $export_handle = false; private $export_insert_size; private $recurse_objects; private $regex; private $regex_flags; private $regex_delimiter; private $skip_columns; private $include_columns; private $format; private $report; private $report_changed_only; private $log_handle = null; private $log_before_context = 40; private $log_after_context = 40; private $log_prefixes = array( '< ', '> ' ); private $log_colors; private $log_encoding; private $log_run_data = array(); /** * Search/replace strings in the database. * * Searches through all rows in a selection of tables and replaces * appearances of the first string with the second string. * * By default, the command uses tables registered to the `$wpdb` object. On * multisite, this will just be the tables for the current site unless * `--network` is specified. * * Search/replace intelligently handles PHP serialized data, and does not * change primary key values. * * ## OPTIONS * * <old> * : A string to search for within the database. * * <new> * : Replace instances of the first string with this new string. * * [<table>...] * : List of database tables to restrict the replacement to. Wildcards are * supported, e.g. `'wp_*options'` or `'wp_post*'`. * * [--dry-run] * : Run the entire search/replace operation and show report, but don't save * changes to the database. * * [--network] * : Search/replace through all the tables registered to $wpdb in a * multisite install. * * [--all-tables-with-prefix] * : Enable replacement on any tables that match the table prefix even if * not registered on $wpdb. * * [--all-tables] * : Enable replacement on ALL tables in the database, regardless of the * prefix, and even if not registered on $wpdb. Overrides --network * and --all-tables-with-prefix. * * [--export[=<file>]] * : Write transformed data as SQL file instead of saving replacements to * the database. If <file> is not supplied, will output to STDOUT. * * [--export_insert_size=<rows>] * : Define number of rows in single INSERT statement when doing SQL export. * You might want to change this depending on your database configuration * (e.g. if you need to do fewer queries). Default: 50 * * [--skip-columns=<columns>] * : Do not perform the replacement on specific columns. Use commas to * specify multiple columns. * * [--include-columns=<columns>] * : Perform the replacement on specific columns. Use commas to * specify multiple columns. * * [--precise] * : Force the use of PHP (instead of SQL) which is more thorough, * but slower. * * [--recurse-objects] * : Enable recursing into objects to replace strings. Defaults to true; * pass --no-recurse-objects to disable. * * [--verbose] * : Prints rows to the console as they're updated. * * [--regex] * : Runs the search using a regular expression (without delimiters). * Warning: search-replace will take about 15-20x longer when using --regex. * * [--regex-flags=<regex-flags>] * : Pass PCRE modifiers to regex search-replace (e.g. 'i' for case-insensitivity). * * [--regex-delimiter=<regex-delimiter>] * : The delimiter to use for the regex. It must be escaped if it appears in the search string. The default value is the result of `chr(1)`. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - count * --- * * [--report] * : Produce report. Defaults to true. * * [--report-changed-only] * : Report changed fields only. Defaults to false, unless logging, when it defaults to true. * * [--log[=<file>]] * : Log the items changed. If <file> is not supplied or is "-", will output to STDOUT. * Warning: causes a significant slow down, similar or worse to enabling --precise or --regex. * * [--before_context=<num>] * : For logging, number of characters to display before the old match and the new replacement. Default 40. Ignored if not logging. * * [--after_context=<num>] * : For logging, number of characters to display after the old match and the new replacement. Default 40. Ignored if not logging. * * ## EXAMPLES * * # Search and replace but skip one column * $ wp search-replace 'http://example.dev' 'http://example.com' --skip-columns=guid * * # Run search/replace operation but dont save in database * $ wp search-replace 'foo' 'bar' wp_posts wp_postmeta wp_terms --dry-run * * # Run case-insensitive regex search/replace operation (slow) * $ wp search-replace '\[foo id="([0-9]+)"' '[bar id="\1"' --regex --regex-flags='i' * * # Turn your production multisite database into a local dev database * $ wp search-replace --url=example.com example.com example.dev 'wp_*options' wp_blogs * * # Search/replace to a SQL file without transforming the database * $ wp search-replace foo bar --export=database.sql * * # Bash script: Search/replace production to development url (multisite compatible) * #!/bin/bash * if $(wp --url=http://example.com core is-installed --network); then * wp search-replace --url=http://example.com 'http://example.com' 'http://example.dev' --recurse-objects --network --skip-columns=guid * else * wp search-replace 'http://example.com' 'http://example.dev' --recurse-objects --skip-columns=guid * fi */ public function __invoke( $args, $assoc_args ) { global $wpdb; $old = array_shift( $args ); $new = array_shift( $args ); $total = 0; $report = array(); $this->dry_run = \WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run' ); $php_only = \WP_CLI\Utils\get_flag_value( $assoc_args, 'precise' ); $this->recurse_objects = \WP_CLI\Utils\get_flag_value( $assoc_args, 'recurse-objects', true ); $this->verbose = \WP_CLI\Utils\get_flag_value( $assoc_args, 'verbose' ); $this->format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ); if ( ( $this->regex = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex', false ) ) ) { $this->regex_flags = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex-flags', false ); $default_regex_delimiter = false; $this->regex_delimiter = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex-delimiter', '' ); if ( '' === $this->regex_delimiter ) { $this->regex_delimiter = chr( 1 ); $default_regex_delimiter = true; } } if ( ! empty( $this->regex ) ) { if ( '' === $this->regex_delimiter ) { $this->regex_delimiter = chr( 1 ); } $search_regex = $this->regex_delimiter; $search_regex .= $old; $search_regex .= $this->regex_delimiter; $search_regex .= $this->regex_flags; if ( false === @preg_match( $search_regex, '' ) ) { if ( $default_regex_delimiter ) { $flags_msg = $this->regex_flags ? "flags '$this->regex_flags'" : "no flags"; $msg = "The regex pattern '$old' with default delimiter 'chr(1)' and {$flags_msg} fails."; } else { $msg = "The regex '$search_regex' fails."; } WP_CLI::error( $msg ); } } $this->skip_columns = explode( ',', \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-columns' ) ); $this->include_columns = array_filter( explode( ',', \WP_CLI\Utils\get_flag_value( $assoc_args, 'include-columns' ) ) ); if ( $old === $new && ! $this->regex ) { WP_CLI::warning( "Replacement value '{$old}' is identical to search value '{$new}'. Skipping operation." ); exit; } if ( null !== ( $export = \WP_CLI\Utils\get_flag_value( $assoc_args, 'export' ) ) ) { if ( $this->dry_run ) { WP_CLI::error( 'You cannot supply --dry-run and --export at the same time.' ); } if ( true === $export ) { $this->export_handle = STDOUT; $this->verbose = false; } else { $this->export_handle = @fopen( $assoc_args['export'], 'w' ); if ( false === $this->export_handle ) { $error = error_get_last(); WP_CLI::error( sprintf( 'Unable to open export file "%s" for writing: %s.', $assoc_args['export'], $error['message'] ) ); } } $export_insert_size = WP_CLI\Utils\get_flag_value( $assoc_args, 'export_insert_size', 50 ); if ( (int) $export_insert_size == $export_insert_size && $export_insert_size > 0 ) { $this->export_insert_size = $export_insert_size; } $php_only = true; } if ( null !== ( $log = \WP_CLI\Utils\get_flag_value( $assoc_args, 'log' ) ) ) { if ( true === $log || '-' === $log ) { $this->log_handle = STDOUT; } else { $this->log_handle = @fopen( $assoc_args['log'], 'w' ); if ( false === $this->log_handle ) { $error = error_get_last(); WP_CLI::error( sprintf( 'Unable to open log file "%s" for writing: %s.', $assoc_args['log'], $error['message'] ) ); } } if ( $this->log_handle ) { if ( null !== ( $before_context = \WP_CLI\Utils\get_flag_value( $assoc_args, 'before_context' ) ) && preg_match( '/^[0-9]+$/', $before_context ) ) { $this->log_before_context = (int) $before_context; } if ( null !== ( $after_context = \WP_CLI\Utils\get_flag_value( $assoc_args, 'after_context' ) ) && preg_match( '/^[0-9]+$/', $after_context ) ) { $this->log_after_context = (int) $after_context; } if ( false !== ( $log_prefixes = getenv( 'WP_CLI_SEARCH_REPLACE_LOG_PREFIXES' ) ) && preg_match( '/^([^,]*),([^,]*)$/', $log_prefixes, $matches ) ) { $this->log_prefixes = array( $matches[1], $matches[2] ); } if ( STDOUT === $this->log_handle ) { $default_log_colors = array( 'log_table_column_id' => '%B', 'log_old' => '%R', 'log_new' => '%G' ); } else { $default_log_colors = array( 'log_table_column_id' => '', 'log_old' => '', 'log_new' => '' ); } if ( false !== ( $log_colors = getenv( 'WP_CLI_SEARCH_REPLACE_LOG_COLORS' ) ) && preg_match( '/^([^,]*),([^,]*),([^,]*)$/', $log_colors, $matches ) ) { $default_log_colors = array( 'log_table_column_id' => $matches[1], 'log_old' => $matches[2], 'log_new' => $matches[3] ); } $this->log_colors = self::get_colors( $assoc_args, $default_log_colors ); $this->log_encoding = 0 === strpos( $wpdb->charset, 'utf8' ) ? 'UTF-8' : false; } } $this->report = \WP_CLI\Utils\get_flag_value( $assoc_args, 'report', true ); // Defaults to true if logging, else defaults to false. $this->report_changed_only = \WP_CLI\Utils\get_flag_value( $assoc_args, 'report-changed-only', null !== $this->log_handle ); if ( $this->regex_flags ) { $php_only = true; } // never mess with hashed passwords $this->skip_columns[] = 'user_pass'; // Get table names based on leftover $args or supplied $assoc_args $tables = \WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ); foreach ( $tables as $table ) { $table_sql = self::esc_sql_ident( $table ); if ( $this->export_handle ) { fwrite( $this->export_handle, "\nDROP TABLE IF EXISTS $table_sql;\n" ); $row = $wpdb->get_row( "SHOW CREATE TABLE $table_sql", ARRAY_N ); fwrite( $this->export_handle, $row[1] . ";\n" ); list( $table_report, $total_rows ) = $this->php_export_table( $table, $old, $new ); if ( $this->report ) { $report = array_merge( $report, $table_report ); } $total += $total_rows; // Don't perform replacements on the actual database continue; } list( $primary_keys, $columns, $all_columns ) = self::get_columns( $table ); // since we'll be updating one row at a time, // we need a primary key to identify the row if ( empty( $primary_keys ) ) { if ( $this->report ) { $report[] = array( $table, '', 'skipped', '' ); } else { WP_CLI::warning( $all_columns ? "No primary keys for table '$table'." : "No such table '$table'." ); } continue; } foreach ( $columns as $col ) { if ( ! empty( $this->include_columns ) && ! in_array( $col, $this->include_columns ) ) { continue; } if ( in_array( $col, $this->skip_columns ) ) { continue; } if ( $this->verbose && 'count' !== $this->format ) { $this->start_time = microtime( true ); WP_CLI::log( sprintf( 'Checking: %s.%s', $table, $col ) ); } if ( ! $php_only && ! $this->regex ) { $col_sql = self::esc_sql_ident( $col ); $wpdb->last_error = ''; $serialRow = $wpdb->get_row( "SELECT * FROM $table_sql WHERE $col_sql REGEXP '^[aiO]:[1-9]' LIMIT 1" ); // When the regex triggers an error, we should fall back to PHP if ( false !== strpos( $wpdb->last_error, 'ERROR 1139' ) ) { $serialRow = true; } } if ( $php_only || $this->regex || NULL !== $serialRow ) { $type = 'PHP'; $count = $this->php_handle_col( $col, $primary_keys, $table, $old, $new ); } else { $type = 'SQL'; $count = $this->sql_handle_col( $col, $primary_keys, $table, $old, $new ); } if ( $this->report && ( $count || ! $this->report_changed_only ) ) { $report[] = array( $table, $col, $count, $type ); } $total += $count; } } if ( $this->export_handle && STDOUT !== $this->export_handle ) { fclose( $this->export_handle ); } // Only informational output after this point if ( WP_CLI::get_config( 'quiet' ) || STDOUT === $this->export_handle ) { return; } if ( 'count' === $this->format ) { WP_CLI::line( $total ); return; } if ( $this->report ) { $table = new \cli\Table(); $table->setHeaders( array( 'Table', 'Column', 'Replacements', 'Type' ) ); $table->setRows( $report ); $table->display(); } if ( ! $this->dry_run ) { if ( ! empty( $assoc_args['export'] ) ) { $success_message = 1 === $total ? "Made 1 replacement and exported to {$assoc_args['export']}." : "Made {$total} replacements and exported to {$assoc_args['export']}."; } else { $success_message = 1 === $total ? "Made 1 replacement." : "Made $total replacements."; if ( $total && 'Default' !== WP_CLI\Utils\wp_get_cache_type() ) { $success_message .= ' Please remember to flush your persistent object cache with `wp cache flush`.'; if ( is_multisite() ) { $success_message .= ' If you see a "Site not found" error after replacing a domain, try flushing cache against the old domain (which may be the cached lookup value).'; } } } WP_CLI::success( $success_message ); } else { $success_message = ( 1 === $total ) ? '%d replacement to be made.' : '%d replacements to be made.'; WP_CLI::success( sprintf( $success_message, $total ) ); } } private function php_export_table( $table, $old, $new ) { list( $primary_keys, $columns, $all_columns ) = self::get_columns( $table ); $chunk_size = getenv( 'BEHAT_RUN' ) ? 10 : 1000; $args = array( 'table' => $table, 'fields' => $all_columns, 'chunk_size' => $chunk_size ); $replacer = new \WP_CLI\SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter ); $col_counts = array_fill_keys( $all_columns, 0 ); if ( $this->verbose && 'table' === $this->format ) { $this->start_time = microtime( true ); WP_CLI::log( sprintf( 'Checking: %s', $table ) ); } $rows = array(); foreach ( new \WP_CLI\Iterators\Table( $args ) as $i => $row ) { $row_fields = array(); foreach( $all_columns as $col ) { $value = $row->$col; if ( $value && ! in_array( $col, $primary_keys ) && ! in_array( $col, $this->skip_columns ) ) { $new_value = $replacer->run( $value ); if ( $new_value !== $value ) { $col_counts[ $col ]++; $value = $new_value; } } $row_fields[ $col ] = $value; } $rows[] = $row_fields; } $this->write_sql_row_fields( $table, $rows ); $table_report = array(); $total_rows = $total_cols = 0; foreach ( $col_counts as $col => $col_count ) { if ( $this->report && ( $col_count || ! $this->report_changed_only ) ) { $table_report[] = array( $table, $col, $col_count, 'PHP' ); } if ( $col_count ) { $total_cols++; $total_rows += $col_count; } } if ( $this->verbose && 'table' === $this->format ) { $time = round( microtime( true ) - $this->start_time, 3 ); WP_CLI::log( sprintf( '%d columns and %d total rows affected using PHP (in %ss).', $total_cols, $total_rows, $time ) ); } return array( $table_report, $total_rows ); } private function sql_handle_col( $col, $primary_keys, $table, $old, $new ) { global $wpdb; $table_sql = self::esc_sql_ident( $table ); $col_sql = self::esc_sql_ident( $col ); if ( $this->dry_run ) { if ( $this->log_handle ) { $count = $this->log_sql_diff( $col, $primary_keys, $table, $old, $new ); } else { $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT($col_sql) FROM $table_sql WHERE $col_sql LIKE BINARY %s;", '%' . self::esc_like( $old ) . '%' ) ); } } else { if ( $this->log_handle ) { $this->log_sql_diff( $col, $primary_keys, $table, $old, $new ); } $count = $wpdb->query( $wpdb->prepare( "UPDATE $table_sql SET $col_sql = REPLACE($col_sql, %s, %s);", $old, $new ) ); } if ( $this->verbose && 'table' === $this->format ) { $time = round( microtime( true ) - $this->start_time, 3 ); WP_CLI::log( sprintf( '%d rows affected using SQL (in %ss).', $count, $time ) ); } return $count; } private function php_handle_col( $col, $primary_keys, $table, $old, $new ) { global $wpdb; $count = 0; $replacer = new \WP_CLI\SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter, null !== $this->log_handle ); $table_sql = self::esc_sql_ident( $table ); $col_sql = self::esc_sql_ident( $col ); $where = $this->regex ? '' : " WHERE $col_sql" . $wpdb->prepare( ' LIKE BINARY %s', '%' . self::esc_like( $old ) . '%' ); $primary_keys_sql = implode( ',', self::esc_sql_ident( $primary_keys ) ); $rows = $wpdb->get_results( "SELECT {$primary_keys_sql} FROM {$table_sql} {$where}" ); foreach ( $rows as $keys ) { $where_sql = ''; foreach( (array) $keys as $k => $v ) { if ( strlen( $where_sql ) ) { $where_sql .= ' AND '; } $where_sql .= self::esc_sql_ident( $k ) . ' = ' . esc_sql( $v ); } $col_value = $wpdb->get_var( "SELECT {$col_sql} FROM {$table_sql} WHERE {$where_sql}" ); if ( '' === $col_value ) continue; $value = $replacer->run( $col_value ); if ( $value === $col_value ) { continue; } if ( $this->log_handle ) { $this->log_php_diff( $col, $keys, $table, $old, $new, $replacer->get_log_data() ); $replacer->clear_log_data(); } if ( $this->dry_run ) { if ( $value != $col_value ) $count++; } else { $where = array(); foreach( (array) $keys as $k => $v ) { $where[ $k ] = $v; } $count += $wpdb->update( $table, array( $col => $value ), $where ); } } if ( $this->verbose && 'table' === $this->format ) { $time = round( microtime( true ) - $this->start_time, 3 ); WP_CLI::log( sprintf( '%d rows affected using PHP (in %ss).', $count, $time ) ); } return $count; } private function write_sql_row_fields( $table, $rows ) { global $wpdb; if(empty($rows)) { return; } $table_sql = self::esc_sql_ident( $table ); $insert = "INSERT INTO $table_sql ("; $insert .= join( ', ', self::esc_sql_ident( array_keys( $rows[0] ) ) ); $insert .= ') VALUES '; $insert .= "\n"; $sql = $insert; $values = array(); $index = 1; $count = count( $rows ); $export_insert_size = $this->export_insert_size; foreach($rows as $row_fields) { $sql .= '(' . join( ', ', array_fill( 0, count( $row_fields ), '%s' ) ) . ')'; $values = array_merge( $values, array_values( $row_fields ) ); // Add new insert statement if needed. Before this we close the previous with semicolon and write statement to sql-file. // "Statement break" is needed: // 1. When the loop is running every nth time (where n is insert statement size, $export_index_size). Remainder is zero also on first round, so it have to be excluded. // $index % $export_insert_size == 0 && $index > 0 // 2. Or when the loop is running last time // $index == $count if( ( $index % $export_insert_size == 0 && $index > 0 ) || $index == $count ) { $sql .= ";\n"; if( method_exists( $wpdb, 'remove_placeholder_escape' ) ) { // since 4.8.3 $sql = $wpdb->remove_placeholder_escape( $wpdb->prepare( $sql, array_values( $values ) ) ); } else { // 4.8.2 or less $sql = $wpdb->prepare( $sql, array_values( $values ) ); } fwrite( $this->export_handle, $sql ); // If there is still rows to loop, reset $sql and $values variables. if( $count > $index ) { $sql = $insert; $values = array(); } } else { // Otherwise just add comma and new line $sql .= ",\n"; } $index++; } } private static function get_columns( $table ) { global $wpdb; $table_sql = self::esc_sql_ident( $table ); $primary_keys = $text_columns = $all_columns = array(); $suppress_errors = $wpdb->suppress_errors(); if ( ( $results = $wpdb->get_results( "DESCRIBE $table_sql" ) ) ) { foreach ( $results as $col ) { if ( 'PRI' === $col->Key ) { $primary_keys[] = $col->Field; } if ( self::is_text_col( $col->Type ) ) { $text_columns[] = $col->Field; } $all_columns[] = $col->Field; } } $wpdb->suppress_errors( $suppress_errors ); return array( $primary_keys, $text_columns, $all_columns ); } private static function is_text_col( $type ) { foreach ( array( 'text', 'varchar' ) as $token ) { if ( false !== strpos( $type, $token ) ) return true; } return false; } private static function esc_like( $old ) { global $wpdb; // Remove notices in 4.0 and support backwards compatibility if( method_exists( $wpdb, 'esc_like' ) ) { // 4.0 $old = $wpdb->esc_like( $old ); } else { // 3.9 or less $old = like_escape( esc_sql( $old ) ); // Note: this double escaping is actually necessary, even though `esc_like()` will be used in a `prepare()`. } return $old; } /** * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names. * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html * * @param string|array $idents A single identifier or an array of identifiers. * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings. */ private static function esc_sql_ident( $idents ) { $backtick = function ( $v ) { // Escape any backticks in the identifier by doubling. return '`' . str_replace( '`', '``', $v ) . '`'; }; if ( is_string( $idents ) ) { return $backtick( $idents ); } return array_map( $backtick, $idents ); } /** * Gets the color codes from the options if any, and returns the passed in array colorized with 2 elements per entry, a color code (or '') and a reset (or ''). * * @param array $assoc_args The associative argument array passed to the command. * @param array $colors Array of default percent color code strings keyed by the color contexts. * @return array Array containing 2-element arrays keyed to the input $colors array. */ private function get_colors( $assoc_args, $colors ) { $color_reset = WP_CLI::colorize( '%n' ); $color_codes = implode( '', array_map( function ( $v ) { return substr( $v, 1 ); }, array_keys( \cli\Colors::getColors() ) ) ); $color_codes_regex = '/^(?:%[' . $color_codes . '])*$/'; foreach ( array_keys( $colors ) as $color_col ) { if ( null !== ( $col_color_flag = \WP_CLI\Utils\get_flag_value( $assoc_args, $color_col . '_color' ) ) ) { if ( ! preg_match( $color_codes_regex, $col_color_flag, $matches ) ) { WP_CLI::warning( "Unrecognized percent color code '$col_color_flag' for '{$color_col}_color'." ); } else { $colors[ $color_col ] = $matches[0]; } } $colors[ $color_col ] = $colors[ $color_col ] ? array( WP_CLI::colorize( $colors[ $color_col ] ), $color_reset ) : array( '', '' ); } return $colors; } /* * Logs the difference between old match and new replacement for SQL replacement. * * @param string $col Column being processed. * @param array $primary_keys Primary keys for table. * @param string $table Table being processed. * @param string $old Old value to match. * @param string $new New value to replace the old value with. * @return int Count of changed rows. */ private function log_sql_diff( $col, $primary_keys, $table, $old, $new ) { global $wpdb; if ( $primary_keys ) { $esc_primary_keys = implode( ', ', self::esc_sql_ident( $primary_keys ) ); $primary_keys_sql = count( $primary_keys ) > 1 ? "CONCAT_WS(',', {$esc_primary_keys}), " : "{$esc_primary_keys}, "; } else { $primary_keys_sql = ''; } if ( ! ( $results = $wpdb->get_results( $wpdb->prepare( "SELECT {$primary_keys_sql}`$col` FROM `$table` WHERE `$col` LIKE BINARY %s", '%' . self::esc_like( $old ) . '%' ), ARRAY_N ) ) ) { return 0; } $search_regex = '/' . preg_quote( $old, '/' ) . '/'; foreach ( $results as $result ) { list( $keys, $data ) = $primary_keys ? array( $result[0], $result[1] ) : array( null, $result[0] ); if ( preg_match_all( $search_regex, $data, $matches, PREG_OFFSET_CAPTURE ) ) { list( $old_bits, $new_bits ) = $this->log_bits( $search_regex, $data, $matches, $new ); $this->log_write( $col, $keys, $table, $old_bits, $new_bits ); } } return count( $results ); } /* * Logs the difference between old matches and new replacements at the end of a PHP (regex) replacement of a database row. * * @param string $col Column being processed. * @param array $keys Associative array (or object) of primary key names and their values for the row being processed. * @param string $table Table being processed. * @param string $old Old value to match. * @param string $new New value to replace the old value with. * @param array $log_data Array of data strings before replacements. */ private function log_php_diff( $col, $keys, $table, $old, $new, $log_data ) { if ( $this->regex ) { $search_regex = $this->regex_delimiter . $old . $this->regex_delimiter . $this->regex_flags; } else { $search_regex = '/' . preg_quote( $old, '/' ) . '/'; } $old_bits = $new_bits = array(); foreach ( $log_data as $data ) { if ( preg_match_all( $search_regex, $data, $matches, PREG_OFFSET_CAPTURE ) ) { $bits = $this->log_bits( $search_regex, $data, $matches, $new ); $old_bits = array_merge( $old_bits, $bits[0] ); $new_bits = array_merge( $new_bits, $bits[1] ); } } if ( $old_bits ) { $this->log_write( $col, $keys, $table, $old_bits, $new_bits ); } } /** * Returns the arrays of old matches and new replacements based on the passed-in matches, with context. * * @param string $search_regex The search regular expression. * @param string $old_data Existing data being processed. * @param array $old_matches Old matches array returned by `preg_match_all()`. * @param string $new New value to replace the old value with. * @return array Two element array containing the array of old match log strings and the array of new replacement log strings with before/after contexts. */ private function log_bits( $search_regex, $old_data, $old_matches, $new ) { $encoding = $this->log_encoding; if ( ! $encoding && ( $this->log_before_context || $this->log_after_context ) && function_exists( 'mb_detect_encoding' ) ) { $encoding = mb_detect_encoding( $old_data, null, true /*strict*/ ); } // Generate a new data matches analog of the old data matches by simulating a `preg_replace()`. $is_regex = $this->regex; $i = $diff = 0; $new_matches = array(); $new_data = preg_replace_callback( $search_regex, function ( $matches ) use ( $old_matches, $new, $is_regex, &$new_matches, &$i, &$diff ) { if ( $is_regex ) { // Sub in any back references, "$1", "\2" etc, in the replacement string. $new = preg_replace_callback( '/(?<!\\\\)(?:\\\\\\\\)*((?:\\\\|\\$)[0-9]{1,2}|\\${[0-9]{1,2}\\})/', function ( $m ) use ( $matches ) { $idx = (int) str_replace( array( '\\', '$', '{', '}' ), '', $m[0] ); return isset( $matches[ $idx ] ) ? $matches[ $idx ] : ''; }, $new ); $new = str_replace( '\\\\', '\\', $new ); // Unescape any backslashed backslashes. } $new_matches[0][ $i ][0] = $new; $new_matches[0][ $i ][1] = $old_matches[0][ $i ][1] + $diff; $diff += strlen( $new ) - strlen( $old_matches[0][ $i ][0] ); $i++; return $new; }, $old_data ); $old_bits = $new_bits = array(); $append_next = false; $last_old_offset = $last_new_offset = 0; $match_cnt = count( $old_matches[0] ); for ( $i = 0; $i < $match_cnt; $i++ ) { $old_match = $old_matches[0][ $i ][0]; $old_offset = $old_matches[0][ $i ][1]; $new_match = $new_matches[0][ $i ][0]; $new_offset = $new_matches[0][ $i ][1]; $old_log = $this->log_colors['log_old'][0] . $old_match . $this->log_colors['log_old'][1]; $new_log = $this->log_colors['log_new'][0] . $new_match . $this->log_colors['log_new'][1]; $old_before = $old_after = $new_before = $new_after = ''; $after_shortened = false; // Offsets are in bytes, so need to use `strlen()` and `substr()` before using `safe_substr()`. if ( $this->log_before_context && $old_offset && ! $append_next ) { $old_before = \cli\safe_substr( substr( $old_data, $last_old_offset, $old_offset - $last_old_offset ), -$this->log_before_context, null /*length*/, false /*is_width*/, $encoding ); $new_before = \cli\safe_substr( substr( $new_data, $last_new_offset, $new_offset - $last_new_offset ), -$this->log_before_context, null /*length*/, false /*is_width*/, $encoding ); } if ( $this->log_after_context ) { $old_end_offset = $old_offset + strlen( $old_match ); $new_end_offset = $new_offset + strlen( $new_match ); $old_after = \cli\safe_substr( substr( $old_data, $old_end_offset ), 0, $this->log_after_context, false /*is_width*/, $encoding ); $new_after = \cli\safe_substr( substr( $new_data, $new_end_offset ), 0, $this->log_after_context, false /*is_width*/, $encoding ); // To lessen context duplication in output, shorten the after context if it overlaps with the next match. if ( $i + 1 < $match_cnt && $old_end_offset + strlen( $old_after ) > $old_matches[0][ $i + 1 ][1] ) { $old_after = substr( $old_after, 0, $old_matches[0][ $i + 1 ][1] - $old_end_offset ); $new_after = substr( $new_after, 0, $new_matches[0][ $i + 1 ][1] - $new_end_offset ); $after_shortened = true; // On the next iteration, will append with no before context. } } if ( $append_next ) { $cnt = count( $old_bits ); $old_bits[ $cnt - 1 ] .= $old_log . $old_after; $new_bits[ $cnt - 1 ] .= $new_log . $new_after; } else { $old_bits[] = $old_before . $old_log . $old_after; $new_bits[] = $new_before . $new_log . $new_after; } $append_next = $after_shortened; $last_old_offset = $old_offset; $last_new_offset = $new_offset; } return array( $old_bits, $new_bits ); } /* * Output the log strings. * * @param string $col Column being processed. * @param array $keys Associative array (or object) of primary key names and their values for the row being processed. * @param string $table Table being processed. * @param array $old_bits Array of old match log strings. * @param array $new_bits Array of new replacement log strings. */ private function log_write( $col, $keys, $table, $old_bits, $new_bits ) { $id_log = $keys ? ( ':' . implode( ',', (array) $keys ) ) : ''; $table_column_id_log = $this->log_colors['log_table_column_id'][0] . $table . '.' . $col . $id_log . $this->log_colors['log_table_column_id'][1]; $old_log = str_replace( array( "\r\n", "\n" ), ' ', implode( ' [...] ', $old_bits ) ); $new_log = str_replace( array( "\r\n", "\n" ), ' ', implode( ' [...] ', $new_bits ) ); if ( $this->log_prefixes[0] ) { $old_log = $this->log_colors['log_old'][0] . $this->log_prefixes[0] . $this->log_colors['log_old'][1] . $old_log; } if ( $this->log_prefixes[1] ) { $new_log = $this->log_colors['log_new'][0] . $this->log_prefixes[1] . $this->log_colors['log_new'][1] . $new_log; } fwrite( $this->log_handle, "{$table_column_id_log}\n{$old_log}\n{$new_log}\n" ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'cache', 'Cache_Command' ); WP_CLI::add_command( 'transient', 'Transient_Command' ); <?php /** * Manipulate the WordPress Transient Cache. * * By default, the transient cache uses the WordPress database to persist values * between requests. When a persistent object cache drop-in is installed, the * transient cache also uses the WordPress Object Cache. * * ## EXAMPLES * * # Set transient. * $ wp transient set sample_key "test data" 3600 * Success: Transient added. * * # Get transient. * $ wp transient get sample_key * test data * * # Delete transient. * $ wp transient delete sample_key * Success: Transient deleted. * * # Delete expired transients. * $ wp transient delete --expired * Success: 12 expired transients deleted from the database. * * # Delete all transients. * $ wp transient delete --all * Success: 14 transients deleted from the database. */ class Transient_Command extends WP_CLI_Command { /** * Get a transient value. * * ## OPTIONS * * <key> * : Key for the transient. * * [--format=<format>] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * [--network] * : Get the value of the network transient, instead of the single site. * * ## EXAMPLES * * $ wp transient get sample_key * test data * * $ wp transient get random_key * Warning: Transient with key "random_key" is not set. */ public function get( $args, $assoc_args ) { list( $key ) = $args; $func = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; $value = $func( $key ); if ( false === $value ) { WP_CLI::warning( 'Transient with key "' . $key . '" is not set.' ); exit; } WP_CLI::print_value( $value, $assoc_args ); } /** * Set a transient value. * * `<expiration>` is the time until expiration, in seconds. * * ## OPTIONS * * <key> * : Key for the transient. * * <value> * : Value to be set for the transient. * * [<expiration>] * : Time until expiration, in seconds. * * [--network] * : Set the transient value on the network, instead of single site. * * ## EXAMPLES * * $ wp transient set sample_key "test data" 3600 * Success: Transient added. */ public function set( $args, $assoc_args ) { list( $key, $value ) = $args; $expiration = \WP_CLI\Utils\get_flag_value( $args, 2, 0 ); $func = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ) ? 'set_site_transient' : 'set_transient'; if ( $func( $key, $value, $expiration ) ) { WP_CLI::success( 'Transient added.' ); } else { WP_CLI::error( 'Transient could not be set.' ); } } /** * Delete a transient value. * * ## OPTIONS * * [<key>] * : Key for the transient. * * [--network] * : Delete the value of a network transient, instead of that on a single site. * * [--all] * : Delete all transients. * * [--expired] * : Delete all expired transients. * * ## EXAMPLES * * # Delete transient. * $ wp transient delete sample_key * Success: Transient deleted. * * # Delete expired transients. * $ wp transient delete --expired * Success: 12 expired transients deleted from the database. * * # Delete all transients. * $ wp transient delete --all * Success: 14 transients deleted from the database. */ public function delete( $args, $assoc_args ) { $key = ( ! empty( $args ) ) ? $args[0] : NULL; $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ); $expired = \WP_CLI\Utils\get_flag_value( $assoc_args, 'expired' ); if ( true === $all ) { $this->delete_all(); return; } else if ( true === $expired ) { $this->delete_expired(); return; } if ( ! $key ) { WP_CLI::error( 'Please specify transient key, or use --all or --expired.' ); } $func = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ) ? 'delete_site_transient' : 'delete_transient'; if ( $func( $key ) ) { WP_CLI::success( 'Transient deleted.' ); } else { $func = \WP_CLI\Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; if ( $func( $key ) ) WP_CLI::error( 'Transient was not deleted even though the transient appears to exist.' ); else WP_CLI::warning( 'Transient was not deleted; however, the transient does not appear to exist.' ); } } /** * Determine type of transients implementation. * * Indicates whether the transients API is using an object cache or the * options table. * * ## EXAMPLES * * $ wp transient type * Transients are saved to the wp_options table. */ public function type() { global $_wp_using_ext_object_cache, $wpdb; if ( $_wp_using_ext_object_cache ) $message = 'Transients are saved to the object cache.'; else $message = 'Transients are saved to the ' . $wpdb->prefix . 'options table.'; WP_CLI::line( $message ); } /** * Delete all expired transients. */ private function delete_expired() { global $wpdb, $_wp_using_ext_object_cache; // Always delete all transients from DB too. $time = current_time('timestamp'); $count = $wpdb->query( "DELETE a, b FROM $wpdb->options a, $wpdb->options b WHERE a.option_name LIKE '\_transient\_%' AND a.option_name NOT LIKE '\_transient\_timeout\_%' AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) ) AND b.option_value < $time" ); if ( $count > 0 ) { WP_CLI::success( "$count expired transients deleted from the database." ); } else { WP_CLI::success( "No expired transients found." ); } if ( $_wp_using_ext_object_cache ) { WP_CLI::warning( 'Transients are stored in an external object cache, and this command only deletes those stored in the database. You must flush the cache to delete all transients.'); } } /** * Delete all transients. */ private function delete_all() { global $wpdb, $_wp_using_ext_object_cache; // Always delete all transients from DB too. $count = $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'" ); if ( $count > 0 ) { WP_CLI::success( "$count transients deleted from the database." ); } else { WP_CLI::success( "No transients found." ); } if ( $_wp_using_ext_object_cache ) { WP_CLI::warning( 'Transients are stored in an external object cache, and this command only deletes those stored in the database. You must flush the cache to delete all transients.'); } } } <?php /** * Manipulate the WP Object Cache object. * * By default, the WP Object Cache exists in PHP memory for the length of the * request (and is emptied at the end). Use a persistent object cache drop-in * to persist the object cache between requests. * * [Read the codex article](https://codex.wordpress.org/Class_Reference/WP_Object_Cache) * for more detail. * * ## EXAMPLES * * # Set cache. * $ wp cache set my_key my_value my_group 300 * Success: Set object 'my_key' in group 'my_group'. * * # Get cache. * $ wp cache get my_key my_group * my_value * * @package wp-cli */ class Cache_Command extends WP_CLI_Command { /** * Add a value to the object cache. * * Errors if a value already exists for the key, which means the value can't * be added. * * ## OPTIONS * * <key> * : Cache key. * * <value> * : Value to add to the key. * * [<group>] * : Method for grouping data within the cache which allows the same key to be used across groups. * --- * default: default * --- * * [<expiration>] * : Define how long to keep the value, in seconds. `0` means as long as possible. * --- * default: 0 * --- * * ## EXAMPLES * * # Add cache. * $ wp cache add my_key my_group my_value 300 * Success: Added object 'my_key' in group 'my_value'. */ public function add( $args, $assoc_args ) { list( $key, $value, $group, $expiration ) = $args; if ( ! wp_cache_add( $key, $value, $group, $expiration ) ) { WP_CLI::error( "Could not add object '$key' in group '$group'. Does it already exist?" ); } WP_CLI::success( "Added object '$key' in group '$group'." ); } /** * Decrement a value in the object cache. * * Errors if the value can't be decremented. * * ## OPTIONS * * <key> * : Cache key. * * [<offset>] * : The amount by which to decrement the item's value. * --- * default: 1 * --- * * [<group>] * : Method for grouping data within the cache which allows the same key to be used across groups. * --- * default: default * --- * * ## EXAMPLES * * # Decrease cache value. * $ wp cache decr my_key 2 my_group * 48 */ public function decr( $args, $assoc_args ) { list( $key, $offset, $group ) = $args; $value = wp_cache_decr( $key, $offset, $group ); if ( false === $value ) { WP_CLI::error( 'The value was not decremented.' ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Remove a value from the object cache. * * Errors if the value can't be deleted. * * ## OPTIONS * * <key> * : Cache key. * * [<group>] * : Method for grouping data within the cache which allows the same key to be used across groups. * --- * default: default * --- * * ## EXAMPLES * * # Delete cache. * $ wp cache delete my_key my_group * Success: Object deleted. */ public function delete( $args, $assoc_args ) { list( $key, $group ) = $args; $result = wp_cache_delete( $key, $group ); if ( false === $result ) { WP_CLI::error( 'The object was not deleted.' ); } WP_CLI::success( 'Object deleted.' ); } /** * Flush the object cache. * * For WordPress multisite instances using a persistent object cache, * flushing the object cache will typically flush the cache for all sites. * Beware of the performance impact when flushing the object cache in * production. * * Errors if the object cache can't be flushed. * * ## EXAMPLES * * # Flush cache. * $ wp cache flush * Success: The cache was flushed. */ public function flush( $args, $assoc_args ) { $value = wp_cache_flush(); if ( false === $value ) { WP_CLI::error( 'The object cache could not be flushed.' ); } WP_CLI::success( 'The cache was flushed.' ); } /** * Get a value from the object cache. * * Errors if the value doesn't exist. * * ## OPTIONS * * <key> * : Cache key. * * [<group>] * : Method for grouping data within the cache which allows the same key to be used across groups. * --- * default: default * --- * * ## EXAMPLES * * # Get cache. * $ wp cache get my_key my_group * my_value */ public function get( $args, $assoc_args ) { list( $key, $group ) = $args; $value = wp_cache_get( $key, $group ); if ( false === $value ) { WP_CLI::error( "Object with key '$key' and group '$group' not found." ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Increment a value in the object cache. * * Errors if the value can't be incremented. * * ## OPTIONS * * <key> * : Cache key. * * [<offset>] * : The amount by which to increment the item's value. * --- * default: 1 * --- * * [<group>] * : Method for grouping data within the cache which allows the same key to be used across groups. * --- * default: default * --- * * ## EXAMPLES * * # Increase cache value. * $ wp cache incr my_key 2 my_group * 50 */ public function incr( $args, $assoc_args ) { list( $key, $offset, $group ) = $args; $value = wp_cache_incr( $key, $offset, $group ); if ( false === $value ) { WP_CLI::error( 'The value was not incremented.' ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Replace a value in the object cache, if the value already exists. * * Errors if the value can't be replaced. * * ## OPTIONS * * <key> * : Cache key. * * <value> * : Value to replace. * * [<group>] * : Method for grouping data within the cache which allows the same key to be used across groups. * --- * default: default * --- * * [<expiration>] * : Define how long to keep the value, in seconds. `0` means as long as possible. * --- * default: 0 * --- * * ## EXAMPLES * * # Replace cache. * $ wp cache replace my_key new_value my_group * Success: Replaced object 'my_key' in group 'my_group'. */ public function replace( $args, $assoc_args ) { list( $key, $value, $group, $expiration ) = $args; $result = wp_cache_replace( $key, $value, $group, $expiration ); if ( false === $result ) { WP_CLI::error( "Could not replace object '$key' in group '$group'. Does it not exist?" ); } WP_CLI::success( "Replaced object '$key' in group '$group'." ); } /** * Set a value to the object cache, regardless of whether it already exists. * * Errors if the value can't be set. * * ## OPTIONS * * <key> * : Cache key. * * <value> * : Value to set on the key. * * [<group>] * : Method for grouping data within the cache which allows the same key to be used across groups. * --- * default: default * --- * * [<expiration>] * : Define how long to keep the value, in seconds. `0` means as long as possible. * --- * default: 0 * --- * * ## EXAMPLES * * # Set cache. * $ wp cache set my_key my_value my_group 300 * Success: Set object 'my_key' in group 'my_group'. */ public function set( $args, $assoc_args ) { list( $key, $value, $group, $expiration ) = $args; $result = wp_cache_set( $key, $value, $group, $expiration ); if ( false === $result ) { WP_CLI::error( "Could not add object '$key' in group '$group'." ); } WP_CLI::success( "Set object '$key' in group '$group'." ); } /** * Attempts to determine which object cache is being used. * * Note that the guesses made by this function are based on the * WP_Object_Cache classes that define the 3rd party object cache extension. * Changes to those classes could render problems with this function's * ability to determine which object cache is being used. * * ## EXAMPLES * * # Check cache type. * $ wp cache type * Default */ public function type( $args, $assoc_args ) { $message = WP_CLI\Utils\wp_get_cache_type(); WP_CLI::line( $message ); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'super-admin', 'Super_Admin_Command', array( 'before_invoke' => function () { if ( !is_multisite() ) { WP_CLI::error( 'This is not a multisite install.' ); } } ) ); <?php /** * Lists, adds, or removes super admin users on a multisite install. * * ## EXAMPLES * * # List user with super-admin capabilities * $ wp super-admin list * supervisor * administrator * * # Grant super-admin privileges to the user. * $ wp super-admin add superadmin2 * Success: Granted super-admin capabilities. * * # Revoke super-admin privileges to the user. * $ wp super-admin remove superadmin2 * Success: Revoked super-admin capabilities. * * @package wp-cli */ class Super_Admin_Command extends WP_CLI_Command { private $fields = array( 'user_login' ); public function __construct() { $this->fetcher = new \WP_CLI\Fetchers\User; } /** * List users with super admin capabilities. * * ## OPTIONS * * [--format=<format>] * : Render output in a particular format. * --- * default: list * options: * - list * - table * - csv * - json * - count * - yaml * --- * * ## EXAMPLES * * # List user with super-admin capabilities * $ wp super-admin list * supervisor * administrator * * @subcommand list */ public function _list( $_, $assoc_args ) { $super_admins = self::get_admins(); if ( 'list' === $assoc_args['format'] ) { foreach ( $super_admins as $user_login ) { WP_CLI::line( $user_login ); } } else { $output_users = array(); foreach ( $super_admins as $user_login ) { $output_user = new stdClass; $output_user->user_login = $user_login; $output_users[] = $output_user; } $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields ); $formatter->display_items( $output_users ); } } /** * Grant super admin privileges to one or more users. * * ## OPTIONS * * <user>... * : One or more user IDs, user emails, or user logins. * * ## EXAMPLES * * $ wp super-admin add superadmin2 * Success: Granted super-admin capabilities. */ public function add( $args, $_ ) { $successes = $errors = 0; $users = $this->fetcher->get_many( $args ); if ( count( $users ) != count( $args ) ) { $errors = count( $args ) - count( $users ); } $user_logins = wp_list_pluck( $users, 'user_login' ); $super_admins = self::get_admins(); $num_super_admins = count( $super_admins ); foreach ( $user_logins as $user_login ) { if ( in_array( $user_login, $super_admins ) ) { WP_CLI::warning( "User '{$user_login}' already has super-admin capabilities." ); continue; } $super_admins[] = $user_login; $successes++; } if ( $num_super_admins === count( $super_admins ) ) { if ( $errors ) { $user_count = count( $args ); WP_CLI::error( "Couldn't grant super-admin capabilities to {$errors} of {$user_count} users." ); } else { WP_CLI::success( 'Super admins remain unchanged.' ); } } else { if ( update_site_option( 'site_admins' , $super_admins ) ) { if ( $errors ) { $user_count = count( $args ); WP_CLI::error( "Only granted super-admin capabilities to {$successes} of {$user_count} users." ); } else { $message = $successes > 1 ? 'users' : 'user'; WP_CLI::success( "Granted super-admin capabilities to {$successes} {$message}." ); } } else { WP_CLI::error( 'Site options update failed.' ); } } } /** * Remove super admin privileges from one or more users. * * ## OPTIONS * * <user>... * : One or more user IDs, user emails, or user logins. * * ## EXAMPLES * * $ wp super-admin remove superadmin2 * Success: Revoked super-admin capabilities. */ public function remove( $args, $_ ) { $super_admins = self::get_admins(); if ( ! $super_admins ) { WP_CLI::error( 'No super admins to revoke super-admin privileges from.' ); } $users = $this->fetcher->get_many( $args ); $user_logins = $users ? array_values( array_unique( wp_list_pluck( $users, 'user_login' ) ) ) : array(); $user_logins_count = count( $user_logins ); if ( $user_logins_count < count( $args ) ) { $flipped_user_logins = array_flip( $user_logins ); // Fetcher has already warned so don't bother here, but continue with any args that are possible login names to cater for invalid users in the site options meta. $user_logins = array_merge( $user_logins, array_unique( array_filter( $args, function ( $v ) use ( $flipped_user_logins ) { // Exclude numeric and email-like logins (login names can be email-like but ignore this given the circumstances). return ! isset( $flipped_user_logins[ $v ] ) && ! is_numeric( $v ) && ! is_email( $v ); } ) ) ); $user_logins_count = count( $user_logins ); } if ( ! $user_logins ) { WP_CLI::error( 'No valid user logins given to revoke super-admin privileges from.' ); } $update_super_admins = array_diff( $super_admins, $user_logins ); if ( $update_super_admins === $super_admins ) { WP_CLI::error( $user_logins_count > 1 ? 'None of the given users is a super admin.' : 'The given user is not a super admin.' ); } update_site_option( 'site_admins' , $update_super_admins ); $successes = count( $super_admins ) - count( $update_super_admins ); if ( $successes === $user_logins_count ) { $message = $user_logins_count > 1 ? 'users' : 'user'; $msg = "Revoked super-admin capabilities from {$user_logins_count} {$message}."; } else { $msg = "Revoked super-admin capabilities from {$successes} of {$user_logins_count} users."; } if ( ! $update_super_admins ) { $msg .= ' There are no remaining super admins.'; } WP_CLI::success( $msg ); } private static function get_admins() { // We don't use get_super_admins() because we don't want to mess with the global return get_site_option( 'site_admins', array('admin') ); } } <?php function wp_export( $args = array() ) { $defaults = array( 'filters' => array(), 'format' => 'WP_Export_WXR_Formatter', 'writer' => 'WP_Export_Returner', 'writer_args' => null, ); $args = wp_parse_args( $args, $defaults ); $export_query = new WP_Export_Query( $args['filters'] ); $formatter = new $args['format']( $export_query ); $writer = new $args['writer']( $formatter, $args['writer_args'] ); try { return $writer->export(); } catch ( WP_Export_Exception $e ) { return new WP_Error( 'wp-export-error', $e->getMessage() ); } } function wp_export_new_style_args_from_old_style_args( $args ) { if ( isset( $args['content'] ) ) { if ( 'all' == $args['content'] ) { unset( $args['content'] ); } else { $args['post_type'] = $args['content']; } } return $args; } // TEMPORARY function _wp_export_build_IN_condition( $column_name, $values, $format = '%s' ) { global $wpdb; if ( !is_array( $values ) || empty( $values ) ) { return ''; } $formats = implode( ', ', array_fill( 0, count( $values ), $format ) ); return $wpdb->prepare( "$column_name IN ($formats)", $values ); } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'export', 'Export_Command' ); <?php class WP_Export_Exception extends RuntimeException { } <?php class WP_Export_Oxymel extends Oxymel { public function optional( $tag_name, $contents ) { if ( $contents ) { $this->$tag_name( $contents ); } return $this; } public function optional_cdata( $tag_name, $contents ) { if ( $contents ) { $this->$tag_name->contains->cdata( $contents )->end; } return $this; } public function cdata( $text ) { if ( !seems_utf8( $text ) ) { $text = utf8_encode( $text ); } return parent::cdata( $text ); } } <?php define( 'WP_CLI_EXPORT_COMMAND_NO_SPLIT', '-1' ); /** * Export WordPress content to a WXR file. * * ## EXAMPLES * * # Export posts published by the user between given start and end date * $ wp export --dir=/tmp/ --user=admin --post_type=post --start_date=2011-01-01 --end_date=2011-12-31 * Starting export process... * Writing to file /tmp/staging.wordpress.2016-05-24.000.xml * Success: All done with export. * * @package wp-cli */ class Export_Command extends WP_CLI_Command { /** * Initialize the array of arguments that will be eventually be passed to export_wp. * * @var array */ public $export_args = array(); private $stdout; private $max_file_size; private $wxr_path; /** * Export WordPress content to a WXR file. * * Generates one or more WXR files containing authors, terms, posts, * comments, and attachments. WXR files do not include site configuration * (options) or the attachment files themselves. * * ## OPTIONS * * [--dir=<dirname>] * : Full path to directory where WXR export files should be stored. Defaults * to current working directory. * * [--stdout] * : Output the whole XML using standard output (incompatible with --dir=) * * [--skip_comments] * : Don't include comments in the WXR export file. * * [--max_file_size=<MB>] * : A single export file should have this many megabytes. -1 for unlimited. * --- * default: 15 * --- * * ## FILTERS * * [--start_date=<date>] * : Export only posts published after this date, in format YYYY-MM-DD. * * [--end_date=<date>] * : Export only posts published before this date, in format YYYY-MM-DD. * * [--post_type=<post-type>] * : Export only posts with this post_type. Separate multiple post types with a * comma. * --- * default: any * --- * * [--post_type__not_in=<post-type>] * : Export all post types except those identified. Separate multiple post types * with a comma. Defaults to none. * * [--post__in=<pid>] * : Export all posts specified as a comma- or space-separated list of IDs. * * [--start_id=<pid>] * : Export only posts with IDs greater than or equal to this post ID. * * [--max_num_posts=<num>] * : Export no more than <num> posts (excluding attachments). * * [--author=<author>] * : Export only posts by this author. Can be either user login or user ID. * * [--category=<name>] * : Export only posts in this category. * * [--post_status=<status>] * : Export only posts with this status. * * [--filename_format=<format>] * : Use a custom format for export filenames. Defaults to '{site}.wordpress.{date}.{n}.xml'. * * ## EXAMPLES * * # Export posts published by the user between given start and end date * $ wp export --dir=/tmp/ --user=admin --post_type=post --start_date=2011-01-01 --end_date=2011-12-31 * Starting export process... * Writing to file /tmp/staging.wordpress.2016-05-24.000.xml * Success: All done with export. * * # Export posts by IDs * $ wp export --dir=/tmp/ --post__in=123,124,125 * Starting export process... * Writing to file /tmp/staging.wordpress.2016-05-24.000.xml * Success: All done with export. * * # Export a random subset of content * $ wp export --post__in="$(wp post list --post_type=post --orderby=rand --posts_per_page=8 --format=ids)" * Starting export process... * Writing to file /var/www/example.com/public_html/staging.wordpress.2016-05-24.000.xml * Success: All done with export. */ public function __invoke( $_, $assoc_args ) { $defaults = array( 'dir' => NULL, 'stdout' => FALSE, 'start_date' => NULL, 'end_date' => NULL, 'post_type' => NULL, 'post_type__not_in' => NULL, 'max_num_posts' => NULL, 'author' => NULL, 'category' => NULL, 'post_status' => NULL, 'post__in' => NULL, 'start_id' => NULL, 'skip_comments' => NULL, 'max_file_size' => 15, 'filename_format' => '{site}.wordpress.{date}.{n}.xml', ); if (! empty( $assoc_args['stdout'] ) && ( ! empty( $assoc_args['dir'] ) || ! empty( $assoc_args['filename_format'] ) ) ) { WP_CLI::error( '--stdout and --dir cannot be used together.' ); } $assoc_args = wp_parse_args( $assoc_args, $defaults ); $this->validate_args( $assoc_args ); if ( !function_exists( 'wp_export' ) ) { self::load_export_api(); } if ( ! $this->stdout ) { WP_CLI::log( 'Starting export process...' ); } add_action( 'wp_export_new_file', function( $file_path ) { WP_CLI::log( sprintf( "Writing to file %s", $file_path ) ); WP_CLI\Utils\wp_clear_object_cache(); } ); try { if ( $this->stdout ) { wp_export( array( 'filters' => $this->export_args, 'writer' => 'WP_Export_Stdout_Writer', 'writer_args' => NULL ) ); } else { wp_export( array( 'filters' => $this->export_args, 'writer' => 'WP_Export_Split_Files_Writer', 'writer_args' => array( 'max_file_size' => $this->max_file_size, 'destination_directory' => $this->wxr_path, 'filename_template' => self::get_filename_template( $assoc_args['filename_format'] ), ) ) ); } } catch ( Exception $e ) { WP_CLI::error( $e->getMessage() ); } if ( ! $this->stdout ) { WP_CLI::success( 'All done with export.' ); } } private static function get_filename_template( $filename_format ) { $sitename = sanitize_key( get_bloginfo( 'name' ) ); if ( empty( $sitename ) ) { $sitename = 'site'; } return str_replace( array( '{site}', '{date}', '{n}' ), array( $sitename, date( 'Y-m-d' ), '%03d' ), $filename_format ); } private static function load_export_api() { require dirname( dirname( __FILE__ ) ) . '/functions.php'; } private function validate_args( $args ) { $has_errors = false; foreach ( $args as $key => $value ) { if ( is_callable( array( $this, 'check_' . $key ) ) ) { $result = call_user_func( array( $this, 'check_' . $key ), $value ); if ( false === $result ) $has_errors = true; } } if ( $args['stdout'] ) { $this->wxr_path = NULL; $this->stdout = TRUE; } if ( $has_errors ) { WP_CLI::halt(1); } } private function check_dir( $path ) { if ( empty( $path ) ) { $path = getcwd(); } elseif ( !is_dir( $path ) ) { WP_CLI::error( sprintf( "The directory '%s' does not exist.", $path ) ); return false; } $this->wxr_path = trailingslashit( $path ); return true; } private function check_start_date( $date ) { if ( is_null( $date ) ) return true; $time = strtotime( $date ); if ( !empty( $date ) && !$time ) { WP_CLI::warning( sprintf( "The start_date %s is invalid.", $date ) ); return false; } $this->export_args['start_date'] = date( 'Y-m-d', $time ); return true; } private function check_end_date( $date ) { if ( is_null( $date ) ) return true; $time = strtotime( $date ); if ( !empty( $date ) && !$time ) { WP_CLI::warning( sprintf( "The end_date %s is invalid.", $date ) ); return false; } $this->export_args['end_date'] = date( 'Y-m-d', $time ); return true; } private function check_post_type( $post_type ) { if ( is_null( $post_type ) || 'any' === $post_type ) return true; $post_type = array_unique( array_filter( explode( ',', $post_type ) ) ); $post_types = get_post_types(); foreach ( $post_type as $type ) { if ( ! in_array( $type, $post_types ) ) { WP_CLI::warning( sprintf( 'The post type %s does not exist. Choose "any" or any of these existing post types instead: %s', $type, implode( ", ", $post_types ) ) ); return false; } } $this->export_args['post_type'] = $post_type; return true; } private function check_post_type__not_in( $post_type ) { if ( is_null( $post_type ) ) { return true; } $post_type = array_unique( array_filter( explode( ',', $post_type ) ) ); $post_types = get_post_types(); $keep_post_types = array(); foreach ( $post_type as $type ) { if ( ! in_array( $type, $post_types ) ) { WP_CLI::warning( sprintf( 'The post type %s does not exist. Use any of these existing post types instead: %s', $type, implode( ", ", $post_types ) ) ); return false; } } $this->export_args['post_type'] = array_diff( $post_types, $post_type ); return true; } private function check_post__in( $post__in ) { if ( is_null( $post__in ) ) return true; $separator = false !== stripos( $post__in, ' ' ) ? ' ' : ','; $post__in = array_unique( array_map( 'intval', explode( $separator, $post__in ) ) ); if ( empty( $post__in ) ) { WP_CLI::warning( "post__in should be comma-separated post IDs." ); return false; } // New exporter uses a different argument. $this->export_args['post_ids'] = $post__in; return true; } private function check_start_id( $start_id ) { if ( is_null( $start_id ) ) { return true; } $start_id = intval( $start_id ); // Post IDs must be greater than 0. if ( 0 >= $start_id ) { WP_CLI::warning( sprintf( __( 'Invalid start ID: %d' ), $start_id ) ); return false; } $this->export_args['start_id'] = $start_id; return true; } private function check_author( $author ) { if ( is_null( $author ) ) return true; $authors = get_users_of_blog(); if ( empty( $authors ) || is_wp_error( $authors ) ) { WP_CLI::warning( sprintf( "Could not find any authors in this blog." ) ); return false; } $hit = false; foreach( $authors as $user ) { if ( $hit ) break; if ( (int) $author == $user->ID || $author == $user->user_login ) $hit = $user->ID; } if ( false === $hit ) { $authors_nice = array(); foreach( $authors as $_author ) $authors_nice[] = sprintf( '%s (%s)', $_author->user_login, $_author->display_name ); WP_CLI::warning( sprintf( 'Could not find a matching author for %s. The following authors exist: %s', $author, implode( ", ", $authors_nice ) ) ); return false; } $this->export_args['author'] = $hit; return true; } private function check_max_num_posts( $num ) { if ( ! is_null( $num ) && ( ! is_numeric( $num ) || $num <= 0) ) { WP_CLI::warning( sprintf( "max_num_posts should be a positive integer.", $num ) ); return false; } $this->export_args['max_num_posts'] = (int)$num; return true; } private function check_category( $category ) { if ( is_null( $category ) ) return true; $term = category_exists( $category ); if ( empty( $term ) || is_wp_error( $term ) ) { WP_CLI::warning( sprintf( 'Could not find a category matching %s.', $category ) ); return false; } $this->export_args['category'] = $category; return true; } private function check_post_status( $status ) { if ( is_null( $status ) ) return true; $stati = get_post_statuses(); if ( empty( $stati ) || is_wp_error( $stati ) ) { WP_CLI::warning( 'Could not find any post stati.' ); return false; } if ( !isset( $stati[$status] ) ) { WP_CLI::warning( sprintf( 'Could not find a post_status matching %s. Here is a list of available stati: %s', $status, implode( ", ", array_keys( $stati ) ) ) ); return false; } $this->export_args['status'] = $status; return true; } private function check_skip_comments( $skip ) { if ( is_null( $skip ) ) return true; if ( (int) $skip <> 0 && (int) $skip <> 1 ) { WP_CLI::warning( 'skip_comments needs to be 0 (no) or 1 (yes).' ); return false; } $this->export_args['skip_comments'] = $skip; return true; } private function check_max_file_size( $size ) { if ( !is_numeric( $size ) ) { WP_CLI::warning( sprintf( "max_file_size should be numeric.", $size ) ); return false; } $this->max_file_size = $size; return true; } } <?php /** * Represents a set of posts and other site data to be exported. * * An immutable object, which gathers all data needed for the export. */ class WP_Export_Query { const QUERY_CHUNK = 100; private static $defaults = array( 'post_ids' => null, 'post_type' => null, 'status' => null, 'author' => null, 'start_date' => null, 'end_date' => null, 'start_id' => null, 'max_num_posts' => NULL, 'category' => null, ); private $post_ids; private $filters; private $xml_gen; private $wheres = array(); private $joins = array(); private $author; private $category; public $missing_parents = false; public function __construct( $filters = array() ) { $this->filters = wp_parse_args( $filters, self::$defaults ); $this->post_ids = $this->calculate_post_ids(); } public function post_ids() { return $this->post_ids; } public function charset() { return get_bloginfo( 'charset' ); } public function site_metadata() { $metadata = array( 'name' => $this->bloginfo_rss( 'name' ), 'url' => $this->bloginfo_rss( 'url' ), 'language' => $this->bloginfo_rss( 'language' ), 'description' => $this->bloginfo_rss( 'description' ), 'pubDate' => date( 'D, d M Y H:i:s +0000' ), 'site_url' => is_multisite()? network_home_url() : $this->bloginfo_rss( 'url' ), 'blog_url' => $this->bloginfo_rss( 'url' ), ); return $metadata; } public function wp_generator_tag() { return apply_filters( 'the_generator', get_the_generator( 'export' ), 'export' ); } public function authors() { global $wpdb; $authors = array(); $author_ids = $wpdb->get_col( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft'" ); foreach ( (array) $author_ids as $author_id ) { $authors[] = get_userdata( $author_id ); } $authors = array_filter( $authors ); return $authors; } public function categories() { if ( $this->category ) { return array( $this->category ); } if ( $this->filters['post_type'] ) { return array(); } $categories = (array) get_categories( array( 'get' => 'all' ) ); $this->check_for_orphaned_terms( $categories ); $categories = self::topologically_sort_terms( $categories ); return $categories; } public function tags() { if ( $this->filters['post_type'] ) { return array(); } $tags = (array) get_tags( array( 'get' => 'all' ) ); $this->check_for_orphaned_terms( $tags ); return $tags; } public function custom_taxonomies_terms() { if ( $this->filters['post_type'] ) { return array(); } $custom_taxonomies = get_taxonomies( array( '_builtin' => false ) ); $custom_terms = (array) get_terms( $custom_taxonomies, array( 'get' => 'all' ) ); $this->check_for_orphaned_terms( $custom_terms ); $custom_terms = self::topologically_sort_terms( $custom_terms ); return $custom_terms; } public function nav_menu_terms() { $nav_menus = wp_get_nav_menus(); foreach( $nav_menus as &$term ) { $term->description = ''; } return $nav_menus; } public function exportify_post( $post ) { $GLOBALS['wp_query']->in_the_loop = true; $previous_global_post = \WP_CLI\Utils\get_flag_value( $GLOBALS, 'post' ); $GLOBALS['post'] = $post; setup_postdata( $post ); $post->post_content = apply_filters( 'the_content_export', $post->post_content ); $post->post_excerpt = apply_filters( 'the_excerpt_export', $post->post_excerpt ); $post->is_sticky = is_sticky( $post->ID ) ? 1 : 0; $post->terms = self::get_terms_for_post( $post ); $post->meta = self::get_meta_for_post( $post ); $post->comments = $this->get_comments_for_post( $post ); $GLOBALS['post'] = $previous_global_post; return $post; } public function posts() { $posts_iterator = new WP_Post_IDs_Iterator( $this->post_ids, self::QUERY_CHUNK ); return new WP_Map_Iterator( $posts_iterator, array( $this, 'exportify_post' ) ); } private function calculate_post_ids() { global $wpdb; if ( is_array( $this->filters['post_ids'] ) ) { return $this->filters['post_ids']; } $this->post_type_where(); $this->status_where(); $this->author_where(); $this->start_date_where(); $this->end_date_where(); $this->start_id_where(); $this->category_where(); $where = implode( ' AND ', array_filter( $this->wheres ) ); if ( $where ) $where = "WHERE $where"; $join = implode( ' ', array_filter( $this->joins ) ); $post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where {$this->max_num_posts()}" ); $post_ids = array_merge( $post_ids, $this->attachments_for_specific_post_types( $post_ids ) ); return $post_ids; } private function post_type_where() { global $wpdb; $post_types_filters = array( 'can_export' => true ); if ( $this->filters['post_type'] ) { $post_types = $this->filters['post_type']; // Flatten single post types if ( is_array( $post_types ) && 1 === count( $post_types ) ) { $post_types = array_shift( $post_types ); } $post_types_filters = array_merge( $post_types_filters, array( 'name' => $post_types ) ); } // Multiple post types if ( isset( $post_types_filters['name'] ) && is_array( $post_types_filters['name'] ) ) { $post_types = array(); foreach ( $post_types_filters['name'] as $post_type ) { if ( post_type_exists( $post_type ) ) { $post_types[] = $post_type; } } } else { $post_types = get_post_types( $post_types_filters ); } if ( ! $post_types ) { $this->wheres[] = 'p.post_type IS NULL'; return; } $this->wheres[] = _wp_export_build_IN_condition( 'p.post_type', $post_types ); } private function status_where() { global $wpdb; if ( !$this->filters['status'] ) { $this->wheres[] = "p.post_status != 'auto-draft'"; return; } $this->wheres[] = $wpdb->prepare( 'p.post_status = %s', $this->filters['status'] ); } private function author_where() { global $wpdb; $user = $this->find_user_from_any_object( $this->filters['author'] ); if ( !$user || is_wp_error( $user ) ) { return; } $this->author = $user; $this->wheres[] = $wpdb->prepare( 'p.post_author = %d', $user->ID ); } private function start_date_where() { global $wpdb; $timestamp = strtotime( $this->filters['start_date'] ); if ( !$timestamp ) { return; } $this->wheres[] = $wpdb->prepare( 'p.post_date >= %s', date( 'Y-m-d 00:00:00', $timestamp ) ); } private function end_date_where() { global $wpdb; if ( preg_match( '/^\d{4}-\d{2}$/', $this->filters['end_date'] ) ) { $timestamp = $this->get_timestamp_for_the_last_day_of_a_month( $this->filters['end_date'] ); } else { $timestamp = strtotime( $this->filters['end_date'] ); } if ( !$timestamp ) { return; } $this->wheres[] = $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); } private function start_id_where() { global $wpdb; $start_id = absint( $this->filters['start_id'] ); if ( 0 === $start_id ) { return; } $this->wheres[] = $wpdb->prepare( 'p.ID >= %d', $start_id ); } private function get_timestamp_for_the_last_day_of_a_month( $yyyy_mm ) { return strtotime( "$yyyy_mm +1month -1day" ); } private function category_where() { global $wpdb; if ( 'post' != $this->filters['post_type'] && ! in_array( 'post', (array) $this->filters['post_type'] ) ) { return; } $category = $this->find_category_from_any_object( $this->filters['category'] ); if ( !$category ) { return; } $this->category = $category; $this->joins[] = "INNER JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id)"; $this->wheres[] = $wpdb->prepare( 'tr.term_taxonomy_id = %d', $category->term_taxonomy_id ); } private function max_num_posts() { if ( $this->filters['max_num_posts'] > 0 ) { return "LIMIT {$this->filters['max_num_posts']}"; } else { return ""; } } private function attachments_for_specific_post_types( $post_ids ) { global $wpdb; if ( !$this->filters['post_type'] ) { return array(); } $attachment_ids = array(); while ( $batch_of_post_ids = array_splice( $post_ids, 0, self::QUERY_CHUNK ) ) { $post_parent_condition = _wp_export_build_IN_condition( 'post_parent', $batch_of_post_ids ); $attachment_ids = array_merge( $attachment_ids, (array)$wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND $post_parent_condition" ) ); } return array_map( 'intval', $attachment_ids ); } private function bloginfo_rss( $section ) { return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section ); } private function find_user_from_any_object( $user ) { if ( is_numeric( $user ) ) { return get_user_by( 'id', $user ); } elseif ( is_string( $user ) ) { return get_user_by( 'login', $user ); } elseif ( isset( $user->ID ) ) { return get_user_by( 'id', $user->ID ); } return false; } private function find_category_from_any_object( $category ) { if ( is_numeric( $category ) ) { return get_term( $category, 'category' ); } elseif ( is_string( $category ) ) { $term = term_exists( $category, 'category' ); return isset( $term['term_id'] )? get_term( $term['term_id'], 'category' ) : false; } elseif ( isset( $category->term_id ) ) { return get_term( $category->term_id, 'category' ); } return false; } private static function topologically_sort_terms( $terms ) { $sorted = array(); while ( $term = array_shift( $terms ) ) { if ( $term->parent == 0 || isset( $sorted[$term->parent] ) ) $sorted[$term->term_id] = $term; else $terms[] = $term; } return $sorted; } private function check_for_orphaned_terms( $terms ) { $term_ids = array(); $have_parent = array(); foreach ( $terms as $term ) { $term_ids[ $term->term_id ] = true; if ( $term->parent != 0 ) $have_parent[] = $term; } foreach ( $have_parent as $has_parent ) { if ( ! isset( $term_ids[ $has_parent->parent ] ) ) { $this->missing_parents = $has_parent; throw new WP_Export_Term_Exception( sprintf( __( 'Term is missing a parent: %s (%d)' ), $has_parent->slug, $has_parent->term_taxonomy_id ) ); } } } private static function get_terms_for_post( $post ) { $taxonomies = get_object_taxonomies( $post->post_type ); if ( empty( $taxonomies ) ) return array(); $terms = wp_get_object_terms( $post->ID, $taxonomies ); $terms = $terms? $terms : array(); return $terms; } private static function get_meta_for_post( $post ) { global $wpdb; $meta_for_export = array(); $meta_from_db = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE post_id = %d", $post->ID ) ); foreach ( $meta_from_db as $meta ) { if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) continue; if ( in_array( $meta->meta_key, array( '_edit_lock', '_wp_attachment_metadata', '_wp_attached_file' ) ) ) { continue; } $meta_for_export[] = $meta; } return $meta_for_export; } private function get_comments_for_post( $post ) { global $wpdb; if ( isset( $this->filters['skip_comments'] ) ) { return array(); } $comments = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) ); foreach( $comments as $comment ) { $meta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) ); $meta = $meta? $meta : array(); $comment->meta = $meta; } return $comments; } } <?php class WP_Export_Returner extends WP_Export_Base_Writer { private $result = ''; public function export() { $this->private = ''; try { parent::export(); } catch ( WP_Export_Exception $e ) { $message = apply_filters( 'export_error_message', $e->getMessage() ); return new WP_Error( 'wp-export-error', $message ); } catch ( WP_Export_Term_Exception $e ) { do_action( 'export_term_orphaned', $this->formatter->export->missing_parents ); $message = apply_filters( 'export_term_error_message', $e->getMessage() ); return new WP_Error( 'wp-export-error', $message ); } return $this->result; } protected function write( $xml ) { $this->result .= $xml; } } <?php class WP_Post_IDs_Iterator implements Iterator { private $limit = 100; private $post_ids; private $ids_left; private $results = array(); public function __construct( $post_ids, $limit = null ) { $this->db = $GLOBALS['wpdb']; $this->post_ids = $post_ids; $this->ids_left = $post_ids; if ( !is_null( $limit ) ) { $this->limit = $limit; } } public function current() { return $this->results[$this->index_in_results]; } public function key() { return $this->global_index; } public function next() { $this->index_in_results++; $this->global_index++; } public function rewind() { $this->results = array(); $this->global_index = 0; $this->index_in_results = 0; $this->ids_left = $this->post_ids; } public function valid() { if ( isset( $this->results[$this->index_in_results] ) ) { return true; } if ( empty( $this->ids_left ) ) { return false; } $has_posts = $this->load_next_posts_from_db(); if ( !$has_posts ) { return false; } $this->index_in_results = 0; return true; } private function load_next_posts_from_db() { $next_batch_post_ids = array_splice( $this->ids_left, 0, $this->limit ); $in_post_ids_sql = _wp_export_build_IN_condition( 'ID', $next_batch_post_ids ); $this->results = $this->db->get_results( "SELECT * FROM {$this->db->posts} WHERE $in_post_ids_sql" ); if ( !$this->results ) { if ( $this->db->last_error ) { throw new WP_Iterator_Exception( 'Database error: ' . $this->db->last_error ); } else { return false; } } return true; } } <?php class WP_Export_Stdout_Writer extends WP_Export_Base_Writer { function __construct( $formatter, $writer_args ) { parent::__construct( $formatter ); $this->before_posts_xml = $this->formatter->before_posts(); $this->after_posts_xml = $this->formatter->after_posts(); } public function export() { fwrite( STDOUT, $this->before_posts_xml ); foreach( $this->formatter->posts() as $post_xml ) { fwrite( STDOUT, $post_xml ); } fwrite( STDOUT, $this->after_posts_xml ); } protected function write( $xml ) { } } <?php /** * Version number for the export format. * * Bump this when something changes that might affect compatibility. * * @since 2.5.0 */ define( 'WXR_VERSION', '1.2' ); /** * Responsible for formatting the data in WP_Export_Query to WXR */ class WP_Export_WXR_Formatter { public function __construct( $export ) { $this->export = $export; $this->wxr_version = WXR_VERSION; } public function before_posts() { $before_posts_xml = ''; $before_posts_xml .= $this->header(); $before_posts_xml .= $this->site_metadata(); $before_posts_xml .= $this->authors(); $before_posts_xml .= $this->categories(); $before_posts_xml .= $this->tags(); $before_posts_xml .= $this->nav_menu_terms(); $before_posts_xml .= $this->custom_taxonomies_terms(); $before_posts_xml .= $this->rss2_head_action(); return $before_posts_xml; } public function posts() { return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) ); } public function after_posts() { return $this->footer(); } public function header() { $oxymel = new Oxymel; $charset = $this->export->charset(); $wp_generator_tag = $this->export->wp_generator_tag(); $comment = <<<COMMENT This is a WordPress eXtended RSS file generated by WordPress as an export of your site. It contains information about your site's posts, pages, comments, categories, and other content. You may use this file to transfer that content from one site to another. This file is not intended to serve as a complete backup of your site. To import this information into a WordPress site follow these steps: 1. Log in to that site as an administrator. 2. Go to Tools: Import in the WordPress admin panel. 3. Install the "WordPress" importer from the list. 4. Activate & Run Importer. 5. Upload this file using the form provided on that page. 6. You will first be asked to map the authors in this export file to users on the site. For each author, you may choose to map to an existing user on the site or to create a new user. 7. WordPress will then import each of the posts, pages, comments, categories, etc. contained in this file into your site. COMMENT; return $oxymel ->xml ->comment( $comment ) ->raw( $wp_generator_tag ) ->open_rss( array( 'version' => '2.0', 'xmlns:excerpt' => "http://wordpress.org/export/{$this->wxr_version}/excerpt/", 'xmlns:content' => "http://purl.org/rss/1.0/modules/content/", 'xmlns:wfw' => "http://wellformedweb.org/CommentAPI/", 'xmlns:dc' => "http://purl.org/dc/elements/1.1/", 'xmlns:wp' => "http://wordpress.org/export/{$this->wxr_version}/", ) ) ->open_channel ->to_string(); } public function site_metadata() { $oxymel = new Oxymel; $metadata = $this->export->site_metadata(); return $oxymel ->title( $metadata['name'] ) ->link( $metadata['url'] ) ->description( $metadata['description'] ) ->pubDate( $metadata['pubDate'] ) ->language( $metadata['language'] ) ->tag( 'wp:wxr_version', $this->wxr_version ) ->tag( 'wp:base_site_url', $metadata['site_url'] ) ->tag( 'wp:base_blog_url', $metadata['blog_url'] ) ->to_string(); } public function authors() { $oxymel = new Oxymel; $authors = $this->export->authors(); foreach ( $authors as $author ) { $oxymel ->tag( 'wp:author' )->contains ->tag( 'wp:author_id', $author->ID ) ->tag( 'wp:author_login', $author->user_login ) ->tag( 'wp:author_email', $author->user_email ) ->tag( 'wp:author_display_name' )->contains->cdata( $author->display_name )->end ->tag( 'wp:author_first_name' )->contains->cdata( $author->user_firstname )->end ->tag( 'wp:author_last_name' )->contains->cdata( $author->user_lastname )->end ->end; } return $oxymel->to_string(); } public function categories() { $oxymel = new WP_Export_Oxymel; $categories = $this->export->categories(); foreach( $categories as $term_id => $category ) { $category->parent_slug = $category->parent? $categories[$category->parent]->slug : ''; $oxymel->tag( 'wp:category' )->contains ->tag( 'wp:term_id', $category->term_id ) ->tag( 'wp:category_nicename', $category->slug ) ->tag( 'wp:category_parent', $category->parent_slug ) ->optional_cdata( 'wp:cat_name', $category->name ) ->optional_cdata( 'wp:category_description', $category->description ) ->end; } return $oxymel->to_string(); } public function tags() { $oxymel = new WP_Export_Oxymel; $tags = $this->export->tags(); foreach( $tags as $tag ) { $oxymel->tag( 'wp:tag' )->contains ->tag( 'wp:term_id', $tag->term_id ) ->tag( 'wp:tag_slug', $tag->slug ) ->optional_cdata( 'wp:tag_name', $tag->name ) ->optional_cdata( 'wp:tag_description', $tag->description ) ->end; } return $oxymel->to_string(); } public function nav_menu_terms() { return $this->terms( $this->export->nav_menu_terms() ); } public function custom_taxonomies_terms() { return $this->terms( $this->export->custom_taxonomies_terms() ); } public function rss2_head_action() { ob_start(); do_action( 'rss2_head' ); $action_output = ob_get_clean(); return $action_output; } public function post( $post ) { $oxymel = new WP_Export_Oxymel; $GLOBALS['wp_query']->in_the_loop = true; $GLOBALS['post'] = $post; setup_postdata( $post ); $oxymel->item->contains ->title( apply_filters( 'the_title_rss', $post->post_title ) ) ->link( esc_url( apply_filters('the_permalink_rss', get_permalink() ) ) ) ->pubDate( mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) ) ->tag( 'dc:creator', get_the_author_meta( 'login' ) ) ->guid( esc_url( get_the_guid() ), array( 'isPermaLink' => 'false' ) ) ->description( '' ) ->tag( 'content:encoded' )->contains->cdata( $post->post_content )->end ->tag( 'excerpt:encoded' )->contains->cdata( $post->post_excerpt )->end ->tag( 'wp:post_id', $post->ID ) ->tag( 'wp:post_date', $post->post_date ) ->tag( 'wp:post_date_gmt', $post->post_date_gmt ) ->tag( 'wp:comment_status', $post->comment_status ) ->tag( 'wp:ping_status', $post->ping_status ) ->tag( 'wp:post_name', $post->post_name ) ->tag( 'wp:status', $post->post_status ) ->tag( 'wp:post_parent', $post->post_parent ) ->tag( 'wp:menu_order', $post->menu_order ) ->tag( 'wp:post_type', $post->post_type ) ->tag( 'wp:post_password', $post->post_password ) ->tag( 'wp:is_sticky', $post->is_sticky ) ->optional( 'wp:attachment_url', wp_get_attachment_url( $post->ID ) ); foreach( $post->terms as $term ) { $oxymel ->category( array( 'domain' => $term->taxonomy, 'nicename' => $term->slug ) )->contains->cdata( $term->name )->end; } foreach( $post->meta as $meta ) { $oxymel ->tag( 'wp:postmeta' )->contains ->tag( 'wp:meta_key', $meta->meta_key ) ->tag( 'wp:meta_value' )->contains->cdata( $meta->meta_value )->end ->end; } foreach( $post->comments as $comment ) { $oxymel ->tag( 'wp:comment' )->contains ->tag( 'wp:comment_id', $comment->comment_ID ) ->tag( 'wp:comment_author' )->contains->cdata( $comment->comment_author )->end ->tag( 'wp:comment_author_email', $comment->comment_author_email ) ->tag( 'wp:comment_author_url', esc_url( $comment->comment_author_url ) ) ->tag( 'wp:comment_author_IP', $comment->comment_author_IP ) ->tag( 'wp:comment_date', $comment->comment_date ) ->tag( 'wp:comment_date_gmt', $comment->comment_date_gmt ) ->tag( 'wp:comment_content' )->contains->cdata( $comment->comment_content )->end ->tag( 'wp:comment_approved', $comment->comment_approved ) ->tag( 'wp:comment_type', $comment->comment_type ) ->tag( 'wp:comment_parent', $comment->comment_parent ) ->tag( 'wp:comment_user_id', $comment->user_id ) ->oxymel( $this->comment_meta( $comment ) ) ->end; } $oxymel ->end; return $oxymel->to_string(); } public function footer() { $oxymel = new Oxymel; return $oxymel->close_channel->close_rss->to_string(); } protected function terms( $terms ) { $oxymel = new WP_Export_Oxymel; foreach( $terms as $term ) { $term->parent_slug = $term->parent? $terms[$term->parent]->slug : ''; $oxymel->tag( 'wp:term' )->contains ->tag( 'wp:term_id', $term->term_id ) ->tag( 'wp:term_taxonomy', $term->taxonomy ) ->tag( 'wp:term_slug', $term->slug ); if ( 'nav_menu' != $term->taxonomy ) { $oxymel ->tag( 'wp:term_parent', $term->parent_slug ); } $oxymel ->optional_cdata( 'wp:term_name', $term->name ) ->optional_cdata( 'wp:term_description', $term->description ) ->end; } return $oxymel->to_string(); } protected function comment_meta( $comment ) { global $wpdb; $metas = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) ); if ( !$metas ) { return new Oxymel; } $oxymel = new WP_Export_Oxymel; foreach( $metas as $meta ) { $oxymel->tag( 'wp:commentmeta' )->contains ->tag( 'wp:meta_key', $meta->meta_key ) ->tag( 'wp:meta_value' )->contains->cdata( $meta->meta_value )->end ->end; } return $oxymel; } } <?php class WP_Iterator_Exception extends Exception { } <?php class WP_Map_Iterator extends IteratorIterator { function __construct( $iterator, $callback ) { $this->callback = $callback; parent::__construct( $iterator ); } function current() { $original_current = parent::current(); return call_user_func( $this->callback, $original_current ); } } <?php abstract class WP_Export_Base_Writer { protected $formatter; function __construct( $formatter ) { $this->formatter = $formatter; } public function export() { $this->write( $this->formatter->before_posts() ); foreach( $this->formatter->posts() as $post_in_wxr ) { $this->write( $post_in_wxr ); } $this->write( $this->formatter->after_posts() ); } abstract protected function write( $xml ); } <?php class WP_Export_XML_Over_HTTP extends WP_Export_Base_Writer { private $file_name; function __construct( $formatter, $file_name ) { parent::__construct( $formatter ); $this->file_name = $file_name; } public function export() { try { $export = $this->get_export(); $this->send_headers(); echo $export; } catch ( WP_Export_Exception $e ) { $message = apply_filters( 'export_error_message', $e->getMessage() ); wp_die( $message, __( 'Export Error' ), array( 'back_link' => true ) ); } catch ( WP_Export_Term_Exception $e ) { do_action( 'export_term_orphaned', $this->formatter->export->missing_parents ); $message = apply_filters( 'export_term_error_message', $e->getMessage() ); wp_die( $message, __( 'Export Error' ), array( 'back_link' => true ) ); } } protected function write( $xml ) { $this->result .= $xml; } protected function get_export() { $this->result = ''; parent::export(); return $this->result; } protected function send_headers() { header( 'Content-Description: File Transfer' ); header( 'Content-Disposition: attachment; filename=' . $this->file_name ); header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); } } <?php class WP_Export_File_Writer extends WP_Export_Base_Writer { private $f; private $file_name; public function __construct( $formatter, $file_name ) { parent::__construct( $formatter ); $this->file_name = $file_name; } public function export() { $this->f = fopen( $this->file_name, 'w' ); if ( !$this->f ) { throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $this->file_name ) ); } try { parent::export(); } catch ( WP_Export_Exception $e ) { throw $e; } catch ( WP_Export_Term_Exception $e ) { throw $e; } fclose( $this->f ); } protected function write( $xml ) { $res = fwrite( $this->f, $xml); if ( false === $res ) { throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) ); } } } <?php class WP_Export_Term_Exception extends RuntimeException { } <?php class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer { private $max_file_size; private $destination_directory; private $filename_template; private $before_posts_xml; private $after_posts_xml; private $result = ''; private $f; private $next_file_number = 0; private $current_file_size = 0; function __construct( $formatter, $writer_args = array() ) { parent::__construct( $formatter ); if ( ! defined( 'MB_IN_BYTES' ) ) { define( 'MB_IN_BYTES', 1024 * 1024 ); } //TODO: check if args are not missing if ( is_null( $writer_args['max_file_size'] ) ) { $this->max_file_size = 15 * MB_IN_BYTES; } elseif ( WP_CLI_EXPORT_COMMAND_NO_SPLIT === $writer_args['max_file_size'] ) { $this->max_file_size = WP_CLI_EXPORT_COMMAND_NO_SPLIT; } else { $this->max_file_size = $writer_args['max_file_size'] * MB_IN_BYTES; } $this->destination_directory = $writer_args['destination_directory']; $this->filename_template = $writer_args['filename_template']; $this->before_posts_xml = $this->formatter->before_posts(); $this->after_posts_xml = $this->formatter->after_posts(); } public function export() { $this->start_new_file(); foreach( $this->formatter->posts() as $post_xml ) { if ( WP_CLI_EXPORT_COMMAND_NO_SPLIT !== $this->max_file_size && $this->current_file_size + strlen( $post_xml ) > $this->max_file_size ) { $this->start_new_file(); } $this->write( $post_xml ); } $this->close_current_file(); } protected function write( $xml ) { $res = fwrite( $this->f, $xml); if ( false === $res ) { throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) ); } $this->current_file_size += strlen( $xml ); } private function start_new_file() { if ( $this->f ) { $this->close_current_file(); } $file_path = $this->next_file_path(); $this->f = fopen( $file_path, 'w' ); if ( !$this->f ) { throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $file_path ) ); } do_action( 'wp_export_new_file', $file_path ); $this->current_file_size = 0; $this->write( $this->before_posts_xml ); } private function close_current_file() { if ( !$this->f ) { return; } $this->write( $this->after_posts_xml ); fclose( $this->f ); } private function next_file_name() { $next_file_name = sprintf( $this->filename_template, $this->next_file_number ); $this->next_file_number++; return $next_file_name; } private function next_file_path() { return untrailingslashit( $this->destination_directory ) . DIRECTORY_SEPARATOR . $this->next_file_name(); } } <?php if ( ! class_exists( 'WP_CLI' ) ) { return; } $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; if ( file_exists( $autoload ) ) { require_once $autoload; } WP_CLI::add_command( 'server', 'Server_Command', array( 'before_invoke' => function() { $min_version = '5.4'; if ( version_compare( PHP_VERSION, $min_version, '<' ) ) { WP_CLI::error( "The `wp server` command requires PHP {$min_version} or newer." ); } } ) ); <?php class Server_Command extends WP_CLI_Command { /** * Launch PHP's built-in web server for this specific WordPress installation. * * Uses `php -S` to launch a web server serving the WordPress webroot. * <http://php.net/manual/en/features.commandline.webserver.php> * * ## OPTIONS * * [--host=<host>] * : The hostname to bind the server to. * --- * default: localhost * --- * * [--port=<port>] * : The port number to bind the server to. * --- * default: 8080 * --- * * [--docroot=<path>] * : The path to use as the document root. If the path global parameter is * set, the default value is it. * * [--config=<file>] * : Configure the server with a specific .ini file. * * ## EXAMPLES * * # Make the instance available on any address (with port 8080) * $ wp server --host=0.0.0.0 * PHP 5.6.9 Development Server started at Tue May 24 01:27:11 2016 * Listening on http://0.0.0.0:8080 * Document root is / * Press Ctrl-C to quit. * * # Run on port 80 (for multisite) * $ sudo wp server --host=localhost.localdomain --port=80 * PHP 5.6.9 Development Server started at Tue May 24 01:30:06 2016 * Listening on http://localhost1.localdomain1:8080 * Document root is / * Press Ctrl-C to quit. * * # Configure the server with a specific .ini file * $ wp server --config=development.ini * PHP 7.0.9 Development Server started at Mon Aug 22 12:09:04 2016 * Listening on http://localhost:8080 * Document root is / * Press Ctrl-C to quit. * * @when before_wp_load */ function __invoke( $_, $assoc_args ) { $defaults = array( 'host' => 'localhost', 'port' => 8080, 'docroot' => ! is_null( WP_CLI::get_runner()->config['path'] ) ? WP_CLI::get_runner()->config['path'] : false, 'config' => get_cfg_var( 'cfg_file_path' ) ); $assoc_args = array_merge( $defaults, $assoc_args ); $docroot = $assoc_args['docroot']; if ( !$docroot ) { $config_path = WP_CLI::get_runner()->project_config_path; if ( !$config_path ) { $docroot = ABSPATH; } else { $docroot = dirname( $config_path ); } } // Get the path to the router file $command_root = WP_CLI\Utils\phar_safe_path( dirname( __DIR__ ) ); $router_path = $command_root . '/router.php'; if ( ! file_exists( $router_path ) ) { WP_CLI::error( "Couldn't find router.php" ); } $cmd = \WP_CLI\Utils\esc_cmd( '%s -S %s -t %s -c %s %s', WP_CLI::get_php_binary(), $assoc_args['host'] . ':' . $assoc_args['port'], $docroot, $assoc_args['config'], \WP_CLI\Utils\extract_from_phar( $router_path ) ); $descriptors = array( STDIN, STDOUT, STDERR ); // https://bugs.php.net/bug.php?id=60181 $options = array(); if ( \WP_CLI\Utils\is_windows() ) { $options["bypass_shell"] = TRUE; } exit( proc_close( proc_open( $cmd, $descriptors, $pipes, NULL, NULL, $options ) ) ); } } <?php // Used by `wp server` to route requests. namespace WP_CLI\Router; /** * This is a copy of WordPress's add_filter() function. * * We duplicate it because WordPress is not loaded yet. */ function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) { global $wp_filter, $merged_filters; $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority); $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args); unset( $merged_filters[ $tag ] ); return true; } /** * This is a copy of WordPress's _wp_filter_build_unique_id() function. * * We duplicate it because WordPress is not loaded yet. */ function _wp_filter_build_unique_id($tag, $function, $priority) { global $wp_filter; static $filter_id_count = 0; if ( is_string($function) ) return $function; if ( is_object($function) ) { // Closures are currently implemented as objects $function = array( $function, '' ); } else { $function = (array) $function; } if (is_object($function[0]) ) { // Object Class Calling if ( function_exists('spl_object_hash') ) { return spl_object_hash($function[0]) . $function[1]; } else { $obj_idx = get_class($function[0]).$function[1]; if ( !isset($function[0]->wp_filter_id) ) { if ( false === $priority ) return false; $obj_idx .= isset($wp_filter[$tag][$priority]) ? count((array)$wp_filter[$tag][$priority]) : $filter_id_count; $function[0]->wp_filter_id = $filter_id_count; ++$filter_id_count; } else { $obj_idx .= $function[0]->wp_filter_id; } return $obj_idx; } } else if ( is_string($function[0]) ) { // Static Calling return $function[0] . '::' . $function[1]; } } function _get_full_host( $url ) { $parsed_url = parse_url( $url ); $host = $parsed_url['host']; if ( isset( $parsed_url['port'] ) && $parsed_url['port'] != 80 ) $host .= ':' . $parsed_url['port']; return $host; } // We need to trick WordPress into using the URL set by `wp server`, especially on multisite. add_filter( 'option_home', function ( $url ) { $GLOBALS['_wp_cli_original_url'] = $url; return 'http://' . $_SERVER['HTTP_HOST']; }, 20 ); add_filter( 'option_siteurl', function ( $url ) { if ( !isset( $GLOBALS['_wp_cli_original_url'] ) ) get_option('home'); // trigger the option_home filter $home_url_host = _get_full_host( $GLOBALS['_wp_cli_original_url'] ); $site_url_host = _get_full_host( $url ); if ( $site_url_host == $home_url_host ) { $url = str_replace( $site_url_host, $_SERVER['HTTP_HOST'], $url ); } return $url; }, 20 ); $root = $_SERVER['DOCUMENT_ROOT']; $path = '/'. ltrim( parse_url( urldecode( $_SERVER['REQUEST_URI'] ) )['path'], '/' ); if ( file_exists( $root.$path ) ) { if ( is_dir( $root.$path ) && substr( $path, -1 ) !== '/' ) { header( "Location: $path/" ); exit; } if ( strpos( $path, '.php' ) !== false ) { chdir( dirname( $root.$path ) ); require_once $root.$path; } else { return false; } } else { chdir( $root ); require_once 'index.php'; } <?php // Utility functions used by Behat steps function assertRegExp( $regex, $actual ) { if ( ! preg_match( $regex, $actual ) ) { throw new Exception( "Actual value: " . var_export( $actual, true ) ); } } function assertEquals( $expected, $actual ) { if ( $expected != $actual ) { throw new Exception( "Actual value: " . var_export( $actual, true ) ); } } function assertNotEquals( $expected, $actual ) { if ( $expected == $actual ) { throw new Exception( "Actual value: " . var_export( $actual, true ) ); } } function assertNumeric( $actual ) { if ( !is_numeric( $actual ) ) { throw new Exception( "Actual value: " . var_export( $actual, true ) ); } } function assertNotNumeric( $actual ) { if ( is_numeric( $actual ) ) { throw new Exception( "Actual value: " . var_export( $actual, true ) ); } } function checkString( $output, $expected, $action, $message = false ) { switch ( $action ) { case 'be': $r = $expected === rtrim( $output, "\n" ); break; case 'contain': $r = false !== strpos( $output, $expected ); break; case 'not contain': $r = false === strpos( $output, $expected ); break; default: throw new Behat\Behat\Exception\PendingException(); } if ( !$r ) { if ( false === $message ) $message = $output; throw new Exception( $message ); } } function compareTables( $expected_rows, $actual_rows, $output ) { // the first row is the header and must be present if ( $expected_rows[0] != $actual_rows[0] ) { throw new \Exception( $output ); } unset( $actual_rows[0] ); unset( $expected_rows[0] ); $missing_rows = array_diff( $expected_rows, $actual_rows ); if ( !empty( $missing_rows ) ) { throw new \Exception( $output ); } } function compareContents( $expected, $actual ) { if ( gettype( $expected ) != gettype( $actual ) ) { return false; } if ( is_object( $expected ) ) { foreach ( get_object_vars( $expected ) as $name => $value ) { if ( ! compareContents( $value, $actual->$name ) ) return false; } } else if ( is_array( $expected ) ) { foreach ( $expected as $key => $value ) { if ( ! compareContents( $value, $actual[$key] ) ) return false; } } else { return $expected === $actual; } return true; } /** * Compare two strings containing JSON to ensure that @a $actualJson contains at * least what the JSON string @a $expectedJson contains. * * @return whether or not @a $actualJson contains @a $expectedJson * @retval true @a $actualJson contains @a $expectedJson * @retval false @a $actualJson does not contain @a $expectedJson * * @param[in] $actualJson the JSON string to be tested * @param[in] $expectedJson the expected JSON string * * Examples: * expected: {'a':1,'array':[1,3,5]} * * 1 ) * actual: {'a':1,'b':2,'c':3,'array':[1,2,3,4,5]} * return: true * * 2 ) * actual: {'b':2,'c':3,'array':[1,2,3,4,5]} * return: false * element 'a' is missing from the root object * * 3 ) * actual: {'a':0,'b':2,'c':3,'array':[1,2,3,4,5]} * return: false * the value of element 'a' is not 1 * * 4 ) * actual: {'a':1,'b':2,'c':3,'array':[1,2,4,5]} * return: false * the contents of 'array' does not include 3 */ function checkThatJsonStringContainsJsonString( $actualJson, $expectedJson ) { $actualValue = json_decode( $actualJson ); $expectedValue = json_decode( $expectedJson ); if ( !$actualValue ) { return false; } return compareContents( $expectedValue, $actualValue ); } /** * Compare two strings to confirm $actualCSV contains $expectedCSV * Both strings are expected to have headers for their CSVs. * $actualCSV must match all data rows in $expectedCSV * * @param string A CSV string * @param array A nested array of values * @return bool Whether $actualCSV contains $expectedCSV */ function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) { $actualCSV = array_map( 'str_getcsv', explode( PHP_EOL, $actualCSV ) ); if ( empty( $actualCSV ) ) return false; // Each sample must have headers $actualHeaders = array_values( array_shift( $actualCSV ) ); $expectedHeaders = array_values( array_shift( $expectedCSV ) ); // Each expectedCSV must exist somewhere in actualCSV in the proper column $expectedResult = 0; foreach ( $expectedCSV as $expected_row ) { $expected_row = array_combine( $expectedHeaders, $expected_row ); foreach ( $actualCSV as $actual_row ) { if ( count( $actualHeaders ) != count( $actual_row ) ) continue; $actual_row = array_intersect_key( array_combine( $actualHeaders, $actual_row ), $expected_row ); if ( $actual_row == $expected_row ) $expectedResult++; } } return $expectedResult >= count( $expectedCSV ); } /** * Compare two strings containing YAML to ensure that @a $actualYaml contains at * least what the YAML string @a $expectedYaml contains. * * @return whether or not @a $actualYaml contains @a $expectedJson * @retval true @a $actualYaml contains @a $expectedJson * @retval false @a $actualYaml does not contain @a $expectedJson * * @param[in] $actualYaml the YAML string to be tested * @param[in] $expectedYaml the expected YAML string */ function checkThatYamlStringContainsYamlString( $actualYaml, $expectedYaml ) { $actualValue = Mustangostang\Spyc::YAMLLoad( $actualYaml ); $expectedValue = Mustangostang\Spyc::YAMLLoad( $expectedYaml ); if ( !$actualValue ) { return false; } return compareContents( $expectedValue, $actualValue ); } <?php use Behat\Behat\Context\ClosuredContextInterface, Behat\Behat\Context\TranslatedContextInterface, Behat\Behat\Context\BehatContext, Behat\Behat\Event\SuiteEvent; use \WP_CLI\Process; use \WP_CLI\Utils; // Inside a community package if ( file_exists( __DIR__ . '/utils.php' ) ) { require_once __DIR__ . '/utils.php'; require_once __DIR__ . '/Process.php'; require_once __DIR__ . '/ProcessRun.php'; $project_composer = dirname( dirname( dirname( __FILE__ ) ) ) . '/composer.json'; if ( file_exists( $project_composer ) ) { $composer = json_decode( file_get_contents( $project_composer ) ); if ( ! empty( $composer->autoload->files ) ) { $contents = 'require:' . PHP_EOL; foreach( $composer->autoload->files as $file ) { $contents .= ' - ' . dirname( dirname( dirname( __FILE__ ) ) ) . '/' . $file . PHP_EOL; } @mkdir( sys_get_temp_dir() . '/wp-cli-package-test/' ); $project_config = sys_get_temp_dir() . '/wp-cli-package-test/config.yml'; file_put_contents( $project_config, $contents ); putenv( 'WP_CLI_CONFIG_PATH=' . $project_config ); } } // Inside WP-CLI } else { require_once __DIR__ . '/../../php/utils.php'; require_once __DIR__ . '/../../php/WP_CLI/Process.php'; require_once __DIR__ . '/../../php/WP_CLI/ProcessRun.php'; if ( file_exists( __DIR__ . '/../../vendor/autoload.php' ) ) { require_once __DIR__ . '/../../vendor/autoload.php'; } else if ( file_exists( __DIR__ . '/../../../../autoload.php' ) ) { require_once __DIR__ . '/../../../../autoload.php'; } } /** * Features context. */ class FeatureContext extends BehatContext implements ClosuredContextInterface { /** * The current working directory for scenarios that have a "Given a WP install" or "Given an empty directory" step. Variable RUN_DIR. Lives until the end of the scenario. */ private static $run_dir; /** * Where WordPress core is downloaded to for caching, and which is copied to RUN_DIR during a "Given a WP install" step. Lives until manually deleted. */ private static $cache_dir; /** * The directory that holds the install cache, and which is copied to RUN_DIR during a "Given a WP install" step. Recreated on each suite run. */ private static $install_cache_dir; /** * The directory that the WP-CLI cache (WP_CLI_CACHE_DIR, normally "$HOME/.wp-cli/cache") is set to on a "Given an empty cache" step. * Variable SUITE_CACHE_DIR. Lives until the end of the scenario (or until another "Given an empty cache" step within the scenario). */ private static $suite_cache_dir; /** * Where the current WP-CLI source repository is copied to for Composer-based tests with a "Given a dependency on current wp-cli" step. * Variable COMPOSER_LOCAL_REPOSITORY. Lives until the end of the suite. */ private static $composer_local_repository; /** * The test database settings. All but `dbname` can be set via environment variables. The database is dropped at the start of each scenario and created on a "Given a WP install" step. */ private static $db_settings = array( 'dbname' => 'wp_cli_test', 'dbuser' => 'wp_cli_test', 'dbpass' => 'password1', 'dbhost' => '127.0.0.1', ); /** * Array of background process ids started by the current scenario. Used to terminate them at the end of the scenario. */ private $running_procs = array(); /** * Array of variables available as {VARIABLE_NAME}. Some are always set: CORE_CONFIG_SETTINGS, SRC_DIR, CACHE_DIR, WP_VERSION-version-latest. Some are step-dependent: * RUN_DIR, SUITE_CACHE_DIR, COMPOSER_LOCAL_REPOSITORY, PHAR_PATH. Scenarios can define their own variables using "Given save" steps. Variables are reset for each scenario. */ public $variables = array(); /** * The current feature file and scenario line number as '<file>.<line>'. Used in RUN_DIR and SUITE_CACHE_DIR directory names. Set at the start of each scenario. */ private static $temp_dir_infix; /** * Settings and variables for WP_CLI_TEST_LOG_RUN_TIMES run time logging. */ private static $log_run_times; // Whether to log run times - WP_CLI_TEST_LOG_RUN_TIMES env var. Set on `@BeforeScenario'. private static $suite_start_time; // When the suite started, set on `@BeforeScenario'. private static $output_to; // Where to output log - stdout|error_log. Set on `@BeforeSuite`. private static $num_top_processes; // Number of processes/methods to output by longest run times. Set on `@BeforeSuite`. private static $num_top_scenarios; // Number of scenarios to output by longest run times. Set on `@BeforeSuite`. private static $scenario_run_times = array(); // Scenario run times (top `self::$num_top_scenarios` only). private static $scenario_count = 0; // Scenario count, incremented on `@AfterScenario`. private static $proc_method_run_times = array(); // Array of run time info for proc methods, keyed by method name and arg, each a 2-element array containing run time and run count. /** * Get the environment variables required for launched `wp` processes */ private static function get_process_env_variables() { // Ensure we're using the expected `wp` binary $bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ?: realpath( __DIR__ . '/../../bin' ); $vendor_dir = realpath( __DIR__ . '/../../vendor/bin' ); $env = array( 'PATH' => $bin_dir . ':' . $vendor_dir . ':' . getenv( 'PATH' ), 'BEHAT_RUN' => 1, 'HOME' => sys_get_temp_dir() . '/wp-cli-home', ); if ( $config_path = getenv( 'WP_CLI_CONFIG_PATH' ) ) { $env['WP_CLI_CONFIG_PATH'] = $config_path; } if ( $term = getenv( 'TERM' ) ) { $env['TERM'] = $term; } if ( $php_args = getenv( 'WP_CLI_PHP_ARGS' ) ) { $env['WP_CLI_PHP_ARGS'] = $php_args; } if ( $travis_build_dir = getenv( 'TRAVIS_BUILD_DIR' ) ) { $env['TRAVIS_BUILD_DIR'] = $travis_build_dir; } if ( $github_token = getenv( 'GITHUB_TOKEN' ) ) { $env['GITHUB_TOKEN'] = $github_token; } return $env; } /** * We cache the results of `wp core download` to improve test performance. * Ideally, we'd cache at the HTTP layer for more reliable tests. */ private static function cache_wp_files() { $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) ) return; $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', self::$cache_dir ); if ( $wp_version ) { $cmd .= Utils\esc_cmd( ' --version=%s', $wp_version ); } Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); } /** * @BeforeSuite */ public static function prepare( SuiteEvent $event ) { // Test performance statistics - useful for detecting slow tests. if ( self::$log_run_times = getenv( 'WP_CLI_TEST_LOG_RUN_TIMES' ) ) { self::log_run_times_before_suite( $event ); } $result = Process::create( 'wp cli info', null, self::get_process_env_variables() )->run_check(); echo PHP_EOL; echo $result->stdout; echo PHP_EOL; self::cache_wp_files(); $result = Process::create( Utils\esc_cmd( 'wp core version --path=%s', self::$cache_dir ) , null, self::get_process_env_variables() )->run_check(); echo 'WordPress ' . $result->stdout; echo PHP_EOL; // Remove install cache if any (not setting the static var). $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; $install_cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-install-cache' . $wp_version_suffix; if ( file_exists( $install_cache_dir ) ) { self::remove_dir( $install_cache_dir ); } } /** * @AfterSuite */ public static function afterSuite( SuiteEvent $event ) { if ( self::$composer_local_repository ) { self::remove_dir( self::$composer_local_repository ); self::$composer_local_repository = null; } if ( self::$log_run_times ) { self::log_run_times_after_suite( $event ); } } /** * @BeforeScenario */ public function beforeScenario( $event ) { if ( self::$log_run_times ) { self::log_run_times_before_scenario( $event ); } $this->variables['SRC_DIR'] = realpath( __DIR__ . '/../..' ); // Used in the names of the RUN_DIR and SUITE_CACHE_DIR directories. self::$temp_dir_infix = null; if ( $file = self::get_event_file( $event, $line ) ) { self::$temp_dir_infix = basename( $file ) . '.' . $line; } } /** * @AfterScenario */ public function afterScenario( $event ) { if ( self::$run_dir ) { // remove altered WP install, unless there's an error if ( $event->getResult() < 4 ) { self::remove_dir( self::$run_dir ); } self::$run_dir = null; } // Remove WP-CLI package directory if any. Set to `wp package path` by package-command and scaffold-package-command features, and by cli-info.feature. if ( isset( $this->variables['PACKAGE_PATH'] ) ) { self::remove_dir( $this->variables['PACKAGE_PATH'] ); } // Remove SUITE_CACHE_DIR if any. if ( self::$suite_cache_dir ) { self::remove_dir( self::$suite_cache_dir ); self::$suite_cache_dir = null; } // Remove any background processes. foreach ( $this->running_procs as $proc ) { $status = proc_get_status( $proc ); self::terminate_proc( $status['pid'] ); } if ( self::$log_run_times ) { self::log_run_times_after_scenario( $event ); } } /** * Terminate a process and any of its children. */ private static function terminate_proc( $master_pid ) { $output = `ps -o ppid,pid,command | grep $master_pid`; foreach ( explode( PHP_EOL, $output ) as $line ) { if ( preg_match( '/^\s*(\d+)\s+(\d+)/', $line, $matches ) ) { $parent = $matches[1]; $child = $matches[2]; if ( $parent == $master_pid ) { self::terminate_proc( $child ); } } } if ( ! posix_kill( (int) $master_pid, 9 ) ) { $errno = posix_get_last_error(); // Ignore "No such process" error as that's what we want. if ( 3 /*ESRCH*/ !== $errno ) { throw new RuntimeException( posix_strerror( $errno ) ); } } } /** * Create a temporary WP_CLI_CACHE_DIR. Exposed as SUITE_CACHE_DIR in "Given an empty cache" step. */ public static function create_cache_dir() { if ( self::$suite_cache_dir ) { self::remove_dir( self::$suite_cache_dir ); } self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-suite-cache-' . self::$temp_dir_infix . '-', TRUE ); mkdir( self::$suite_cache_dir ); return self::$suite_cache_dir; } /** * Initializes context. * Every scenario gets its own context object. * * @param array $parameters context parameters (set them up through behat.yml) */ public function __construct( array $parameters ) { if ( getenv( 'WP_CLI_TEST_DBUSER' ) ) { self::$db_settings['dbuser'] = getenv( 'WP_CLI_TEST_DBUSER' ); } if ( false !== getenv( 'WP_CLI_TEST_DBPASS' ) ) { self::$db_settings['dbpass'] = getenv( 'WP_CLI_TEST_DBPASS' ); } if ( getenv( 'WP_CLI_TEST_DBHOST' ) ) { self::$db_settings['dbhost'] = getenv( 'WP_CLI_TEST_DBHOST' ); } $this->drop_db(); $this->set_cache_dir(); $this->variables['CORE_CONFIG_SETTINGS'] = Utils\assoc_args_to_str( self::$db_settings ); } public function getStepDefinitionResources() { return glob( __DIR__ . '/../steps/*.php' ); } public function getHookDefinitionResources() { return array(); } /** * Replace {VARIABLE_NAME}. Note that variable names can only contain uppercase letters and underscores (no numbers). */ public function replace_variables( $str ) { $ret = preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $str ); if ( false !== strpos( $str, '{WP_VERSION-' ) ) { $ret = $this->_replace_wp_versions( $ret ); } return $ret; } /** * Replace variables callback. */ private function _replace_var( $matches ) { $cmd = $matches[0]; foreach ( array_slice( $matches, 1 ) as $key ) { $cmd = str_replace( '{' . $key . '}', $this->variables[ $key ], $cmd ); } return $cmd; } /** * Substitute "{WP_VERSION-version-latest}" variables. */ private function _replace_wp_versions( $str ) { static $wp_versions = null; if ( null === $wp_versions ) { $wp_versions = array(); $response = Requests::get( 'https://api.wordpress.org/core/version-check/1.7/', null, array( 'timeout' => 30 ) ); if ( 200 === $response->status_code && ( $body = json_decode( $response->body ) ) && is_object( $body ) && isset( $body->offers ) && is_array( $body->offers ) ) { // Latest version alias. $wp_versions["{WP_VERSION-latest}"] = count( $body->offers ) ? $body->offers[0]->version : ''; foreach ( $body->offers as $offer ) { $sub_ver = preg_replace( '/(^[0-9]+\.[0-9]+)\.[0-9]+$/', '$1', $offer->version ); $sub_ver_key = "{WP_VERSION-{$sub_ver}-latest}"; $main_ver = preg_replace( '/(^[0-9]+)\.[0-9]+$/', '$1', $sub_ver ); $main_ver_key = "{WP_VERSION-{$main_ver}-latest}"; if ( ! isset( $wp_versions[ $main_ver_key ] ) ) { $wp_versions[ $main_ver_key ] = $offer->version; } if ( ! isset( $wp_versions[ $sub_ver_key ] ) ) { $wp_versions[ $sub_ver_key ] = $offer->version; } } } } return strtr( $str, $wp_versions ); } /** * Get the file and line number for the current behat event. */ private static function get_event_file( $event, &$line ) { if ( method_exists( $event, 'getScenario' ) ) { $scenario_feature = $event->getScenario(); } elseif ( method_exists( $event, 'getFeature' ) ) { $scenario_feature = $event->getFeature(); } elseif ( method_exists( $event, 'getOutline' ) ) { $scenario_feature = $event->getOutline(); } else { return null; } $line = $scenario_feature->getLine(); return $scenario_feature->getFile(); } /** * Create the RUN_DIR directory, unless already set for this scenario. */ public function create_run_dir() { if ( !isset( $this->variables['RUN_DIR'] ) ) { self::$run_dir = $this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', TRUE ); mkdir( $this->variables['RUN_DIR'] ); } } public function build_phar( $version = 'same' ) { $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' . uniqid( "wp-cli-build-", TRUE ) . '.phar'; // Test running against a package installed as a WP-CLI dependency // WP-CLI installed as a project dependency $make_phar_path = __DIR__ . '/../../../../../utils/make-phar.php'; if ( ! file_exists( $make_phar_path ) ) { // Test running against WP-CLI proper $make_phar_path = __DIR__ . '/../../utils/make-phar.php'; if ( ! file_exists( $make_phar_path ) ) { // WP-CLI as a dependency of this project $make_phar_path = __DIR__ . '/../../vendor/wp-cli/wp-cli/utils/make-phar.php'; } } $this->proc( Utils\esc_cmd( 'php -dphar.readonly=0 %1$s %2$s --version=%3$s && chmod +x %2$s', $make_phar_path, $this->variables['PHAR_PATH'], $version ) )->run_check(); } public function download_phar( $version = 'same' ) { if ( 'same' === $version ) { $version = WP_CLI_VERSION; } $download_url = sprintf( 'https://github.com/wp-cli/wp-cli/releases/download/v%1$s/wp-cli-%1$s.phar', $version ); $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' . uniqid( 'wp-cli-download-', true ) . '.phar'; Process::create( Utils\esc_cmd( 'curl -sSfL %1$s > %2$s && chmod +x %2$s', $download_url, $this->variables['PHAR_PATH'] ) )->run_check(); } /** * CACHE_DIR is a cache for downloaded test data such as images. Lives until manually deleted. */ private function set_cache_dir() { $path = sys_get_temp_dir() . '/wp-cli-test-cache'; if ( ! file_exists( $path ) ) { mkdir( $path ); } $this->variables['CACHE_DIR'] = $path; } /** * Run a MySQL command with `$db_settings`. * * @param string $sql_cmd Command to run. * @param array $assoc_args Optional. Associative array of options. Default empty. * @param bool $add_database Optional. Whether to add dbname to the $sql_cmd. Default false. */ private static function run_sql( $sql_cmd, $assoc_args = array(), $add_database = false ) { $default_assoc_args = array( 'host' => self::$db_settings['dbhost'], 'user' => self::$db_settings['dbuser'], 'pass' => self::$db_settings['dbpass'], ); if ( $add_database ) { $sql_cmd .= ' ' . escapeshellarg( self::$db_settings['dbname'] ); } $start_time = microtime( true ); Utils\run_mysql_command( $sql_cmd, array_merge( $assoc_args, $default_assoc_args ) ); if ( self::$log_run_times ) { self::log_proc_method_run_time( 'run_sql ' . $sql_cmd, $start_time ); } } public function create_db() { $dbname = self::$db_settings['dbname']; self::run_sql( 'mysql --no-defaults', array( 'execute' => "CREATE DATABASE IF NOT EXISTS $dbname" ) ); } public function drop_db() { $dbname = self::$db_settings['dbname']; self::run_sql( 'mysql --no-defaults', array( 'execute' => "DROP DATABASE IF EXISTS $dbname" ) ); } public function proc( $command, $assoc_args = array(), $path = '' ) { if ( !empty( $assoc_args ) ) $command .= Utils\assoc_args_to_str( $assoc_args ); $env = self::get_process_env_variables(); if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; } if ( isset( $this->variables['RUN_DIR'] ) ) { $cwd = "{$this->variables['RUN_DIR']}/{$path}"; } else { $cwd = null; } return Process::create( $command, $cwd, $env ); } /** * Start a background process. Will automatically be closed when the tests finish. */ public function background_proc( $cmd ) { $descriptors = array( 0 => STDIN, 1 => array( 'pipe', 'w' ), 2 => array( 'pipe', 'w' ), ); $proc = proc_open( $cmd, $descriptors, $pipes, $this->variables['RUN_DIR'], self::get_process_env_variables() ); sleep(1); $status = proc_get_status( $proc ); if ( !$status['running'] ) { throw new RuntimeException( stream_get_contents( $pipes[2] ) ); } else { $this->running_procs[] = $proc; } } public function move_files( $src, $dest ) { rename( $this->variables['RUN_DIR'] . "/$src", $this->variables['RUN_DIR'] . "/$dest" ); } /** * Remove a directory (recursive). */ public static function remove_dir( $dir ) { Process::create( Utils\esc_cmd( 'rm -rf %s', $dir ) )->run_check(); } /** * Copy a directory (recursive). Destination directory must exist. */ public static function copy_dir( $src_dir, $dest_dir ) { Process::create( Utils\esc_cmd( "cp -r %s/* %s", $src_dir, $dest_dir ) )->run_check(); } public function add_line_to_wp_config( &$wp_config_code, $line ) { $token = "/* That's all, stop editing!"; $wp_config_code = str_replace( $token, "$line\n\n$token", $wp_config_code ); } public function download_wp( $subdir = '' ) { $dest_dir = $this->variables['RUN_DIR'] . "/$subdir"; if ( $subdir ) { mkdir( $dest_dir ); } self::copy_dir( self::$cache_dir, $dest_dir ); // disable emailing mkdir( $dest_dir . '/wp-content/mu-plugins' ); copy( __DIR__ . '/../extra/no-mail.php', $dest_dir . '/wp-content/mu-plugins/no-mail.php' ); } public function create_config( $subdir = '', $extra_php = false ) { $params = self::$db_settings; // Replaces all characters that are not alphanumeric or an underscore into an underscore. $params['dbprefix'] = $subdir ? preg_replace( '#[^a-zA-Z\_0-9]#', '_', $subdir ) : 'wp_'; $params['skip-salts'] = true; if( false !== $extra_php ) { $params['extra-php'] = $extra_php; } $config_cache_path = ''; if ( self::$install_cache_dir ) { $config_cache_path = self::$install_cache_dir . '/config_' . md5( implode( ':', $params ) . ':subdir=' . $subdir ); $run_dir = '' !== $subdir ? ( $this->variables['RUN_DIR'] . "/$subdir" ) : $this->variables['RUN_DIR']; } if ( $config_cache_path && file_exists( $config_cache_path ) ) { copy( $config_cache_path, $run_dir . '/wp-config.php' ); } else { $this->proc( 'wp config create', $params, $subdir )->run_check(); if ( $config_cache_path && file_exists( $run_dir . '/wp-config.php' ) ) { copy( $run_dir . '/wp-config.php', $config_cache_path ); } } } public function install_wp( $subdir = '' ) { $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; self::$install_cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-install-cache' . $wp_version_suffix; if ( ! file_exists( self::$install_cache_dir ) ) { mkdir( self::$install_cache_dir ); } $subdir = $this->replace_variables( $subdir ); $this->create_db(); $this->create_run_dir(); $this->download_wp( $subdir ); $this->create_config( $subdir ); $install_args = array( 'url' => 'http://example.com', 'title' => 'WP CLI Site', 'admin_user' => 'admin', 'admin_email' => 'admin@example.com', 'admin_password' => 'password1' ); $install_cache_path = ''; if ( self::$install_cache_dir ) { $install_cache_path = self::$install_cache_dir . '/install_' . md5( implode( ':', $install_args ) . ':subdir=' . $subdir ); $run_dir = '' !== $subdir ? ( $this->variables['RUN_DIR'] . "/$subdir" ) : $this->variables['RUN_DIR']; } if ( $install_cache_path && file_exists( $install_cache_path ) ) { self::copy_dir( $install_cache_path, $run_dir ); self::run_sql( 'mysql --no-defaults', array( 'execute' => "source {$install_cache_path}.sql" ), true /*add_database*/ ); } else { $this->proc( 'wp core install', $install_args, $subdir )->run_check(); if ( $install_cache_path ) { mkdir( $install_cache_path ); self::dir_diff_copy( $run_dir, self::$cache_dir, $install_cache_path ); self::run_sql( 'mysqldump --no-defaults', array( 'result-file' => "{$install_cache_path}.sql" ), true /*add_database*/ ); } } } public function install_wp_with_composer( $vendor_directory = 'vendor' ) { $this->create_run_dir(); $this->create_db(); $yml_path = $this->variables['RUN_DIR'] . "/wp-cli.yml"; file_put_contents( $yml_path, 'path: wordpress' ); $this->composer_command( 'init --name="wp-cli/composer-test" --type="project" --no-interaction' ); $this->composer_command( 'config vendor-dir ' . $vendor_directory ); $this->composer_command( 'require johnpbloch/wordpress --optimize-autoloader --no-interaction' ); $config_extra_php = "require_once dirname(__DIR__) . '/" . $vendor_directory . "/autoload.php';"; $this->create_config( 'wordpress', $config_extra_php ); $install_args = array( 'url' => 'http://localhost:8080', 'title' => 'WP CLI Site with both WordPress and wp-cli as Composer dependencies', 'admin_user' => 'admin', 'admin_email' => 'admin@example.com', 'admin_password' => 'password1' ); $this->proc( 'wp core install', $install_args )->run_check(); } public function composer_add_wp_cli_local_repository() { if ( ! self::$composer_local_repository ) { self::$composer_local_repository = sys_get_temp_dir() . '/' . uniqid( "wp-cli-composer-local-", TRUE ); mkdir( self::$composer_local_repository ); $env = self::get_process_env_variables(); $src = isset( $env['TRAVIS_BUILD_DIR'] ) ? $env['TRAVIS_BUILD_DIR'] : realpath( __DIR__ . '/../../' ); self::copy_dir( $src, self::$composer_local_repository . '/' ); self::remove_dir( self::$composer_local_repository . '/.git' ); self::remove_dir( self::$composer_local_repository . '/vendor' ); } $dest = self::$composer_local_repository . '/'; $this->composer_command( "config repositories.wp-cli '{\"type\": \"path\", \"url\": \"$dest\", \"options\": {\"symlink\": false}}'" ); $this->variables['COMPOSER_LOCAL_REPOSITORY'] = self::$composer_local_repository; } public function composer_require_current_wp_cli() { $this->composer_add_wp_cli_local_repository(); $this->composer_command( 'require wp-cli/wp-cli:dev-master --optimize-autoloader --no-interaction' ); } public function get_php_binary() { if ( getenv( 'WP_CLI_PHP_USED' ) ) return getenv( 'WP_CLI_PHP_USED' ); if ( getenv( 'WP_CLI_PHP' ) ) return getenv( 'WP_CLI_PHP' ); if ( defined( 'PHP_BINARY' ) ) return PHP_BINARY; return 'php'; } public function start_php_server( $subdir = '' ) { $dir = $this->variables['RUN_DIR'] . '/'; if ( $subdir ) { $dir .= trim( $subdir, '/' ) . '/'; } $cmd = Utils\esc_cmd( '%s -S %s -t %s -c %s %s', $this->get_php_binary(), 'localhost:8080', $dir, get_cfg_var( 'cfg_file_path' ), $this->variables['RUN_DIR'] . '/vendor/wp-cli/server-command/router.php' ); $this->background_proc( $cmd ); } private function composer_command($cmd) { if ( !isset( $this->variables['COMPOSER_PATH'] ) ) { $this->variables['COMPOSER_PATH'] = exec('which composer'); } $this->proc( $this->variables['COMPOSER_PATH'] . ' ' . $cmd )->run_check(); } /** * Initialize run time logging. */ private static function log_run_times_before_suite( $event ) { self::$suite_start_time = microtime( true ); Process::$log_run_times = true; $travis = getenv( 'TRAVIS' ); // Default output settings. self::$output_to = 'stdout'; self::$num_top_processes = $travis ? 10 : 40; self::$num_top_scenarios = $travis ? 10 : 20; // Allow setting of above with "WP_CLI_TEST_LOG_RUN_TIMES=<output_to>[,<num_top_processes>][,<num_top_scenarios>]" formatted env var. if ( preg_match( '/^(stdout|error_log)?(,[0-9]+)?(,[0-9]+)?$/i', self::$log_run_times, $matches ) ) { if ( isset( $matches[1] ) ) { self::$output_to = strtolower( $matches[1] ); } if ( isset( $matches[2] ) ) { self::$num_top_processes = max( (int) substr( $matches[2], 1 ), 1 ); } if ( isset( $matches[3] ) ) { self::$num_top_scenarios = max( (int) substr( $matches[3], 1 ), 1 ); } } } /** * Record the start time of the scenario into the `$scenario_run_times` array. */ private static function log_run_times_before_scenario( $event ) { if ( $scenario_key = self::get_scenario_key( $event ) ) { self::$scenario_run_times[ $scenario_key ] = -microtime( true ); } } /** * Save the run time of the scenario into the `$scenario_run_times` array. Only the top `self::$num_top_scenarios` are kept. */ private static function log_run_times_after_scenario( $event ) { if ( $scenario_key = self::get_scenario_key( $event ) ) { self::$scenario_run_times[ $scenario_key ] += microtime( true ); self::$scenario_count++; if ( count( self::$scenario_run_times ) > self::$num_top_scenarios ) { arsort( self::$scenario_run_times ); array_pop( self::$scenario_run_times ); } } } /** * Copy files in updated directory that are not in source directory to copy directory. ("Incremental backup".) * Note: does not deal with changed files (ie does not compare file contents for changes), for speed reasons. * * @param string $upd_dir The directory to search looking for files/directories not in `$src_dir`. * @param string $src_dir The directory to be compared to `$upd_dir`. * @param string $cop_dir Where to copy any files/directories in `$upd_dir` but not in `$src_dir` to. */ private static function dir_diff_copy( $upd_dir, $src_dir, $cop_dir ) { if ( false === ( $files = scandir( $upd_dir ) ) ) { $error = error_get_last(); throw new \RuntimeException( sprintf( "Failed to open updated directory '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_dir, $error['message'] ) ); } foreach ( array_diff( $files, array( '.', '..' ) ) as $file ) { $upd_file = $upd_dir . '/' . $file; $src_file = $src_dir . '/' . $file; $cop_file = $cop_dir . '/' . $file; if ( ! file_exists( $src_file ) ) { if ( is_dir( $upd_file ) ) { if ( ! file_exists( $cop_file ) && ! mkdir( $cop_file, 0777, true /*recursive*/ ) ) { $error = error_get_last(); throw new \RuntimeException( sprintf( "Failed to create copy directory '%s': %s. " . __FILE__ . ':' . __LINE__, $cop_file, $error['message'] ) ); } self::copy_dir( $upd_file, $cop_file ); } else { if ( ! copy( $upd_file, $cop_file ) ) { $error = error_get_last(); throw new \RuntimeException( sprintf( "Failed to copy '%s' to '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_file, $cop_file, $error['message'] ) ); } } } elseif ( is_dir( $upd_file ) ) { self::dir_diff_copy( $upd_file, $src_file, $cop_file ); } } } /** * Get the scenario key used for `$scenario_run_times` array. * Format "<grandparent-dir> <feature-file>:<line-number>", eg "core-command core-update.feature:221". */ private static function get_scenario_key( $event ) { $scenario_key = ''; if ( $file = self::get_event_file( $event, $line ) ) { $scenario_grandparent = Utils\basename( dirname( dirname( $file ) ) ); $scenario_key = $scenario_grandparent . ' ' . Utils\basename( $file ) . ':' . $line; } return $scenario_key; } /** * Print out stats on the run times of processes and scenarios. */ private static function log_run_times_after_suite( $event ) { $suite = ''; if ( self::$scenario_run_times ) { // Grandparent directory is first part of key. $keys = array_keys( self::$scenario_run_times ); $suite = substr( $keys[0], 0, strpos( $keys[0], ' ' ) ); } $run_from = Utils\basename( dirname( dirname( __DIR__ ) ) ); // Format same as Behat, if have minutes. $fmt = function ( $time ) { $mins = floor( $time / 60 ); return round( $time, 3 ) . ( $mins ? ( ' (' . $mins . 'm' . round( $time - ( $mins * 60 ), 3 ) . 's)' ) : '' ); }; $time = microtime( true ) - self::$suite_start_time; $log = PHP_EOL . str_repeat( '(', 80 ) . PHP_EOL; // Process and proc method run times. $run_times = array_merge( Process::$run_times, self::$proc_method_run_times ); list( $ptime, $calls ) = array_reduce( $run_times, function ( $carry, $item ) { return array( $carry[0] + $item[0], $carry[1] + $item[1] ); }, array( 0, 0 ) ); $overhead = $time - $ptime; $pct = round( ( $overhead / $time ) * 100 ); $unique = count( $run_times ); $log .= sprintf( PHP_EOL . "Total process run time %s (tests %s, overhead %.3f %d%%), calls %d (%d unique) for '%s' run from '%s'" . PHP_EOL, $fmt( $ptime ), $fmt( $time ), $overhead, $pct, $calls, $unique, $suite, $run_from ); uasort( $run_times, function ( $a, $b ) { return $a[0] === $b[0] ? 0 : ( $a[0] < $b[0] ? 1 : -1 ); // Reverse sort. } ); $tops = array_slice( $run_times, 0, self::$num_top_processes, true ); $log .= PHP_EOL . "Top " . self::$num_top_processes . " process run times for '$suite'"; $log .= PHP_EOL . implode( PHP_EOL, array_map( function ( $k, $v, $i ) { return sprintf( ' %3d. %7.3f %3d %s', $i + 1, round( $v[0], 3 ), $v[1], $k ); }, array_keys( $tops ), $tops, array_keys( array_keys( $tops ) ) ) ) . PHP_EOL; // Scenario run times. arsort( self::$scenario_run_times ); $tops = array_slice( self::$scenario_run_times, 0, self::$num_top_scenarios, true ); $log .= PHP_EOL . "Top " . self::$num_top_scenarios . " (of " . self::$scenario_count . ") scenario run times for '$suite'"; $log .= PHP_EOL . implode( PHP_EOL, array_map( function ( $k, $v, $i ) { return sprintf( ' %3d. %7.3f %s', $i + 1, round( $v, 3 ), substr( $k, strpos( $k, ' ' ) + 1 ) ); }, array_keys( $tops ), $tops, array_keys( array_keys( $tops ) ) ) ) . PHP_EOL; $log .= PHP_EOL . str_repeat( ')', 80 ); if ( 'error_log' === self::$output_to ) { error_log( $log ); } else { echo PHP_EOL . $log; } } /** * Log the run time of a proc method (one that doesn't use Process but does (use a function that does) a `proc_open()`). */ private static function log_proc_method_run_time( $key, $start_time ) { $run_time = microtime( true ) - $start_time; if ( ! isset( self::$proc_method_run_times[ $key ] ) ) { self::$proc_method_run_times[ $key ] = array( 0, 0 ); } self::$proc_method_run_times[ $key ][0] += $run_time; self::$proc_method_run_times[ $key ][1]++; } } <?php use Behat\Gherkin\Node\PyStringNode, Behat\Gherkin\Node\TableNode, WP_CLI\Process; function invoke_proc( $proc, $mode ) { $map = array( 'run' => 'run_check', 'try' => 'run' ); $method = $map[ $mode ]; return $proc->$method(); } function capture_email_sends( $stdout ) { $stdout = preg_replace( '#WP-CLI test suite: Sent email to.+\n?#', '', $stdout, -1, $email_sends ); return array( $stdout, $email_sends ); } $steps->When( '/^I launch in the background `([^`]+)`$/', function ( $world, $cmd ) { $world->background_proc( $cmd ); } ); $steps->When( '/^I (run|try) `([^`]+)`$/', function ( $world, $mode, $cmd ) { $cmd = $world->replace_variables( $cmd ); $world->result = invoke_proc( $world->proc( $cmd ), $mode ); list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); } ); $steps->When( "/^I (run|try) `([^`]+)` from '([^\s]+)'$/", function ( $world, $mode, $cmd, $subdir ) { $cmd = $world->replace_variables( $cmd ); $world->result = invoke_proc( $world->proc( $cmd, array(), $subdir ), $mode ); list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); } ); $steps->When( '/^I (run|try) the previous command again$/', function ( $world, $mode ) { if ( !isset( $world->result ) ) throw new \Exception( 'No previous command.' ); $proc = Process::create( $world->result->command, $world->result->cwd, $world->result->env ); $world->result = invoke_proc( $proc, $mode ); list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); } ); <?php use Behat\Gherkin\Node\PyStringNode, Behat\Gherkin\Node\TableNode; $steps->Then( '/^the return code should be (\d+)$/', function ( $world, $return_code ) { if ( $return_code != $world->result->return_code ) { throw new RuntimeException( $world->result ); } } ); $steps->Then( '/^(STDOUT|STDERR) should (be|contain|not contain):$/', function ( $world, $stream, $action, PyStringNode $expected ) { $stream = strtolower( $stream ); $expected = $world->replace_variables( (string) $expected ); checkString( $world->result->$stream, $expected, $action, $world->result ); } ); $steps->Then( '/^(STDOUT|STDERR) should be a number$/', function ( $world, $stream ) { $stream = strtolower( $stream ); assertNumeric( trim( $world->result->$stream, "\n" ) ); } ); $steps->Then( '/^(STDOUT|STDERR) should not be a number$/', function ( $world, $stream ) { $stream = strtolower( $stream ); assertNotNumeric( trim( $world->result->$stream, "\n" ) ); } ); $steps->Then( '/^STDOUT should be a table containing rows:$/', function ( $world, TableNode $expected ) { $output = $world->result->stdout; $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); $expected_rows = array(); foreach ( $expected->getRows() as $row ) { $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); } compareTables( $expected_rows, $actual_rows, $output ); } ); $steps->Then( '/^STDOUT should end with a table containing rows:$/', function ( $world, TableNode $expected ) { $output = $world->result->stdout; $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); $expected_rows = array(); foreach ( $expected->getRows() as $row ) { $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); } $start = array_search( $expected_rows[0], $actual_rows ); if ( false === $start ) throw new \Exception( $world->result ); compareTables( $expected_rows, array_slice( $actual_rows, $start ), $output ); } ); $steps->Then( '/^STDOUT should be JSON containing:$/', function ( $world, PyStringNode $expected ) { $output = $world->result->stdout; $expected = $world->replace_variables( (string) $expected ); if ( !checkThatJsonStringContainsJsonString( $output, $expected ) ) { throw new \Exception( $world->result ); } }); $steps->Then( '/^STDOUT should be a JSON array containing:$/', function ( $world, PyStringNode $expected ) { $output = $world->result->stdout; $expected = $world->replace_variables( (string) $expected ); $actualValues = json_decode( $output ); $expectedValues = json_decode( $expected ); $missing = array_diff( $expectedValues, $actualValues ); if ( !empty( $missing ) ) { throw new \Exception( $world->result ); } }); $steps->Then( '/^STDOUT should be CSV containing:$/', function ( $world, TableNode $expected ) { $output = $world->result->stdout; $expected_rows = $expected->getRows(); foreach ( $expected as &$row ) { foreach ( $row as &$value ) { $value = $world->replace_variables( $value ); } } if ( ! checkThatCsvStringContainsValues( $output, $expected_rows ) ) throw new \Exception( $world->result ); } ); $steps->Then( '/^STDOUT should be YAML containing:$/', function ( $world, PyStringNode $expected ) { $output = $world->result->stdout; $expected = $world->replace_variables( (string) $expected ); if ( !checkThatYamlStringContainsYamlString( $output, $expected ) ) { throw new \Exception( $world->result ); } }); $steps->Then( '/^(STDOUT|STDERR) should be empty$/', function ( $world, $stream ) { $stream = strtolower( $stream ); if ( !empty( $world->result->$stream ) ) { throw new \Exception( $world->result ); } } ); $steps->Then( '/^(STDOUT|STDERR) should not be empty$/', function ( $world, $stream ) { $stream = strtolower( $stream ); if ( '' === rtrim( $world->result->$stream, "\n" ) ) { throw new Exception( $world->result ); } } ); $steps->Then( '/^(STDOUT|STDERR) should be a version string (<|<=|>|>=|==|=|!=|<>) ([+\w.{}-]+)$/', function ( $world, $stream, $operator, $goal_ver ) { $goal_ver = $world->replace_variables( $goal_ver ); $stream = strtolower( $stream ); if ( false === version_compare( trim( $world->result->$stream, "\n" ), $goal_ver, $operator ) ) { throw new Exception( $world->result ); } } ); $steps->Then( '/^the (.+) (file|directory) should (exist|not exist|be:|contain:|not contain:)$/', function ( $world, $path, $type, $action, $expected = null ) { $path = $world->replace_variables( $path ); // If it's a relative path, make it relative to the current test dir if ( '/' !== $path[0] ) $path = $world->variables['RUN_DIR'] . "/$path"; if ( 'file' == $type ) { $test = 'file_exists'; } else if ( 'directory' == $type ) { $test = 'is_dir'; } switch ( $action ) { case 'exist': if ( ! $test( $path ) ) { throw new Exception( "$path doesn't exist." ); } break; case 'not exist': if ( $test( $path ) ) { throw new Exception( "$path exists." ); } break; default: if ( ! $test( $path ) ) { throw new Exception( "$path doesn't exist." ); } $action = substr( $action, 0, -1 ); $expected = $world->replace_variables( (string) $expected ); if ( 'file' == $type ) { $contents = file_get_contents( $path ); } else if ( 'directory' == $type ) { $files = glob( rtrim( $path, '/' ) . '/*' ); foreach( $files as &$file ) { $file = str_replace( $path . '/', '', $file ); } $contents = implode( PHP_EOL, $files ); } checkString( $contents, $expected, $action ); } } ); $steps->Then( '/^the contents of the (.+) file should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', function ( $world, $path, $expected ) { $path = $world->replace_variables( $path ); // If it's a relative path, make it relative to the current test dir if ( '/' !== $path[0] ) { $path = $world->variables['RUN_DIR'] . "/$path"; } $contents = file_get_contents( $path ); assertRegExp( $expected, $contents ); } ); $steps->Then( '/^(STDOUT|STDERR) should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', function ( $world, $stream, $expected ) { $stream = strtolower( $stream ); assertRegExp( $expected, $world->result->$stream ); } ); $steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $expected ) { if ( 'be sent' === $expected ) { assertNotEquals( 0, $world->email_sends ); } else if ( 'not be sent' === $expected ) { assertEquals( 0, $world->email_sends ); } else { throw new Exception( 'Invalid expectation' ); } }); $steps->Then( '/^the HTTP status code should be (\d+)$/', function ( $world, $return_code ) { $response = \Requests::request( 'http://localhost:8080' ); assertEquals( $return_code, $response->status_code ); } ); <?php use Behat\Gherkin\Node\PyStringNode, Behat\Gherkin\Node\TableNode, WP_CLI\Process; $steps->Given( '/^an empty directory$/', function ( $world ) { $world->create_run_dir(); } ); $steps->Given( '/^an? (empty|non-existent) ([^\s]+) directory$/', function ( $world, $empty_or_nonexistent, $dir ) { $dir = $world->replace_variables( $dir ); if ( ! WP_CLI\Utils\is_path_absolute( $dir ) ) { $dir = $world->variables['RUN_DIR'] . "/$dir"; } if ( 0 !== strpos( $dir, sys_get_temp_dir() ) ) { throw new RuntimeException( sprintf( "Attempted to delete directory '%s' that is not in the temp directory '%s'. " . __FILE__ . ':' . __LINE__, $dir, sys_get_temp_dir() ) ); } $world->remove_dir( $dir ); if ( 'empty' === $empty_or_nonexistent ) { mkdir( $dir, 0777, true /*recursive*/ ); } } ); $steps->Given( '/^an empty cache/', function ( $world ) { $world->variables['SUITE_CACHE_DIR'] = FeatureContext::create_cache_dir(); } ); $steps->Given( '/^an? ([^\s]+) file:$/', function ( $world, $path, PyStringNode $content ) { $content = (string) $content . "\n"; $full_path = $world->variables['RUN_DIR'] . "/$path"; $dir = dirname( $full_path ); if ( ! file_exists( $dir ) ) { mkdir( $dir, 0777, true /*recursive*/ ); } file_put_contents( $full_path, $content ); } ); $steps->Given( '/^"([^"]+)" replaced with "([^"]+)" in the ([^\s]+) file$/', function( $world, $search, $replace, $path ) { $full_path = $world->variables['RUN_DIR'] . "/$path"; $contents = file_get_contents( $full_path ); $contents = str_replace( $search, $replace, $contents ); file_put_contents( $full_path, $contents ); }); $steps->Given( '/^WP files$/', function ( $world ) { $world->download_wp(); } ); $steps->Given( '/^wp-config\.php$/', function ( $world ) { $world->create_config(); } ); $steps->Given( '/^a database$/', function ( $world ) { $world->create_db(); } ); $steps->Given( '/^a WP install$/', function ( $world ) { $world->install_wp(); } ); $steps->Given( "/^a WP install in '([^\s]+)'$/", function ( $world, $subdir ) { $world->install_wp( $subdir ); } ); $steps->Given( '/^a WP install with Composer$/', function ( $world ) { $world->install_wp_with_composer(); } ); $steps->Given( "/^a WP install with Composer and a custom vendor directory '([^\s]+)'$/", function ( $world, $vendor_directory ) { $world->install_wp_with_composer( $vendor_directory ); } ); $steps->Given( '/^a WP multisite (subdirectory|subdomain)?\s?install$/', function ( $world, $type = 'subdirectory' ) { $world->install_wp(); $subdomains = ! empty( $type ) && 'subdomain' === $type ? 1 : 0; $world->proc( 'wp core install-network', array( 'title' => 'WP CLI Network', 'subdomains' => $subdomains ) )->run_check(); } ); $steps->Given( '/^these installed and active plugins:$/', function( $world, $stream ) { $plugins = implode( ' ', array_map( 'trim', explode( PHP_EOL, (string)$stream ) ) ); $world->proc( "wp plugin install $plugins --activate" )->run_check(); } ); $steps->Given( '/^a custom wp-content directory$/', function ( $world ) { $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; $wp_config_code = file_get_contents( $wp_config_path ); $world->move_files( 'wp-content', 'my-content' ); $world->add_line_to_wp_config( $wp_config_code, "define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/my-content' );" ); $world->move_files( 'my-content/plugins', 'my-plugins' ); $world->add_line_to_wp_config( $wp_config_code, "define( 'WP_PLUGIN_DIR', __DIR__ . '/my-plugins' );" ); file_put_contents( $wp_config_path, $wp_config_code ); } ); $steps->Given( '/^download:$/', function ( $world, TableNode $table ) { foreach ( $table->getHash() as $row ) { $path = $world->replace_variables( $row['path'] ); if ( file_exists( $path ) ) { // assume it's the same file and skip re-download continue; } Process::create( \WP_CLI\Utils\esc_cmd( 'curl -sSL %s > %s', $row['url'], $path ) )->run_check(); } } ); $steps->Given( '/^save (STDOUT|STDERR) ([\'].+[^\'])?\s?as \{(\w+)\}$/', function ( $world, $stream, $output_filter, $key ) { $stream = strtolower( $stream ); if ( $output_filter ) { $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; if ( false !== preg_match( $output_filter, $world->result->$stream, $matches ) ) $output = array_pop( $matches ); else $output = ''; } else { $output = $world->result->$stream; } $world->variables[ $key ] = trim( $output, "\n" ); } ); $steps->Given( '/^a new Phar with (?:the same version|version "([^"]+)")$/', function ( $world, $version = 'same' ) { $world->build_phar( $version ); } ); $steps->Given( '/^a downloaded Phar with (?:the same version|version "([^"]+)")$/', function ( $world, $version = 'same' ) { $world->download_phar( $version ); } ); $steps->Given( '/^save the (.+) file ([\'].+[^\'])?as \{(\w+)\}$/', function ( $world, $filepath, $output_filter, $key ) { $full_file = file_get_contents( $world->replace_variables( $filepath ) ); if ( $output_filter ) { $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; if ( false !== preg_match( $output_filter, $full_file, $matches ) ) $output = array_pop( $matches ); else $output = ''; } else { $output = $full_file; } $world->variables[ $key ] = trim( $output, "\n" ); } ); $steps->Given('/^a misconfigured WP_CONTENT_DIR constant directory$/', function($world) { $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; $wp_config_code = file_get_contents( $wp_config_path ); $world->add_line_to_wp_config( $wp_config_code, "define( 'WP_CONTENT_DIR', '' );" ); file_put_contents( $wp_config_path, $wp_config_code ); } ); $steps->Given( '/^a dependency on current wp-cli$/', function ( $world ) { $world->composer_require_current_wp_cli(); } ); $steps->Given( '/^a PHP built-in web server$/', function ( $world ) { $world->start_php_server(); } ); $steps->Given( "/^a PHP built-in web server to serve '([^\s]+)'$/", function ( $world, $subdir ) { $world->start_php_server( $subdir ); } ); <?php function wp_mail( $to ) { // Log for testing purposes WP_CLI::log( "WP-CLI test suite: Sent email to {$to}." ); } <?php require_once dirname( __FILE__ ) . '/Oxymel.php'; class OxymelTest extends PHPUnit_Framework_TestCase { function __construct() { $this->x = new Oxymel; } function test_xml_adds_procesing_instrution_with_version_and_encoding() { $this->a( '<?xml version="1.0" encoding="UTF-8"?>', $this->x->xml ); } function test_self_closing() { $this->a('<baba/>', $this->x->baba); } function test_self_closing_method() { $this->a('<baba/>', $this->x->baba()); } function test_attribute_no_content() { $this->a('<baba a="b" c="d"/>', $this->x->baba( array( 'a' => 'b', 'c' => 'd' ) ) ); } function test_content_no_attribute() { $this->a('<baba>content</baba>', $this->x->baba( 'content' ) ); } function test_escapiing_of_content() { $this->a('<baba><</baba>', $this->x->baba( '<' ) ); } function test_escaping_of_attributes() { $this->a('<baba a="<"/>', $this->x->baba( array( 'a' => '<' ) ) ); } function test_content_and_attributes() { $this->a('<baba a="b" c="d">content</baba>', $this->x->baba( 'content', array( 'a' => 'b', 'c' => 'd' ) ) ); } function test_content_and_attributes_error() { $this->setExpectedException( 'PHPUnit_Framework_Error' ); $this->a('<baba a="b" c="d">content</baba>', $this->x->baba( array( 'a' => 'b', 'c' => 'd' ), 'content' ) ); } function test_go_down() { $this->a('<baba> <dyado/> </baba>', $this->x->baba->contains->dyado->end ); } function test_go_down_and_up() { $this->a("<level0> <level1/> </level0> <level0/>", $this->x->level0->contains->level1->end->level0 ); } function test_cdata() { $this->a('<baba><![CDATA[content]]></baba>', $this->x->baba->contains->cdata('content')->end); } function test_nested_cdata() { $this->a( '<![CDATA[x]]]]><![CDATA[>]]>', $this->x->cdata( 'x]]>' ) ); } function test_raw() { $this->a('<baba> <dyado/> </baba>', $this->x->baba->contains->raw('<dyado></dyado>')->end); } function test_raw_doesnt_do_anything_with_empty_arg() { $this->a('<baba/>', $this->x->baba->raw( '' ) ); } function test_oxymel_ns() { $oxymel = new Oxymel; $oxymel->tag( 'wp:baba' ); $this->a( '<wp:baba/>', $this->x->oxymel( $oxymel ) ); } function x_test_only_up_error() { $this->a('', $this->x->end ); } function test_open_in_the_end() { $this->a("<baba/> <newtag>", $this->x->baba->open_newtag ); } function test_open_with_attributes() { $this->a("<baba/> <newtag a=\"b\">", $this->x->baba->open_newtag( array( 'a' => 'b' ) ) ); } function test_dom_open_dom() { $this->a("<baba/> <newtag> <baba/>", $this->x->baba->open_newtag->baba ); } function test_close_in_the_beginning() { $this->a("</oldtag> <baba/>", $this->x->close_oldtag->baba ); } function test_dom_close_dom() { $this->a(" <baba/> </oldtag> <baba/>", $this->x->baba->close_oldtag->baba ); } function test_baba() { $this->a("<a>baba<x/></a>", $this->x->a('baba')->contains->x->end ); } function test_comment() { $this->a('<!--baba-->', $this->x->comment('baba')); } function test_two_invocations() { $this->x->baba(); $this->x->dyado(); $this->a( "<baba/> <dyado/>", $this->x ); } function test_double_end() { $this->a( "<out> <mid> <in/> </mid> </out> <nextout/>", $this->x->out->contains->mid->contains->in->end->end->nextout ); } function test_end_without_contains() { $this->setExpectedException( 'OxymelException' ); $this->x->end->baba; } function test_leading_contains() { $this->setExpectedException( 'OxymelException' ); $this->a( '<baba/>', $this->x->contains->baba ); } function test_consecutive_contains_should_error() { $this->setExpectedException( 'OxymelException' ); $this->x->baba->contains->contains->dyado->end->wink; } function test_end_without_mathcing_contains_but_with_enough_parents() { $this->setExpectedException( 'OxymelException' ); $this->x->contains->baba->end->end->baba; } function test_contains_after_newly_initialized_dom_should_error() { $this->setExpectedException( 'OxymelException' ); $this->x->baba->open_rss->contains->baba->end; } function test_nested_open_tags_should_be_indented() { $this->a( "<baba> <baba>", $this->x->open_baba->open_baba ); } function test_normal_xml_between_two_opening_tags_should_be_indented() { $this->a( "<baba> <baba/> <baba>", $this->x->open_baba->baba->open_baba ); } function test_normal_xml_after_opening_tag_should_be_indented() { $this->a( "<baba> <baba/>", $this->x->open_baba->baba ); } function test_normal_xml_before_closing_tag_should_be_indented() { $this->a( " <baba/> </baba>", $this->x->baba->close_baba ); } function test_text_should_escape_the_content_inside() { $this->a( 'a & b', $this->x->text( 'a & b' ) ); } function test_dangling_contains_should_be_caught_in_the_end() { $this->setExpectedException( 'OxymelException' ); $this->x->contains->to_string(); } private function a($value, $x) { $this->assertEquals( $value . "\n", $x->to_string()); } } <?php class Oxymel { private $xml; private $dom; private $current_element; private $last_inserted; private $go_deep_on_next_element = 0; private $go_up_on_next_element = 0; private $nesting_level = 0; private $contains_nesting_level = 0; private $indentation= ' '; public function __construct() { $this->xml = ''; $this->init_new_dom(); } public function to_string() { return $this->xml .= $this->indent( $this->xml_from_dom(), $this->nesting_level ); } public function __call( $name, $args ) { array_unshift( $args, $name ); return call_user_func_array( array( $this, 'tag' ), $args ); } public function __get( $name ) { return $this->$name(); } public function contains() { $this->contains_nesting_level++; $this->nesting_level++; if ( $this->go_deep_on_next_element ) { throw new OxymelException( 'contains cannot be used consecutively more than once' ); } $this->go_deep_on_next_element++; return $this; } public function end() { $this->contains_nesting_level--; $this->nesting_level--; if ( $this->contains_nesting_level < 0 ) { throw new OxymelException( 'end is used without a matching contains' ); } $this->go_up_on_next_element++; return $this; } public function tag( $name, $content_or_attributes = null, $attributes = array() ) { list( $content, $attributes ) = $this->get_content_and_attributes_from_tag_args( $content_or_attributes, $attributes ); $is_opening = 0 === strpos( $name, 'open_' ); $is_closing = 0 === strpos( $name, 'close_' ); $name = preg_replace("/^(open|close)_/", '', $name ); $element = $this->create_element( $name, $content, $attributes ); if ( !$is_opening && !$is_closing ) $this->add_element_to_dom( $element ); elseif ( $is_opening ) $this->add_opening_tag_from_element( $element ); elseif ( $is_closing ) $this->add_closing_tag_from_tag_name( $name ); return $this; } public function cdata( $text ) { $this->add_element_to_dom( $this->dom->createCDATASection( $text ) ); return $this; } public function text( $text ) { $this->add_element_to_dom( $this->dom->createTextNode( $text ) ); return $this; } public function comment( $text ) { $this->add_element_to_dom( $this->dom->createComment( $text ) ); return $this; } public function xml() { $this->add_element_to_dom( $this->dom->createProcessingInstruction( 'xml', 'version="1.0" encoding="UTF-8"' ) ); return $this; } public function oxymel( Oxymel $other ) { foreach( $other->dom->childNodes as $child ) { $child = $this->dom->importNode( $child, true ); $this->add_element_to_dom( $child ); } return $this; } public function raw( $raw_xml ) { if ( !$raw_xml ) { return $this; } $fragment = $this->dom->createDocumentFragment(); $fragment->appendXML($raw_xml); $this->add_element_to_dom( $fragment ); return $this; } private function add_element_to_dom( $element ) { $this->move_current_element_deep(); $this->move_current_element_up(); $this->last_inserted = $this->current_element->appendChild($element); } private function move_current_element_deep() { if ( $this->go_deep_on_next_element ) { if ( !$this->last_inserted ) { throw new OxymelException( 'contains has been used before adding any tags' ); } $this->current_element = $this->last_inserted; $this->go_deep_on_next_element--; } } private function move_current_element_up() { if ( $this->go_up_on_next_element ) { while ( $this->go_up_on_next_element ) { $this->current_element = $this->current_element->parentNode; $this->go_up_on_next_element--; } } } private function get_content_and_attributes_from_tag_args( $content_or_attributes, array $attributes ) { $content = null; if ( !$attributes ) { if ( is_array( $content_or_attributes ) ) $attributes = $content_or_attributes; else $content = $content_or_attributes; } else { $content = $content_or_attributes; } return array( $content, $attributes ); } private function init_new_dom() { unset( $this->dom, $this->current_element ); $this->dom = new DOMDocument(); $this->dom->formatOutput = true; $this->current_element = $this->dom; $this->last_inserted = null; } private function xml_from_dom() { if ( 0 !== $this->contains_nesting_level ) { throw new OxymelException( 'contains and end calls do not match' ); } $xml = ''; foreach( $this->dom->childNodes as $child ) { $xml .= $this->dom->saveXML( $child ) . "\n"; } return $xml; } private function create_element( $name, $content, $attributes ) { if ( !is_null( $content ) ) $element = $this->dom->createElement( $name, $content ); else $element = $this->dom->createElement( $name ); foreach( $attributes as $attribute_name => $attribute_value ) { $element->setAttribute( $attribute_name, $attribute_value ); } return $element; } private function add_opening_tag_from_element( $element ) { $this->xml .= $this->indent( $this->xml_from_dom(), $this->nesting_level ); $tag = $this->dom->saveXML($element); $this->xml .= $this->indent( str_replace( '/>', '>', $tag ) . "\n", $this->nesting_level ); $this->nesting_level++; $this->init_new_dom(); } private function add_closing_tag_from_tag_name( $name ) { $this->xml .= $this->xml_from_dom(); $this->nesting_level--; if ( $this->nesting_level < 0 ) { $this->xml = $this->indent( $this->xml, -$this->nesting_level ); $this->nesting_level = 0; } $this->xml .= $this->indent( "</$name>\n", $this->nesting_level ); $this->init_new_dom(); } private function indent( $string, $level ) { if ( !$level ) { return $string; } $lines = explode( "\n", $string ); foreach( $lines as &$line ) { if ( !trim( $line ) ) continue; $line = str_repeat( $this->indentation, $level ) . $line; } return implode( "\n", $lines ); } } class OxymelException extends Exception { } <?php namespace Psr\Log; /** * This Logger can be used to avoid conditional log calls. * * Logging should always be optional, and if no logger is provided to your * library creating a NullLogger instance to have something to throw logs at * is a good way to avoid littering your code with `if ($this->logger) { }` * blocks. */ class NullLogger extends AbstractLogger { /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * * @return void */ public function log($level, $message, array $context = array()) { // noop } } <?php namespace Psr\Log; /** * Basic Implementation of LoggerAwareInterface. */ trait LoggerAwareTrait { /** * The logger instance. * * @var LoggerInterface */ protected $logger; /** * Sets a logger. * * @param LoggerInterface $logger */ public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } } <?php namespace Psr\Log; /** * Describes a logger instance. * * The message MUST be a string or object implementing __toString(). * * The message MAY contain placeholders in the form: {foo} where foo * will be replaced by the context data in key "foo". * * The context array can contain arbitrary data. The only assumption that * can be made by implementors is that if an Exception instance is given * to produce a stack trace, it MUST be in a key named "exception". * * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md * for the full interface specification. */ interface LoggerInterface { /** * System is unusable. * * @param string $message * @param array $context * * @return void */ public function emergency($message, array $context = array()); /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context * * @return void */ public function alert($message, array $context = array()); /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context * * @return void */ public function critical($message, array $context = array()); /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context * * @return void */ public function error($message, array $context = array()); /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context * * @return void */ public function warning($message, array $context = array()); /** * Normal but significant events. * * @param string $message * @param array $context * * @return void */ public function notice($message, array $context = array()); /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context * * @return void */ public function info($message, array $context = array()); /** * Detailed debug information. * * @param string $message * @param array $context * * @return void */ public function debug($message, array $context = array()); /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * * @return void */ public function log($level, $message, array $context = array()); } <?php namespace Psr\Log; /** * Describes a logger-aware instance. */ interface LoggerAwareInterface { /** * Sets a logger instance on the object. * * @param LoggerInterface $logger * * @return void */ public function setLogger(LoggerInterface $logger); } <?php namespace Psr\Log; class InvalidArgumentException extends \InvalidArgumentException { } <?php namespace Psr\Log; /** * This is a simple Logger implementation that other Loggers can inherit from. * * It simply delegates all log-level-specific methods to the `log` method to * reduce boilerplate code that a simple Logger that does the same thing with * messages regardless of the error level has to implement. */ abstract class AbstractLogger implements LoggerInterface { /** * System is unusable. * * @param string $message * @param array $context * * @return void */ public function emergency($message, array $context = array()) { $this->log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context * * @return void */ public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context * * @return void */ public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context * * @return void */ public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context * * @return void */ public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param array $context * * @return void */ public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context * * @return void */ public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param array $context * * @return void */ public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } } <?php namespace Psr\Log; /** * Describes log levels. */ class LogLevel { const EMERGENCY = 'emergency'; const ALERT = 'alert'; const CRITICAL = 'critical'; const ERROR = 'error'; const WARNING = 'warning'; const NOTICE = 'notice'; const INFO = 'info'; const DEBUG = 'debug'; } <?php namespace Psr\Log; /** * This is a simple Logger trait that classes unable to extend AbstractLogger * (because they extend another class, etc) can include. * * It simply delegates all log-level-specific methods to the `log` method to * reduce boilerplate code that a simple Logger that does the same thing with * messages regardless of the error level has to implement. */ trait LoggerTrait { /** * System is unusable. * * @param string $message * @param array $context * * @return void */ public function emergency($message, array $context = array()) { $this->log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context * * @return void */ public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context * * @return void */ public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context * * @return void */ public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context * * @return void */ public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param array $context * * @return void */ public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context * * @return void */ public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param array $context * * @return void */ public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * * @return void */ abstract public function log($level, $message, array $context = array()); } <?php /* * This file is part of the JSON Lint package. * * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Seld\JsonLint; class ParsingException extends \Exception { protected $details; public function __construct($message, $details = array()) { $this->details = $details; parent::__construct($message); } public function getDetails() { return $this->details; } } <?php /* * This file is part of the JSON Lint package. * * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Seld\JsonLint; /** * Lexer class * * Ported from https://github.com/zaach/jsonlint */ class Lexer { private $EOF = 1; private $rules = array( 0 => '/^\s+/', 1 => '/^-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/', 2 => '{^"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x1f\\\\"]++)*+"}', 3 => '/^\{/', 4 => '/^\}/', 5 => '/^\[/', 6 => '/^\]/', 7 => '/^,/', 8 => '/^:/', 9 => '/^true\b/', 10 => '/^false\b/', 11 => '/^null\b/', 12 => '/^$/', 13 => '/^./', ); private $conditions = array( "INITIAL" => array( "rules" => array(0,1,2,3,4,5,6,7,8,9,10,11,12,13), "inclusive" => true, ), ); private $conditionStack; private $input; private $more; private $done; private $matched; public $match; public $yylineno; public $yyleng; public $yytext; public $yylloc; public function lex() { $r = $this->next(); if (!$r instanceof Undefined) { return $r; } return $this->lex(); } public function setInput($input) { $this->input = $input; $this->more = false; $this->done = false; $this->yylineno = $this->yyleng = 0; $this->yytext = $this->matched = $this->match = ''; $this->conditionStack = array('INITIAL'); $this->yylloc = array('first_line' => 1, 'first_column' => 0, 'last_line' => 1, 'last_column' => 0); return $this; } public function showPosition() { $pre = str_replace("\n", '', $this->getPastInput()); $c = str_repeat('-', max(0, strlen($pre) - 1)); // new Array(pre.length + 1).join("-"); return $pre . str_replace("\n", '', $this->getUpcomingInput()) . "\n" . $c . "^"; } public function getPastInput() { $past = substr($this->matched, 0, strlen($this->matched) - strlen($this->match)); return (strlen($past) > 20 ? '...' : '') . substr($past, -20); } public function getUpcomingInput() { $next = $this->match; if (strlen($next) < 20) { $next .= substr($this->input, 0, 20 - strlen($next)); } return substr($next, 0, 20) . (strlen($next) > 20 ? '...' : ''); } protected function parseError($str, $hash) { throw new \Exception($str); } private function next() { if ($this->done) { return $this->EOF; } if ($this->input === '') { $this->done = true; } $token = null; $match = null; $col = null; $lines = null; if (!$this->more) { $this->yytext = ''; $this->match = ''; } $rules = $this->getCurrentRules(); $rulesLen = count($rules); for ($i=0; $i < $rulesLen; $i++) { if (preg_match($this->rules[$rules[$i]], $this->input, $match)) { preg_match_all('/\n.*/', $match[0], $lines); $lines = $lines[0]; if ($lines) { $this->yylineno += count($lines); } $this->yylloc = array( 'first_line' => $this->yylloc['last_line'], 'last_line' => $this->yylineno+1, 'first_column' => $this->yylloc['last_column'], 'last_column' => $lines ? strlen($lines[count($lines) - 1]) - 1 : $this->yylloc['last_column'] + strlen($match[0]), ); $this->yytext .= $match[0]; $this->match .= $match[0]; $this->yyleng = strlen($this->yytext); $this->more = false; $this->input = substr($this->input, strlen($match[0])); $this->matched .= $match[0]; $token = $this->performAction($rules[$i], $this->conditionStack[count($this->conditionStack)-1]); if ($token) { return $token; } return new Undefined(); } } if ($this->input === "") { return $this->EOF; } $this->parseError( 'Lexical error on line ' . ($this->yylineno+1) . ". Unrecognized text.\n" . $this->showPosition(), array( 'text' => "", 'token' => null, 'line' => $this->yylineno, ) ); } private function getCurrentRules() { return $this->conditions[$this->conditionStack[count($this->conditionStack)-1]]['rules']; } private function performAction($avoiding_name_collisions, $YY_START) { switch ($avoiding_name_collisions) { case 0:/* skip whitespace */ break; case 1: return 6; break; case 2: $this->yytext = substr($this->yytext, 1, $this->yyleng-2); return 4; case 3: return 17; case 4: return 18; case 5: return 23; case 6: return 24; case 7: return 22; case 8: return 21; case 9: return 10; case 10: return 11; case 11: return 8; case 12: return 14; case 13: return 'INVALID'; } } } <?php /* * This file is part of the JSON Lint package. * * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Seld\JsonLint; class Undefined { } <?php /* * This file is part of the JSON Lint package. * * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Seld\JsonLint; use stdClass; /** * Parser class * * Example: * * $parser = new JsonParser(); * // returns null if it's valid json, or an error object * $parser->lint($json); * // returns parsed json, like json_decode does, but slower, throws exceptions on failure. * $parser->parse($json); * * Ported from https://github.com/zaach/jsonlint */ class JsonParser { const DETECT_KEY_CONFLICTS = 1; const ALLOW_DUPLICATE_KEYS = 2; const PARSE_TO_ASSOC = 4; private $lexer; private $flags; private $stack; private $vstack; // semantic value stack private $lstack; // location stack private $symbols = array( 'error' => 2, 'JSONString' => 3, 'STRING' => 4, 'JSONNumber' => 5, 'NUMBER' => 6, 'JSONNullLiteral' => 7, 'NULL' => 8, 'JSONBooleanLiteral' => 9, 'TRUE' => 10, 'FALSE' => 11, 'JSONText' => 12, 'JSONValue' => 13, 'EOF' => 14, 'JSONObject' => 15, 'JSONArray' => 16, '{' => 17, '}' => 18, 'JSONMemberList' => 19, 'JSONMember' => 20, ':' => 21, ',' => 22, '[' => 23, ']' => 24, 'JSONElementList' => 25, '$accept' => 0, '$end' => 1, ); private $terminals_ = array( 2 => "error", 4 => "STRING", 6 => "NUMBER", 8 => "NULL", 10 => "TRUE", 11 => "FALSE", 14 => "EOF", 17 => "{", 18 => "}", 21 => ":", 22 => ",", 23 => "[", 24 => "]", ); private $productions_ = array( 0, array(3, 1), array(5, 1), array(7, 1), array(9, 1), array(9, 1), array(12, 2), array(13, 1), array(13, 1), array(13, 1), array(13, 1), array(13, 1), array(13, 1), array(15, 2), array(15, 3), array(20, 3), array(19, 1), array(19, 3), array(16, 2), array(16, 3), array(25, 1), array(25, 3) ); private $table = array(array(3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 1 => array(3)), array( 14 => array(1,16)), array( 14 => array(2,7), 18 => array(2,7), 22 => array(2,7), 24 => array(2,7)), array( 14 => array(2,8), 18 => array(2,8), 22 => array(2,8), 24 => array(2,8)), array( 14 => array(2,9), 18 => array(2,9), 22 => array(2,9), 24 => array(2,9)), array( 14 => array(2,10), 18 => array(2,10), 22 => array(2,10), 24 => array(2,10)), array( 14 => array(2,11), 18 => array(2,11), 22 => array(2,11), 24 => array(2,11)), array( 14 => array(2,12), 18 => array(2,12), 22 => array(2,12), 24 => array(2,12)), array( 14 => array(2,3), 18 => array(2,3), 22 => array(2,3), 24 => array(2,3)), array( 14 => array(2,4), 18 => array(2,4), 22 => array(2,4), 24 => array(2,4)), array( 14 => array(2,5), 18 => array(2,5), 22 => array(2,5), 24 => array(2,5)), array( 14 => array(2,1), 18 => array(2,1), 21 => array(2,1), 22 => array(2,1), 24 => array(2,1)), array( 14 => array(2,2), 18 => array(2,2), 22 => array(2,2), 24 => array(2,2)), array( 3 => 20, 4 => array(1,12), 18 => array(1,17), 19 => 18, 20 => 19 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ), array( 1 => array(2,6)), array( 14 => array(2,13), 18 => array(2,13), 22 => array(2,13), 24 => array(2,13)), array( 18 => array(1,24), 22 => array(1,25)), array( 18 => array(2,16), 22 => array(2,16)), array( 21 => array(1,26)), array( 14 => array(2,18), 18 => array(2,18), 22 => array(2,18), 24 => array(2,18)), array( 22 => array(1,28), 24 => array(1,27)), array( 22 => array(2,20), 24 => array(2,20)), array( 14 => array(2,14), 18 => array(2,14), 22 => array(2,14), 24 => array(2,14)), array( 3 => 20, 4 => array(1,12), 20 => 29 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 18 => array(2,17), 22 => array(2,17)), array( 18 => array(2,15), 22 => array(2,15)), array( 22 => array(2,21), 24 => array(2,21)), ); private $defaultActions = array( 16 => array(2, 6) ); /** * @param string $input JSON string * @param int $flags Bitmask of parse/lint options (see constants of this class) * @return null|ParsingException null if no error is found, a ParsingException containing all details otherwise */ public function lint($input, $flags = 0) { try { $this->parse($input, $flags); } catch (ParsingException $e) { return $e; } } /** * @param string $input JSON string * @param int $flags Bitmask of parse/lint options (see constants of this class) * @return mixed * @throws ParsingException */ public function parse($input, $flags = 0) { $this->failOnBOM($input); $this->flags = $flags; $this->stack = array(0); $this->vstack = array(null); $this->lstack = array(); $yytext = ''; $yylineno = 0; $yyleng = 0; $recovering = 0; $TERROR = 2; $EOF = 1; $this->lexer = new Lexer(); $this->lexer->setInput($input); $yyloc = $this->lexer->yylloc; $this->lstack[] = $yyloc; $symbol = null; $preErrorSymbol = null; $state = null; $action = null; $a = null; $r = null; $yyval = new stdClass; $p = null; $len = null; $newState = null; $expected = null; $errStr = null; while (true) { // retreive state number from top of stack $state = $this->stack[count($this->stack)-1]; // use default actions if available if (isset($this->defaultActions[$state])) { $action = $this->defaultActions[$state]; } else { if ($symbol == null) { $symbol = $this->lex(); } // read action for current state and first input $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false; } // handle parse error if (!$action || !$action[0]) { if (!$recovering) { // Report error $expected = array(); foreach ($this->table[$state] as $p => $ignore) { if (isset($this->terminals_[$p]) && $p > 2) { $expected[] = "'" . $this->terminals_[$p] . "'"; } } $message = null; if (in_array("'STRING'", $expected) && in_array(substr($this->lexer->match, 0, 1), array('"', "'"))) { $message = "Invalid string"; if ("'" === substr($this->lexer->match, 0, 1)) { $message .= ", it appears you used single quotes instead of double quotes"; } elseif (preg_match('{".+?(\\\\[^"bfnrt/\\\\u])}', $this->lexer->getUpcomingInput(), $match)) { $message .= ", it appears you have an unescaped backslash at: ".$match[1]; } elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getUpcomingInput())) { $message .= ", it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid"; } } $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; if ($message) { $errStr .= $message; } else { $errStr .= (count($expected) > 1) ? "Expected one of: " : "Expected: "; $errStr .= implode(', ', $expected); } if (',' === substr(trim($this->lexer->getPastInput()), -1)) { $errStr .= " - It appears you have an extra trailing comma"; } $this->parseError($errStr, array( 'text' => $this->lexer->match, 'token' => !empty($this->terminals_[$symbol]) ? $this->terminals_[$symbol] : $symbol, 'line' => $this->lexer->yylineno, 'loc' => $yyloc, 'expected' => $expected, )); } // just recovered from another error if ($recovering == 3) { if ($symbol == $EOF) { throw new ParsingException($errStr ?: 'Parsing halted.'); } // discard current lookahead and grab another $yyleng = $this->lexer->yyleng; $yytext = $this->lexer->yytext; $yylineno = $this->lexer->yylineno; $yyloc = $this->lexer->yylloc; $symbol = $this->lex(); } // try to recover from error while (true) { // check for error recovery rule in this state if (array_key_exists($TERROR, $this->table[$state])) { break; } if ($state == 0) { throw new ParsingException($errStr ?: 'Parsing halted.'); } $this->popStack(1); $state = $this->stack[count($this->stack)-1]; } $preErrorSymbol = $symbol; // save the lookahead token $symbol = $TERROR; // insert generic error symbol as new lookahead $state = $this->stack[count($this->stack)-1]; $action = isset($this->table[$state][$TERROR]) ? $this->table[$state][$TERROR] : false; $recovering = 3; // allow 3 real symbols to be shifted before reporting a new error } // this shouldn't happen, unless resolve defaults are off if (is_array($action[0]) && count($action) > 1) { throw new ParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol); } switch ($action[0]) { case 1: // shift $this->stack[] = $symbol; $this->vstack[] = $this->lexer->yytext; $this->lstack[] = $this->lexer->yylloc; $this->stack[] = $action[1]; // push state $symbol = null; if (!$preErrorSymbol) { // normal execution/no error $yyleng = $this->lexer->yyleng; $yytext = $this->lexer->yytext; $yylineno = $this->lexer->yylineno; $yyloc = $this->lexer->yylloc; if ($recovering > 0) { $recovering--; } } else { // error just occurred, resume old lookahead f/ before error $symbol = $preErrorSymbol; $preErrorSymbol = null; } break; case 2: // reduce $len = $this->productions_[$action[1]][1]; // perform semantic action $yyval->token = $this->vstack[count($this->vstack) - $len]; // default to $$ = $1 // default location, uses first token for firsts, last for lasts $yyval->store = array( // _$ = store 'first_line' => $this->lstack[count($this->lstack) - ($len ?: 1)]['first_line'], 'last_line' => $this->lstack[count($this->lstack) - 1]['last_line'], 'first_column' => $this->lstack[count($this->lstack) - ($len ?: 1)]['first_column'], 'last_column' => $this->lstack[count($this->lstack) - 1]['last_column'], ); $r = $this->performAction($yyval, $yytext, $yyleng, $yylineno, $action[1], $this->vstack, $this->lstack); if (!$r instanceof Undefined) { return $r; } if ($len) { $this->popStack($len); } $this->stack[] = $this->productions_[$action[1]][0]; // push nonterminal (reduce) $this->vstack[] = $yyval->token; $this->lstack[] = $yyval->store; $newState = $this->table[$this->stack[count($this->stack)-2]][$this->stack[count($this->stack)-1]]; $this->stack[] = $newState; break; case 3: // accept return true; } } return true; } protected function parseError($str, $hash) { throw new ParsingException($str, $hash); } // $$ = $tokens // needs to be passed by ref? // $ = $token // _$ removed, useless? private function performAction(stdClass $yyval, $yytext, $yyleng, $yylineno, $yystate, &$tokens) { // $0 = $len $len = count($tokens) - 1; switch ($yystate) { case 1: $yytext = preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this, 'stringInterpolation'), $yytext); $yyval->token = $yytext; break; case 2: if (strpos($yytext, 'e') !== false || strpos($yytext, 'E') !== false) { $yyval->token = floatval($yytext); } else { $yyval->token = strpos($yytext, '.') === false ? intval($yytext) : floatval($yytext); } break; case 3: $yyval->token = null; break; case 4: $yyval->token = true; break; case 5: $yyval->token = false; break; case 6: return $yyval->token = $tokens[$len-1]; case 13: if ($this->flags & self::PARSE_TO_ASSOC) { $yyval->token = array(); } else { $yyval->token = new stdClass; } break; case 14: $yyval->token = $tokens[$len-1]; break; case 15: $yyval->token = array($tokens[$len-2], $tokens[$len]); break; case 16: if (PHP_VERSION_ID < 70100) { $property = $tokens[$len][0] === '' ? '_empty_' : $tokens[$len][0]; } else { $property = $tokens[$len][0]; } if ($this->flags & self::PARSE_TO_ASSOC) { $yyval->token = array(); $yyval->token[$property] = $tokens[$len][1]; } else { $yyval->token = new stdClass; $yyval->token->$property = $tokens[$len][1]; } break; case 17: if ($this->flags & self::PARSE_TO_ASSOC) { $yyval->token =& $tokens[$len-2]; $key = $tokens[$len][0]; if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($tokens[$len-2][$key])) { $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; $errStr .= "Duplicate key: ".$tokens[$len][0]; throw new DuplicateKeyException($errStr, $tokens[$len][0], array('line' => $yylineno+1)); } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($tokens[$len-2][$key])) { $duplicateCount = 1; do { $duplicateKey = $key . '.' . $duplicateCount++; } while (isset($tokens[$len-2][$duplicateKey])); $key = $duplicateKey; } $tokens[$len-2][$key] = $tokens[$len][1]; } else { $yyval->token = $tokens[$len-2]; if (PHP_VERSION_ID < 70100) { $key = $tokens[$len][0] === '' ? '_empty_' : $tokens[$len][0]; } else { $key = $tokens[$len][0]; } if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($tokens[$len-2]->{$key})) { $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; $errStr .= "Duplicate key: ".$tokens[$len][0]; throw new DuplicateKeyException($errStr, $tokens[$len][0], array('line' => $yylineno+1)); } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($tokens[$len-2]->{$key})) { $duplicateCount = 1; do { $duplicateKey = $key . '.' . $duplicateCount++; } while (isset($tokens[$len-2]->$duplicateKey)); $key = $duplicateKey; } $tokens[$len-2]->$key = $tokens[$len][1]; } break; case 18: $yyval->token = array(); break; case 19: $yyval->token = $tokens[$len-1]; break; case 20: $yyval->token = array($tokens[$len]); break; case 21: $tokens[$len-2][] = $tokens[$len]; $yyval->token = $tokens[$len-2]; break; } return new Undefined(); } private function stringInterpolation($match) { switch ($match[0]) { case '\\\\': return '\\'; case '\"': return '"'; case '\b': return chr(8); case '\f': return chr(12); case '\n': return "\n"; case '\r': return "\r"; case '\t': return "\t"; case '\/': return "/"; default: return html_entity_decode('&#x'.ltrim(substr($match[0], 2), '0').';', 0, 'UTF-8'); } } private function popStack($n) { $this->stack = array_slice($this->stack, 0, - (2 * $n)); $this->vstack = array_slice($this->vstack, 0, - $n); $this->lstack = array_slice($this->lstack, 0, - $n); } private function lex() { $token = $this->lexer->lex() ?: 1; // $end = 1 // if token isn't its numeric value, convert if (!is_numeric($token)) { $token = isset($this->symbols[$token]) ? $this->symbols[$token] : $token; } return $token; } private function failOnBOM($input) { // UTF-8 ByteOrderMark sequence $bom = "\xEF\xBB\xBF"; if (substr($input, 0, 3) === $bom) { $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark", array()); } } } <?php /* * This file is part of the JSON Lint package. * * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Seld\JsonLint; class DuplicateKeyException extends ParsingException { public function __construct($message, $key, array $details = array()) { $details['key'] = $key; parent::__construct($message, $details); } public function getKey() { return $this->details['key']; } } <?php /* * This file is part of PHAR Utils. * * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Seld\PharUtils; class Timestamps { private $contents; /** * @param string $file path to the phar file to use */ public function __construct($file) { $this->contents = file_get_contents($file); } /** * Updates each file's unix timestamps in the PHAR * * The PHAR signature can then be produced in a reproducible manner. * * @param int|DateTime|string $timestamp Date string or DateTime or unix timestamp to use */ public function updateTimestamps($timestamp = null) { if ($timestamp instanceof \DateTime) { $timestamp = $timestamp->getTimestamp(); } elseif (is_string($timestamp)) { $timestamp = strtotime($timestamp); } elseif (!is_int($timestamp)) { $timestamp = strtotime('1984-12-24T00:00:00Z'); } // detect manifest offset / end of stub if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) { throw new \RuntimeException('Could not detect the stub\'s end in the phar'); } // set starting position and skip past manifest length $pos = $match[0][1] + strlen($match[0][0]); $stubEnd = $pos + $this->readUint($pos, 4); $pos += 4; $numFiles = $this->readUint($pos, 4); $pos += 4; // skip API version (YOLO) $pos += 2; // skip PHAR flags $pos += 4; $aliasLength = $this->readUint($pos, 4); $pos += 4 + $aliasLength; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; while ($pos < $stubEnd) { $filenameLength = $this->readUint($pos, 4); $pos += 4 + $filenameLength; // skip filesize $pos += 4; // update timestamp to a fixed value $this->contents = substr_replace($this->contents, pack('L', $timestamp), $pos, 4); // skip timestamp, compressed file size, crc32 checksum and file flags $pos += 4*4; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $numFiles--; } if ($numFiles !== 0) { throw new \LogicException('All files were not processed, something must have gone wrong'); } } /** * Saves the updated phar file, optionally with an updated signature. * * @param string $path * @param int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512 * @return bool */ public function save($path, $signatureAlgo) { $pos = $this->determineSignatureBegin(); $algos = array( \Phar::MD5 => 'md5', \Phar::SHA1 => 'sha1', \Phar::SHA256 => 'sha256', \Phar::SHA512 => 'sha512', ); if (!isset($algos[$signatureAlgo])) { throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512'); } $algo = $algos[$signatureAlgo]; // re-sign phar // signature $signature = hash($algo, substr($this->contents, 0, $pos), true) // sig type . pack('L', $signatureAlgo) // ohai Greg & Marcus . 'GBMB'; $this->contents = substr($this->contents, 0, $pos) . $signature; return file_put_contents($path, $this->contents); } private function readUint($pos, $bytes) { $res = unpack("L", substr($this->contents, $pos, $bytes)); return $res[1]; } /** * Determine the beginning of the signature. * * @return int */ private function determineSignatureBegin() { // detect signature position if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) { throw new \RuntimeException('Could not detect the stub\'s end in the phar'); } // set starting position and skip past manifest length $pos = $match[0][1] + strlen($match[0][0]); $stubEnd = $pos + $this->readUint($pos, 4); $pos += 4; $numFiles = $this->readUint($pos, 4); $pos += 4; // skip API version (YOLO) $pos += 2; // skip PHAR flags $pos += 4; $aliasLength = $this->readUint($pos, 4); $pos += 4 + $aliasLength; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $compressedSizes = 0; while ($pos < $stubEnd) { $filenameLength = $this->readUint($pos, 4); $pos += 4 + $filenameLength; // skip filesize and timestamp $pos += 2*4; $compressedSizes += $this->readUint($pos, 4); // skip compressed file size, crc32 checksum and file flags $pos += 3*4; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $numFiles--; } if ($numFiles !== 0) { throw new \LogicException('All files were not processed, something must have gone wrong'); } return $pos + $compressedSizes; } } <?php require __DIR__.'/../vendor/autoload.php'; echo 'Say hello (visible): '; $answer = Seld\CliPrompt\CliPrompt::prompt(); echo 'You answered: '.$answer . PHP_EOL; echo 'Say hello (hidden): '; $answer = Seld\CliPrompt\CliPrompt::hiddenPrompt(); echo 'You answered: '.$answer . PHP_EOL; <?php /* * This file is part of CLI Prompt. * * (c) Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Seld\CliPrompt; class CliPrompt { /** * Prompts the user for input and shows what they type * * @return string */ public static function prompt() { $stdin = fopen('php://stdin', 'r'); $answer = self::trimAnswer(fgets($stdin, 4096)); fclose($stdin); return $answer; } /** * Prompts the user for input and hides what they type * * @param bool $allowFallback If prompting fails for any reason and this is set to true the prompt * will be done using the regular prompt() function, otherwise a * \RuntimeException is thrown. * @return string * @throws RuntimeException on failure to prompt, unless $allowFallback is true */ public static function hiddenPrompt($allowFallback = false) { // handle windows if (defined('PHP_WINDOWS_VERSION_BUILD')) { // fallback to hiddeninput executable $exe = __DIR__.'\\..\\res\\hiddeninput.exe'; // handle code running from a phar if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; // use stream_copy_to_stream instead of copy // to work around https://bugs.php.net/bug.php?id=64634 $source = fopen($exe, 'r'); $target = fopen($tmpExe, 'w+'); stream_copy_to_stream($source, $target); fclose($source); fclose($target); unset($source, $target); $exe = $tmpExe; } $output = shell_exec($exe); // clean up if (isset($tmpExe)) { unlink($tmpExe); } if ($output !== null) { // output a newline to be on par with the regular prompt() echo PHP_EOL; return self::trimAnswer($output); } } if (file_exists('/usr/bin/env')) { // handle other OSs with bash/zsh/ksh/csh if available to hide the answer $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; foreach (array('bash', 'zsh', 'ksh', 'csh', 'sh') as $sh) { if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { $shell = $sh; break; } } if (isset($shell)) { $readCmd = ($shell === 'csh') ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); $output = shell_exec($command); if ($output !== null) { // output a newline to be on par with the regular prompt() echo PHP_EOL; return self::trimAnswer($output); } } } // not able to hide the answer if (!$allowFallback) { throw new \RuntimeException('Could not prompt for input in a secure fashion, aborting'); } return self::prompt(); } private static function trimAnswer($str) { return preg_replace('{\r?\n$}D', '', $str); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * HelpCommand displays the help for a given command. * * @author Fabien Potencier <fabien@symfony.com> */ class HelpCommand extends Command { private $command; /** * {@inheritdoc} */ protected function configure() { $this->ignoreValidationErrors(); $this ->setName('help') ->setDefinition(array( new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), 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 <info>%command.name%</info> command displays help for a given command: <info>php %command.full_name% list</info> You can also output the help in other formats by using the <comment>--format</comment> option: <info>php %command.full_name% --format=xml list</info> To display the list of available commands, please use the <info>list</info> command. EOF ) ; } /** * Sets the command. * * @param Command $command The command to set */ public function setCommand(Command $command) { $this->command = $command; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (null === $this->command) { $this->command = $this->getApplication()->find($input->getArgument('command_name')); } if ($input->getOption('xml')) { @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); $input->setOption('format', 'xml'); } $helper = new DescriptorHelper(); $helper->describe($output, $this->command, array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), )); $this->command = null; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Base class for all commands. * * @author Fabien Potencier <fabien@symfony.com> */ class Command { private $application; private $name; private $processTitle; private $aliases = array(); private $definition; private $help; private $description; private $ignoreValidationErrors = false; private $applicationDefinitionMerged = false; private $applicationDefinitionMergedWithArgs = false; private $code; private $synopsis = array(); private $usages = array(); private $helperSet; /** * @param string|null $name The name of the command; passing null means it must be set in configure() * * @throws LogicException When the command name is empty */ public function __construct($name = null) { $this->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))); } } /** * Ignores validation errors. * * This is mainly useful for the help command. */ public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } /** * Sets the application instance for this command. * * @param Application $application An Application instance */ public function setApplication(Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } } /** * Sets the helper set. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Gets the helper set. * * @return HelperSet A HelperSet instance */ public function getHelperSet() { return $this->helperSet; } /** * Gets the application instance for this command. * * @return Application An Application instance */ public function getApplication() { return $this->application; } /** * Checks whether the command is enabled or not in the current environment. * * Override this to check for x or y and return false if the command can not * run properly under the current conditions. * * @return bool */ public function isEnabled() { return true; } /** * Configures the current command. */ protected function configure() { } /** * Executes the current command. * * This method is not abstract because you can use this class * as a concrete class. In this case, instead of defining the * execute() method, you set the code to execute by passing * a Closure to the setCode() method. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return null|int null or 0 if everything went fine, or an error code * * @throws LogicException When this abstract method is not implemented * * @see setCode() */ protected function execute(InputInterface $input, OutputInterface $output) { throw new LogicException('You must override the execute() method in the concrete command class.'); } /** * Interacts with the user. * * This method is executed before the InputDefinition is validated. * This means that this is the only place where the command can * interactively ask for values of missing required arguments. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function interact(InputInterface $input, OutputInterface $output) { } /** * Initializes the command just after the input has been validated. * * This is mainly useful when a lot of commands extends one main command * where some things need to be initialized based on the input arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function initialize(InputInterface $input, OutputInterface $output) { } /** * Runs the command. * * The code to execute is either defined directly with the * setCode() method or by overriding the execute() method * in a sub-class. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return int The command exit code * * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}. * * @see setCode() * @see execute() */ public function run(InputInterface $input, OutputInterface $output) { // force the creation of the synopsis before the merge with the app definition $this->getSynopsis(true); $this->getSynopsis(false); // add the application arguments and options $this->mergeApplicationDefinition(); // bind the input against the command specific arguments/options 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('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>'); } 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('<comment>Install the proctitle PECL to be able to change the process title.</comment>'); } } if ($input->isInteractive()) { $this->interact($input, $output); } // The command name argument is often omitted when a command is executed directly with its run() method. // It would fail the validation if we didn't make sure the command argument is present, // since it's required by the application. 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; } /** * Sets the code to execute when running this command. * * If this method is used, it overrides the code defined * in the execute() method. * * @param callable $code A callable(InputInterface $input, OutputInterface $output) * * @return $this * * @throws InvalidArgumentException * * @see execute() */ public function setCode($code) { if (!is_callable($code)) { throw new InvalidArgumentException('Invalid callable provided to Command::setCode.'); } if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { $r = new \ReflectionFunction($code); if (null === $r->getClosureThis()) { if (PHP_VERSION_ID < 70000) { // Bug in PHP5: https://bugs.php.net/bug.php?id=64761 // This means that we cannot bind static closures and therefore we must // ignore any errors here. There is no way to test if the closure is // bindable. $code = @\Closure::bind($code, $this); } else { $code = \Closure::bind($code, $this); } } } $this->code = $code; return $this; } /** * Merges the application definition with the command definition. * * This method is not part of public API and should not be used directly. * * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments */ 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; } } /** * Sets an array of argument and option instances. * * @param array|InputDefinition $definition An array of argument and option instances or a definition instance * * @return $this */ public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->applicationDefinitionMerged = false; return $this; } /** * Gets the InputDefinition attached to this Command. * * @return InputDefinition An InputDefinition instance */ public function getDefinition() { return $this->definition; } /** * Gets the InputDefinition to be used to create XML and Text representations of this Command. * * Can be overridden to provide the original command representation when it would otherwise * be changed by merging with the application InputDefinition. * * This method is not part of public API and should not be used directly. * * @return InputDefinition An InputDefinition instance */ public function getNativeDefinition() { return $this->getDefinition(); } /** * Adds an argument. * * @param string $name The argument name * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * * @return $this */ public function addArgument($name, $mode = null, $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } /** * Adds an option. * * @param string $name The option name * @param string $shortcut The shortcut (can be null) * @param int $mode The option mode: One of the InputOption::VALUE_* constants * @param string $description A description text * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) * * @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; } /** * Sets the name of the command. * * This method can set both the namespace and the name if * you separate them by a colon (:) * * $command->setName('foo:bar'); * * @param string $name The command name * * @return $this * * @throws InvalidArgumentException When the name is invalid */ public function setName($name) { $this->validateName($name); $this->name = $name; return $this; } /** * Sets the process title of the command. * * This feature should be used only when creating a long process command, * like a daemon. * * PHP 5.5+ or the proctitle PECL library is required * * @param string $title The process title * * @return $this */ public function setProcessTitle($title) { $this->processTitle = $title; return $this; } /** * Returns the command name. * * @return string The command name */ public function getName() { return $this->name; } /** * Sets the description for the command. * * @param string $description The description for the command * * @return $this */ public function setDescription($description) { $this->description = $description; return $this; } /** * Returns the description for the command. * * @return string The description for the command */ public function getDescription() { return $this->description; } /** * Sets the help for the command. * * @param string $help The help for the command * * @return $this */ public function setHelp($help) { $this->help = $help; return $this; } /** * Returns the help for the command. * * @return string The help for the command */ public function getHelp() { return $this->help; } /** * Returns the processed help for the command replacing the %command.name% and * %command.full_name% patterns with the real values dynamically. * * @return string The processed help for the command */ 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()); } /** * Sets the aliases for the command. * * @param string[] $aliases An array of aliases for the command * * @return $this * * @throws InvalidArgumentException When an alias is invalid */ 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; } /** * Returns the aliases for the command. * * @return array An array of aliases for the command */ public function getAliases() { return $this->aliases; } /** * Returns the synopsis for the command. * * @param bool $short Whether to show the short version of the synopsis (with options folded) or not * * @return string The synopsis */ 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]; } /** * Add a command usage example. * * @param string $usage The usage, it'll be prefixed with the command name * * @return $this */ public function addUsage($usage) { if (0 !== strpos($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } /** * Returns alternative usages of the command. * * @return array */ public function getUsages() { return $this->usages; } /** * Gets a helper instance by name. * * @param string $name The helper name * * @return mixed The helper value * * @throws LogicException if no HelperSet is defined * @throws InvalidArgumentException if the helper is not defined */ 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); } /** * Returns a text representation of the command. * * @return string A string representing the command * * @deprecated since version 2.3, to be removed in 3.0. */ public function asText() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); $descriptor->describe($output, $this, array('raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the command. * * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the command * * @deprecated since version 2.3, to be removed in 3.0. */ public function asXml($asDom = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getCommandDocument($this); } $output = new BufferedOutput(); $descriptor->describe($output, $this); return $output->fetch(); } /** * Validates a command name. * * It must be non-empty and parts can optionally be separated by ":". * * @param string $name * * @throws InvalidArgumentException When the name is invalid */ private function validateName($name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputDefinition; /** * ListCommand displays the list of all available commands for the application. * * @author Fabien Potencier <fabien@symfony.com> */ class ListCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('list') ->setDefinition($this->createDefinition()) ->setDescription('Lists commands') ->setHelp(<<<'EOF' The <info>%command.name%</info> command lists all commands: <info>php %command.full_name%</info> You can also display the commands for a specific namespace: <info>php %command.full_name% test</info> You can also output the information in other formats by using the <comment>--format</comment> option: <info>php %command.full_name% --format=xml</info> It's also possible to get raw list of commands (useful for embedding command runner): <info>php %command.full_name% --raw</info> EOF ) ; } /** * {@inheritdoc} */ public function getNativeDefinition() { return $this->createDefinition(); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('xml')) { @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); $input->setOption('format', 'xml'); } $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), )); } /** * {@inheritdoc} */ private function createDefinition() { return new InputDefinition(array( new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'), 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'), )); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; /** * Contains all events dispatched by an Application. * * @author Francesco Levorato <git@flevour.net> */ final class ConsoleEvents { /** * The COMMAND event allows you to attach listeners before any command is * executed by the console. It also allows you to modify the command, input and output * before they are handled to the command. * * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent * instance. * * @Event * * @var string */ const COMMAND = 'console.command'; /** * The TERMINATE event allows you to attach listeners after a command is * executed by the console. * * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent * instance. * * @Event * * @var string */ const TERMINATE = 'console.terminate'; /** * The EXCEPTION event occurs when an uncaught exception appears. * * This event allows you to deal with the exception or * to modify the thrown exception. The event listener method receives * a Symfony\Component\Console\Event\ConsoleExceptionEvent * instance. * * @Event * * @var string */ const EXCEPTION = 'console.exception'; } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Represents a choice question. * * @author Fabien Potencier <fabien@symfony.com> */ class ChoiceQuestion extends Question { private $choices; private $multiselect = false; private $prompt = ' > '; private $errorMessage = 'Value "%s" is invalid'; /** * @param string $question The question to ask to the user * @param array $choices The list of available choices * @param mixed $default The default answer to return */ public function __construct($question, array $choices, $default = null) { if (!$choices) { throw new \LogicException('Choice question must have at least 1 choice available.'); } parent::__construct($question, $default); $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } /** * Returns available choices. * * @return array */ public function getChoices() { return $this->choices; } /** * Sets multiselect option. * * When multiselect is set to true, multiple choices can be answered. * * @param bool $multiselect * * @return $this */ public function setMultiselect($multiselect) { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } /** * Returns whether the choices are multiselect. * * @return bool */ public function isMultiselect() { return $this->multiselect; } /** * Gets the prompt for choices. * * @return string */ public function getPrompt() { return $this->prompt; } /** * Sets the prompt for choices. * * @param string $prompt * * @return $this */ public function setPrompt($prompt) { $this->prompt = $prompt; return $this; } /** * Sets the error message for invalid values. * * The error message has a string placeholder (%s) for the invalid value. * * @param string $errorMessage * * @return $this */ public function setErrorMessage($errorMessage) { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); return $this; } /** * Returns the default answer validator. * * @return callable */ private function getDefaultValidator() { $choices = $this->choices; $errorMessage = $this->errorMessage; $multiselect = $this->multiselect; $isAssoc = $this->isAssoc($choices); return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { // Collapse all spaces. $selectedChoices = str_replace(' ', '', $selected); if ($multiselect) { // Check for a separated comma values if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $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); }; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Represents a Question. * * @author Fabien Potencier <fabien@symfony.com> */ class Question { private $question; private $attempts; private $hidden = false; private $hiddenFallback = true; private $autocompleterValues; private $validator; private $default; private $normalizer; /** * @param string $question The question to ask to the user * @param mixed $default The default answer to return if the user enters nothing */ public function __construct($question, $default = null) { $this->question = $question; $this->default = $default; } /** * Returns the question. * * @return string */ public function getQuestion() { return $this->question; } /** * Returns the default answer. * * @return mixed */ public function getDefault() { return $this->default; } /** * Returns whether the user response must be hidden. * * @return bool */ public function isHidden() { return $this->hidden; } /** * Sets whether the user response must be hidden or not. * * @param bool $hidden * * @return $this * * @throws LogicException In case the autocompleter is also used */ public function setHidden($hidden) { if ($this->autocompleterValues) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = (bool) $hidden; return $this; } /** * In case the response can not be hidden, whether to fallback on non-hidden question or not. * * @return bool */ public function isHiddenFallback() { return $this->hiddenFallback; } /** * Sets whether to fallback on non-hidden question if the response can not be hidden. * * @param bool $fallback * * @return $this */ public function setHiddenFallback($fallback) { $this->hiddenFallback = (bool) $fallback; return $this; } /** * Gets values for the autocompleter. * * @return null|array|\Traversable */ public function getAutocompleterValues() { return $this->autocompleterValues; } /** * Sets values for the autocompleter. * * @param null|array|\Traversable $values * * @return $this * * @throws InvalidArgumentException * @throws LogicException */ 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; } /** * Sets a validator for the question. * * @param null|callable $validator * * @return $this */ public function setValidator($validator) { $this->validator = $validator; return $this; } /** * Gets the validator for the question. * * @return null|callable */ public function getValidator() { return $this->validator; } /** * Sets the maximum number of attempts. * * Null means an unlimited number of attempts. * * @param null|int $attempts * * @return $this * * @throws InvalidArgumentException in case the number of attempts is invalid */ 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; } /** * Gets the maximum number of attempts. * * Null means an unlimited number of attempts. * * @return null|int */ public function getMaxAttempts() { return $this->attempts; } /** * Sets a normalizer for the response. * * The normalizer can be a callable (a string), a closure or a class implementing __invoke. * * @param callable $normalizer * * @return $this */ public function setNormalizer($normalizer) { $this->normalizer = $normalizer; return $this; } /** * Gets the normalizer for the response. * * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. * * @return callable */ public function getNormalizer() { return $this->normalizer; } protected function isAssoc($array) { return (bool) count(array_filter(array_keys($array), 'is_string')); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; /** * Represents a yes/no question. * * @author Fabien Potencier <fabien@symfony.com> */ class ConfirmationQuestion extends Question { private $trueAnswerRegex; /** * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param string $trueAnswerRegex A regex to match the "yes" answer */ public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') { parent::__construct($question, (bool) $default); $this->trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } /** * Returns the default answer normalizer. * * @return callable */ 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; }; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * StringInput represents an input provided as a string. * * Usage: * * $input = new StringInput('foo --bar="foobar"'); * * @author Fabien Potencier <fabien@symfony.com> */ class StringInput extends ArgvInput { const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)'; const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; /** * @param string $input An array of parameters from the CLI (in the argv format) * @param InputDefinition $definition A InputDefinition instance * * @deprecated The second argument is deprecated as it does not work (will be removed in 3.0), use 'bind' method instead */ public function __construct($input, InputDefinition $definition = null) { if ($definition) { @trigger_error('The $definition argument of the '.__METHOD__.' method is deprecated and will be removed in 3.0. Set this parameter with the bind() method instead.', E_USER_DEPRECATED); } parent::__construct(array(), null); $this->setTokens($this->tokenize($input)); if (null !== $definition) { $this->bind($definition); } } /** * Tokenizes a string. * * @param string $input The input to tokenize * * @return array An array of tokens * * @throws InvalidArgumentException When unable to parse input (should never happen) */ 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 { // should never happen throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); } $cursor += strlen($match[0]); } return $tokens; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Represents a command line argument. * * @author Fabien Potencier <fabien@symfony.com> */ class InputArgument { const REQUIRED = 1; const OPTIONAL = 2; const IS_ARRAY = 4; private $name; private $mode; private $default; private $description; /** * @param string $name The argument name * @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for self::OPTIONAL mode only) * * @throws InvalidArgumentException When argument mode is not valid */ public function __construct($name, $mode = null, $description = '', $default = null) { if (null === $mode) { $mode = self::OPTIONAL; } elseif (!is_int($mode) || $mode > 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); } /** * Returns the argument name. * * @return string The argument name */ public function getName() { return $this->name; } /** * Returns true if the argument is required. * * @return bool true if parameter mode is self::REQUIRED, false otherwise */ public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } /** * Returns true if the argument can take multiple values. * * @return bool true if mode is self::IS_ARRAY, false otherwise */ public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value * * @throws LogicException When incorrect default value is given */ 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; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\RuntimeException; /** * ArgvInput represents an input coming from the CLI arguments. * * Usage: * * $input = new ArgvInput(); * * By default, the `$_SERVER['argv']` array is used for the input values. * * This can be overridden by explicitly passing the input values in the constructor: * * $input = new ArgvInput($_SERVER['argv']); * * If you pass it yourself, don't forget that the first element of the array * is the name of the running application. * * When passing an argument to the constructor, be sure that it respects * the same rules as the argv one. It's almost always better to use the * `StringInput` when you want to provide your own input. * * @author Fabien Potencier <fabien@symfony.com> * * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 */ class ArgvInput extends Input { private $tokens; private $parsed; /** * @param array|null $argv An array of parameters from the CLI (in the argv format) * @param InputDefinition|null $definition A InputDefinition instance */ public function __construct(array $argv = null, InputDefinition $definition = null) { if (null === $argv) { $argv = $_SERVER['argv']; } // strip the application name array_shift($argv); $this->tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } /** * {@inheritdoc} */ 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); } } } /** * Parses a short option. * * @param string $token The current 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()) { // an option with a value (with no space) $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } /** * Parses a short option set. * * @param string $name The current token * * @throws RuntimeException When option given doesn't exist */ 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); } } } /** * Parses a long option. * * @param string $token The current token */ 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); } } /** * Parses an argument. * * @param string $token The current token * * @throws RuntimeException When too many arguments are given */ private function parseArgument($token) { $c = count($this->arguments); // if input is expecting another argument, add it if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token; // if last argument isArray(), append token to last argument } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; // unexpected argument } 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)); } } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws RuntimeException When option given doesn't exist */ 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); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws RuntimeException When option given doesn't exist */ 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); // Convert empty values to null 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)) { // if option accepts an optional or mandatory argument // let's see if there is one provided $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; } } /** * {@inheritdoc} */ public function getFirstArgument() { foreach ($this->tokens as $token) { if ($token && '-' === $token[0]) { continue; } return $token; } } /** * {@inheritdoc} */ public function hasParameterOption($values) { $values = (array) $values; foreach ($this->tokens as $token) { foreach ($values as $value) { if ($token === $value || 0 === strpos($token, $value.'=')) { return true; } } } return false; } /** * {@inheritdoc} */ public function getParameterOption($values, $default = false) { $values = (array) $values; $tokens = $this->tokens; while (0 < count($tokens)) { $token = array_shift($tokens); 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; } /** * Returns a stringified representation of the args passed to the command. * * @return string */ public function __toString() { $self = $this; $tokens = array_map(function ($token) use ($self) { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1].$self->escapeToken($match[2]); } if ($token && '-' !== $token[0]) { return $self->escapeToken($token); } return $token; }, $this->tokens); return implode(' ', $tokens); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * A InputDefinition represents a set of valid command line arguments and options. * * Usage: * * $definition = new InputDefinition(array( * new InputArgument('name', InputArgument::REQUIRED), * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), * )); * * @author Fabien Potencier <fabien@symfony.com> */ class InputDefinition { private $arguments; private $requiredCount; private $hasAnArrayArgument = false; private $hasOptional; private $options; private $shortcuts; /** * @param array $definition An array of InputArgument and InputOption instance */ public function __construct(array $definition = array()) { $this->setDefinition($definition); } /** * Sets the definition of the input. * * @param array $definition The definition array */ 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); } /** * Sets the InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects */ public function setArguments($arguments = array()) { $this->arguments = array(); $this->requiredCount = 0; $this->hasOptional = false; $this->hasAnArrayArgument = false; $this->addArguments($arguments); } /** * Adds an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects */ public function addArguments($arguments = array()) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } /** * Adds an InputArgument object. * * @param InputArgument $argument An InputArgument object * * @throws LogicException When incorrect argument is given */ 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; } /** * Returns an InputArgument by name or by position. * * @param string|int $name The InputArgument name or position * * @return InputArgument An InputArgument object * * @throws InvalidArgumentException When argument given doesn't exist */ 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]; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise */ public function hasArgument($name) { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } /** * Gets the array of InputArgument objects. * * @return InputArgument[] An array of InputArgument objects */ public function getArguments() { return $this->arguments; } /** * Returns the number of InputArguments. * * @return int The number of InputArguments */ public function getArgumentCount() { return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); } /** * Returns the number of required InputArguments. * * @return int The number of required InputArguments */ public function getArgumentRequiredCount() { return $this->requiredCount; } /** * Gets the default values. * * @return array An array of default values */ public function getArgumentDefaults() { $values = array(); foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } /** * Sets the InputOption objects. * * @param InputOption[] $options An array of InputOption objects */ public function setOptions($options = array()) { $this->options = array(); $this->shortcuts = array(); $this->addOptions($options); } /** * Adds an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects */ public function addOptions($options = array()) { foreach ($options as $option) { $this->addOption($option); } } /** * Adds an InputOption object. * * @param InputOption $option An InputOption object * * @throws LogicException When option given already exist */ 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(); } } } /** * Returns an InputOption by name. * * @param string $name The InputOption name * * @return InputOption A InputOption object * * @throws InvalidArgumentException When option given doesn't exist */ public function getOption($name) { if (!$this->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } /** * Returns true if an InputOption object exists by name. * * This method can't be used to check if the user included the option when * executing the command (use getOption() instead). * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise */ public function hasOption($name) { return isset($this->options[$name]); } /** * Gets the array of InputOption objects. * * @return InputOption[] An array of InputOption objects */ public function getOptions() { return $this->options; } /** * Returns true if an InputOption object exists by shortcut. * * @param string $name The InputOption shortcut * * @return bool true if the InputOption object exists, false otherwise */ public function hasShortcut($name) { return isset($this->shortcuts[$name]); } /** * Gets an InputOption by shortcut. * * @param string $shortcut the Shortcut name * * @return InputOption An InputOption object */ public function getOptionForShortcut($shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } /** * Gets an array of default values. * * @return array An array of all default values */ public function getOptionDefaults() { $values = array(); foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } /** * Returns the InputOption name given a shortcut. * * @param string $shortcut The shortcut * * @return string The InputOption name * * @throws InvalidArgumentException When option given does not exist */ private function shortcutToName($shortcut) { if (!isset($this->shortcuts[$shortcut])) { throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } /** * Gets the synopsis. * * @param bool $short Whether to return the short version (with options folded) or not * * @return string The synopsis */ 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); } /** * Returns a textual representation of the InputDefinition. * * @return string A string representing the InputDefinition * * @deprecated since version 2.3, to be removed in 3.0. */ public function asText() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); $descriptor->describe($output, $this, array('raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the InputDefinition. * * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the InputDefinition * * @deprecated since version 2.3, to be removed in 3.0. */ public function asXml($asDom = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getInputDefinitionDocument($this); } $output = new BufferedOutput(); $descriptor->describe($output, $this); return $output->fetch(); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; /** * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier <fabien@symfony.com> */ interface InputInterface { /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument(); /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The values to look for in the raw parameters (can be an array) * * @return bool true if the value is contained in the raw parameters */ public function hasParameterOption($values); /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ public function getParameterOption($values, $default = false); /** * Binds the current Input instance with the given arguments and options. * * @param InputDefinition $definition A InputDefinition instance */ public function bind(InputDefinition $definition); /** * Validates the input. * * @throws RuntimeException When not enough arguments are given */ public function validate(); /** * Returns all the given arguments merged with the default values. * * @return array */ public function getArguments(); /** * Returns the argument value for a given argument name. * * @param string $name The argument name * * @return mixed The argument value * * @throws InvalidArgumentException When argument given doesn't exist */ public function getArgument($name); /** * Sets an argument value by name. * * @param string $name The argument name * @param string $value The argument value * * @throws InvalidArgumentException When argument given doesn't exist */ public function setArgument($name, $value); /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise */ public function hasArgument($name); /** * Returns all the given options merged with the default values. * * @return array */ public function getOptions(); /** * Returns the option value for a given option name. * * @param string $name The option name * * @return mixed The option value * * @throws InvalidArgumentException When option given doesn't exist */ public function getOption($name); /** * Sets an option value by name. * * @param string $name The option name * @param string|bool $value The option value * * @throws InvalidArgumentException When option given doesn't exist */ public function setOption($name, $value); /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise */ public function hasOption($name); /** * Is this input means interactive? * * @return bool */ public function isInteractive(); /** * Sets the input interactivity. * * @param bool $interactive If the input should be interactive */ public function setInteractive($interactive); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * InputAwareInterface should be implemented by classes that depends on the * Console Input. * * @author Wouter J <waldio.webdesign@gmail.com> */ interface InputAwareInterface { /** * Sets the Console Input. * * @param InputInterface */ public function setInput(InputInterface $input); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; /** * Input is the base class for all concrete Input classes. * * Three concrete classes are provided by default: * * * `ArgvInput`: The input comes from the CLI arguments (argv) * * `StringInput`: The input is provided as a string * * `ArrayInput`: The input is provided as an array * * @author Fabien Potencier <fabien@symfony.com> */ abstract class Input implements InputInterface { /** * @var InputDefinition */ protected $definition; protected $options = array(); protected $arguments = array(); protected $interactive = true; /** * @param InputDefinition|null $definition A InputDefinition instance */ public function __construct(InputDefinition $definition = null) { if (null === $definition) { $this->definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } /** * {@inheritdoc} */ public function bind(InputDefinition $definition) { $this->arguments = array(); $this->options = array(); $this->definition = $definition; $this->parse(); } /** * Processes command line arguments. */ abstract protected function parse(); /** * {@inheritdoc} */ 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))); } } /** * {@inheritdoc} */ public function isInteractive() { return $this->interactive; } /** * {@inheritdoc} */ public function setInteractive($interactive) { $this->interactive = (bool) $interactive; } /** * {@inheritdoc} */ public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } /** * {@inheritdoc} */ 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(); } /** * {@inheritdoc} */ 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; } /** * {@inheritdoc} */ public function hasArgument($name) { return $this->definition->hasArgument($name); } /** * {@inheritdoc} */ public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } /** * {@inheritdoc} */ 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(); } /** * {@inheritdoc} */ 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; } /** * {@inheritdoc} */ public function hasOption($name) { return $this->definition->hasOption($name); } /** * Escapes a token through escapeshellarg if it contains unsafe chars. * * @param string $token * * @return string */ public function escapeToken($token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\InvalidOptionException; /** * ArrayInput represents an input provided as an array. * * Usage: * * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); * * @author Fabien Potencier <fabien@symfony.com> */ class ArrayInput extends Input { private $parameters; /** * @param array $parameters An array of parameters * @param InputDefinition|null $definition A InputDefinition instance */ public function __construct(array $parameters, InputDefinition $definition = null) { $this->parameters = $parameters; parent::__construct($definition); } /** * {@inheritdoc} */ public function getFirstArgument() { foreach ($this->parameters as $key => $value) { if ($key && '-' === $key[0]) { continue; } return $value; } } /** * {@inheritdoc} */ public function hasParameterOption($values) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!is_int($k)) { $v = $k; } if (in_array($v, $values)) { return true; } } return false; } /** * {@inheritdoc} */ public function getParameterOption($values, $default = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (is_int($k)) { if (in_array($v, $values)) { return true; } } elseif (in_array($k, $values)) { return $v; } } return $default; } /** * Returns a stringified representation of the args passed to the command. * * @return string */ public function __toString() { $params = array(); foreach ($this->parameters as $param => $val) { if ($param && '-' === $param[0]) { if (is_array($val)) { foreach ($val as $v) { $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : ''); } } else { $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); } } else { $params[] = is_array($val) ? array_map(array($this, 'escapeToken'), $val) : $this->escapeToken($val); } } return implode(' ', $params); } /** * {@inheritdoc} */ protected function parse() { foreach ($this->parameters as $key => $value) { if (0 === strpos($key, '--')) { $this->addLongOption(substr($key, 2), $value); } elseif ('-' === $key[0]) { $this->addShortOption(substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws InvalidOptionException When option given doesn't exist */ 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); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws InvalidOptionException When option given doesn't exist * @throws InvalidOptionException When a required value is missing */ 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; } /** * Adds an argument value. * * @param string $name The argument name * @param mixed $value The value for the argument * * @throws InvalidArgumentException When argument given doesn't exist */ 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; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Represents a command line option. * * @author Fabien Potencier <fabien@symfony.com> */ class InputOption { const VALUE_NONE = 1; const VALUE_REQUIRED = 2; const VALUE_OPTIONAL = 4; const VALUE_IS_ARRAY = 8; private $name; private $shortcut; private $mode; private $default; private $description; /** * @param string $name The option name * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param int $mode The option mode: One of the VALUE_* constants * @param string $description A description text * @param mixed $default The default value (must be null for self::VALUE_NONE) * * @throws InvalidArgumentException If option mode is invalid or incompatible */ public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) { if (0 === strpos($name, '--')) { $name = substr($name, 2); } if (empty($name)) { throw new InvalidArgumentException('An option name cannot be empty.'); } if (empty($shortcut)) { $shortcut = null; } if (null !== $shortcut) { if (is_array($shortcut)) { $shortcut = implode('|', $shortcut); } $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); $shortcuts = array_filter($shortcuts); $shortcut = implode('|', $shortcuts); if (empty($shortcut)) { throw new InvalidArgumentException('An option shortcut cannot be empty.'); } } if (null === $mode) { $mode = self::VALUE_NONE; } elseif (!is_int($mode) || $mode > 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); } /** * Returns the option shortcut. * * @return string The shortcut */ public function getShortcut() { return $this->shortcut; } /** * Returns the option name. * * @return string The name */ public function getName() { return $this->name; } /** * Returns true if the option accepts a value. * * @return bool true if value mode is not self::VALUE_NONE, false otherwise */ public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } /** * Returns true if the option requires a value. * * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise */ public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } /** * Returns true if the option takes an optional value. * * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise */ public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } /** * Returns true if the option can take multiple values. * * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise */ public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value * * @throws LogicException When incorrect default value is given */ 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; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } /** * Checks whether the given option equals this one. * * @param InputOption $option option to compare * * @return bool */ 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() ; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\DebugFormatterHelper; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\ProcessHelper; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputAwareInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\ListCommand; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\ProgressHelper; use Symfony\Component\Console\Helper\TableHelper; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Debug\Exception\FatalThrowableError; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * An Application is the container for a collection of commands. * * It is the main entry point of a Console application. * * This class is optimized for a standard CLI environment. * * Usage: * * $app = new Application('myapp', '1.0 (stable)'); * $app->add(new SimpleCommand()); * $app->run(); * * @author Fabien Potencier <fabien@symfony.com> */ class Application { private $commands = array(); private $wantHelps = false; private $runningCommand; private $name; private $version; private $catchExceptions = true; private $autoExit = true; private $definition; private $helperSet; private $dispatcher; private $terminalDimensions; private $defaultCommand; private $initialized; /** * @param string $name The name of the application * @param string $version The version of the application */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { $this->name = $name; $this->version = $version; $this->defaultCommand = 'list'; } public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code * * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. */ public function run(InputInterface $input = null, OutputInterface $output = null) { 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 || !$x instanceof \Exception) { 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; } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--version', '-V'))) { $output->writeln($this->getLongVersion()); return 0; } $name = $this->getCommandName($input); if (true === $input->hasParameterOption(array('--help', '-h'))) { if (!$name) { $name = 'help'; $input = new ArrayInput(array('command' => 'help')); } else { $this->wantHelps = true; } } if (!$name) { $name = $this->defaultCommand; $definition = $this->getDefinition(); $definition->setArguments(array_merge( $definition->getArguments(), array( 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), ) )); } $this->runningCommand = null; // the command name MUST be the first element of the input $command = $this->find($name); $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } /** * Set a helper set to be used with the command. * * @param HelperSet $helperSet The helper set */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Get the helper set associated with the command. * * @return HelperSet The HelperSet instance associated with this command */ public function getHelperSet() { if (!$this->helperSet) { $this->helperSet = $this->getDefaultHelperSet(); } return $this->helperSet; } /** * Set an input definition to be used with this application. * * @param InputDefinition $definition The input definition */ public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } /** * Gets the InputDefinition related to this Application. * * @return InputDefinition The InputDefinition instance */ public function getDefinition() { if (!$this->definition) { $this->definition = $this->getDefaultInputDefinition(); } return $this->definition; } /** * Gets the help message. * * @return string A help message */ public function getHelp() { return $this->getLongVersion(); } /** * Sets whether to catch exceptions or not during commands execution. * * @param bool $boolean Whether to catch exceptions or not during commands execution */ public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; } /** * Sets whether to automatically exit after a command execution or not. * * @param bool $boolean Whether to automatically exit after a command execution or not */ public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; } /** * Gets the name of the application. * * @return string The application name */ public function getName() { return $this->name; } /** * Sets the application name. * * @param string $name The application name */ public function setName($name) { $this->name = $name; } /** * Gets the application version. * * @return string The application version */ public function getVersion() { return $this->version; } /** * Sets the application version. * * @param string $version The application version */ public function setVersion($version) { $this->version = $version; } /** * Returns the long version of the application. * * @return string The long application version */ public function getLongVersion() { if ('UNKNOWN' !== $this->getName()) { if ('UNKNOWN' !== $this->getVersion()) { return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion()); } return sprintf('<info>%s</info>', $this->getName()); } return '<info>Console Tool</info>'; } /** * Registers a new command. * * @param string $name The command name * * @return Command The newly created command */ public function register($name) { return $this->add(new Command($name)); } /** * Adds an array of command objects. * * If a Command is not enabled it will not be added. * * @param Command[] $commands An array of commands */ public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } /** * Adds a command object. * * If a command with the same name already exists, it will be overridden. * If the command is not enabled it will not be added. * * @param Command $command A Command object * * @return Command|null The registered command if enabled or null */ public function add(Command $command) { $this->init(); $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; } /** * Returns a registered command by name or alias. * * @param string $name The command name or alias * * @return Command A Command object * * @throws CommandNotFoundException When given command name does not exist */ public function get($name) { $this->init(); 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; } /** * Returns true if the command exists, false otherwise. * * @param string $name The command name or alias * * @return bool true if the command exists, false otherwise */ public function has($name) { $this->init(); return isset($this->commands[$name]); } /** * Returns an array of all unique namespaces used by currently registered commands. * * It does not return the global namespace which always exists. * * @return string[] An array of namespaces */ 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))); } /** * Finds a registered namespace by a name or an abbreviation. * * @param string $namespace A namespace or abbreviation to search for * * @return string A registered namespace * * @throws CommandNotFoundException When namespace is incorrect or ambiguous */ 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); } /** * Finds a command by name or alias. * * Contrary to get, this command tries to find the best * match if you give it an abbreviation of a name or alias. * * @param string $name A command name or a command alias * * @return Command A Command instance * * @throws CommandNotFoundException When command name is incorrect or ambiguous */ public function find($name) { $this->init(); $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, ':')) { // check if a namespace exists and contains commands $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); } // filter out aliases for commands which are already on the list 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)); } /** * Gets the commands (registered in the given namespace if provided). * * The array keys are the full names and the values the command instances. * * @param string $namespace A namespace name * * @return Command[] An array of Command instances */ public function all($namespace = null) { $this->init(); 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; } /** * Returns an array of possible abbreviations given a set of names. * * @param array $names An array of names * * @return array An array of abbreviations */ 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; } /** * Returns a text representation of the Application. * * @param string $namespace An optional namespace name * @param bool $raw Whether to return raw command list * * @return string A string representing the Application * * @deprecated since version 2.3, to be removed in 3.0. */ public function asText($namespace = null, $raw = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw); $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the Application. * * @param string $namespace An optional namespace name * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the Application * * @deprecated since version 2.3, to be removed in 3.0. */ public function asXml($namespace = null, $asDom = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getApplicationDocument($this, $namespace); } $output = new BufferedOutput(); $descriptor->describe($output, $this, array('namespace' => $namespace)); return $output->fetch(); } /** * Renders a caught exception. * * @param \Exception $e An exception instance * @param OutputInterface $output An OutputInterface instance */ public function renderException($e, $output) { $output->writeln('', OutputInterface::VERBOSITY_QUIET); do { $title = sprintf(' [%s] ', get_class($e)); $len = Helper::strlen($title); $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 if (defined('HHVM_VERSION') && $width > 1 << 31) { $width = 1 << 31; } $lines = array(); foreach (preg_split('/\r?\n/', trim($e->getMessage())) as $line) { foreach ($this->splitStringByWidth($line, $width - 4) as $line) { // pre-format lines to get the right string length $lineLength = Helper::strlen($line) + 4; $lines[] = array($line, $lineLength); $len = max($lineLength, $len); } } $messages = array(); $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len)); $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); foreach ($lines as $line) { $messages[] = sprintf('<error> %s %s</error>', 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('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET); // exception related properties $trace = $e->getTrace(); array_unshift($trace, array( 'function' => '', 'file' => null !== $e->getFile() ? $e->getFile() : 'n/a', 'line' => null !== $e->getLine() ? $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 <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET); } $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } while ($e = $e->getPrevious()); if (null !== $this->runningCommand) { $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET); $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } /** * Tries to figure out the terminal width in which this application runs. * * @return int|null */ protected function getTerminalWidth() { $dimensions = $this->getTerminalDimensions(); return $dimensions[0]; } /** * Tries to figure out the terminal height in which this application runs. * * @return int|null */ protected function getTerminalHeight() { $dimensions = $this->getTerminalDimensions(); return $dimensions[1]; } /** * Tries to figure out the terminal dimensions based on the current environment. * * @return array Array containing width and height */ public function getTerminalDimensions() { if ($this->terminalDimensions) { return $this->terminalDimensions; } if ('\\' === DIRECTORY_SEPARATOR) { // extract [w, H] from "wxh (WxH)" if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { return array((int) $matches[1], (int) $matches[2]); } // extract [w, h] from "wxh" if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) { return array((int) $matches[1], (int) $matches[2]); } } if ($sttyString = $this->getSttyColumns()) { // extract [w, h] from "rows h; columns w;" if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { return array((int) $matches[2], (int) $matches[1]); } // extract [w, h] from "; h rows; w columns" if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { return array((int) $matches[2], (int) $matches[1]); } } return array(null, null); } /** * Sets terminal dimensions. * * Can be useful to force terminal dimensions for functional tests. * * @param int $width The width * @param int $height The height * * @return $this */ public function setTerminalDimensions($width, $height) { $this->terminalDimensions = array($width, $height); return $this; } /** * Configures the input and output instances based on the user arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function configureIO(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--ansi'))) { $output->setDecorated(true); } elseif (true === $input->hasParameterOption(array('--no-ansi'))) { $output->setDecorated(false); } if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) { $input->setInteractive(false); } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) { $inputStream = $this->getHelperSet()->get('question')->getInputStream(); if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { $input->setInteractive(false); } } if (true === $input->hasParameterOption(array('--quiet', '-q'))) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); $input->setInteractive(false); } else { if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || 3 === $input->getParameterOption('--verbose')) { $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || 2 === $input->getParameterOption('--verbose')) { $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); } } } /** * Runs the current command. * * If an event dispatcher has been attached to the application, * events are also dispatched during the life-cycle of the command. * * @param Command $command A Command instance * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code */ 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); } // bind before the console.command event, so the listeners have access to input options/arguments try { $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); } catch (ExceptionInterface $e) { // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition } $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(); } /** * Gets the name of the command based on input. * * @param InputInterface $input The input interface * * @return string The command name */ protected function getCommandName(InputInterface $input) { return $input->getFirstArgument(); } /** * Gets the default input definition. * * @return InputDefinition An InputDefinition instance */ 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'), )); } /** * Gets the default commands that should always be available. * * @return Command[] An array of default Command instances */ protected function getDefaultCommands() { return array(new HelpCommand(), new ListCommand()); } /** * Gets the default helper set with the helpers that should always be available. * * @return HelperSet A HelperSet instance */ protected function getDefaultHelperSet() { return new HelperSet(array( new FormatterHelper(), new DialogHelper(false), new ProgressHelper(false), new TableHelper(false), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper(), )); } /** * Runs and parses stty -a if it's available, suppressing any error output. * * @return string */ private 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; } } /** * Runs and parses mode CON if it's available, suppressing any error output. * * @return string|null <width>x<height> or null if it could not be parsed */ private function getConsoleMode() { if (!function_exists('proc_open')) { return; } $descriptorspec = array(1 => 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 $matches[2].'x'.$matches[1]; } } } /** * Returns abbreviated suggestions in string format. * * @param array $abbrevs Abbreviated suggestions to convert * * @return string A formatted string of abbreviated suggestions */ private function getAbbreviationSuggestions($abbrevs) { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } /** * Returns the namespace part of the command name. * * This method is not part of public API and should not be used directly. * * @param string $name The full name of the command * @param string $limit The maximum number of parts of the namespace * * @return string The namespace of the command */ public function extractNamespace($name, $limit = null) { $parts = explode(':', $name); array_pop($parts); return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); } /** * Finds alternative of $name among $collection, * if nothing is found in $collection, try in $abbrevs. * * @param string $name The string * @param array|\Traversable $collection The collection * * @return string[] A sorted array of similar string */ 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); } /** * Sets the default Command name. * * @param string $commandName The Command name */ public function setDefaultCommand($commandName) { $this->defaultCommand = $commandName; } private function splitStringByWidth($string, $width) { // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. // additionally, array_slice() is not enough as some character has doubled width. // we need a function to split string not by character count but by 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) { // test if $char could be appended to current line if (mb_strwidth($line.$char, 'utf8') <= $width) { $line .= $char; continue; } // if not, push current line to array and make new line $lines[] = str_pad($line, $width); $line = $char; } $lines[] = count($lines) ? str_pad($line, $width) : $line; mb_convert_variables($encoding, 'utf8', $lines); return $lines; } /** * Returns all namespaces of the command name. * * @param string $name The full name of the command * * @return string[] The namespaces of the command */ private function extractAllNamespaces($name) { // -1 as third argument is needed to skip the command short name when exploding $parts = explode(':', $name, -1); $namespaces = array(); foreach ($parts as $part) { if (count($namespaces)) { $namespaces[] = end($namespaces).':'.$part; } else { $namespaces[] = $part; } } return $namespaces; } private function init() { if ($this->initialized) { return; } $this->initialized = true; foreach ($this->getDefaultCommands() as $command) { $this->add($command); } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * @author Jean-François Simon <contact@jfsimon.fr> */ class OutputFormatterStyleStack { /** * @var OutputFormatterStyleInterface[] */ private $styles; /** * @var OutputFormatterStyleInterface */ private $emptyStyle; /** * @param OutputFormatterStyleInterface|null $emptyStyle */ public function __construct(OutputFormatterStyleInterface $emptyStyle = null) { $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); $this->reset(); } /** * Resets stack (ie. empty internal arrays). */ public function reset() { $this->styles = array(); } /** * Pushes a style in the stack. * * @param OutputFormatterStyleInterface $style */ public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } /** * Pops a style from the stack. * * @param OutputFormatterStyleInterface|null $style * * @return OutputFormatterStyleInterface * * @throws InvalidArgumentException When style tags incorrectly nested */ 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.'); } /** * Computes current style with stacks top codes. * * @return OutputFormatterStyle */ public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[count($this->styles) - 1]; } /** * @param OutputFormatterStyleInterface $emptyStyle * * @return $this */ public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } /** * @return OutputFormatterStyleInterface */ public function getEmptyStyle() { return $this->emptyStyle; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Formatter class for console output. * * @author Konstantin Kudryashov <ever.zet@gmail.com> */ class OutputFormatter implements OutputFormatterInterface { private $decorated; private $styles = array(); private $styleStack; /** * Escapes "<" special char in given text. * * @param string $text Text to escape * * @return string Escaped text */ public static function escape($text) { $text = preg_replace('/([^\\\\]?)</', '$1\\<', $text); return self::escapeTrailingBackslash($text); } /** * Escapes trailing "\" in given text. * * @param string $text Text to escape * * @return string Escaped text * * @internal */ public static function escapeTrailingBackslash($text) { if ('\\' === substr($text, -1)) { $len = strlen($text); $text = rtrim($text, '\\'); $text = str_replace("\0", '', $text); $text .= str_repeat("\0", $len - strlen($text)); } return $text; } /** * Initializes console output formatter. * * @param bool $decorated Whether this formatter should actually decorate strings * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances */ public function __construct($decorated = false, array $styles = array()) { $this->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(); } /** * {@inheritdoc} */ public function setDecorated($decorated) { $this->decorated = (bool) $decorated; } /** * {@inheritdoc} */ public function isDecorated() { return $this->decorated; } /** * {@inheritdoc} */ public function setStyle($name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } /** * {@inheritdoc} */ public function hasStyle($name) { return isset($this->styles[strtolower($name)]); } /** * {@inheritdoc} */ public function getStyle($name) { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(sprintf('Undefined style: %s', $name)); } return $this->styles[strtolower($name)]; } /** * {@inheritdoc} */ 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; } // add the text up to the next tag $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); $offset = $pos + strlen($text); // opening tag? 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, "\0")) { return strtr($output, array("\0" => '\\', '\\<' => '<')); } return str_replace('\\<', '<', $output); } /** * @return OutputFormatterStyleStack */ public function getStyleStack() { return $this->styleStack; } /** * Tries to create new style instance from string. * * @param string $string * * @return OutputFormatterStyle|false false if string is not format string */ private function createStyleFromString($string) { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($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]); } else { try { $style->setOption($match[1]); } catch (\InvalidArgumentException $e) { return false; } } } return $style; } /** * Applies current style from stack to text, if must be applied. * * @param string $text Input text * * @return string Styled text */ private function applyCurrentStyle($text) { return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Formatter style class for defining styles. * * @author Konstantin Kudryashov <ever.zet@gmail.com> */ class OutputFormatterStyle implements OutputFormatterStyleInterface { private static $availableForegroundColors = array( 'black' => 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(); /** * Initializes output formatter style. * * @param string|null $foreground The style foreground color name * @param string|null $background The style background color name * @param array $options The style options */ 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); } } /** * Sets style foreground color. * * @param string|null $color The color name * * @throws InvalidArgumentException When the color name isn't defined */ 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]; } /** * Sets style background color. * * @param string|null $color The color name * * @throws InvalidArgumentException When the color name isn't defined */ 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]; } /** * Sets some specific style option. * * @param string $option The option name * * @throws InvalidArgumentException When the option name isn't defined */ 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]; } } /** * Unsets some specific style option. * * @param string $option The option name * * @throws InvalidArgumentException When the option name isn't defined */ 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]); } } /** * Sets multiple style options at once. * * @param array $options */ public function setOptions(array $options) { $this->options = array(); foreach ($options as $option) { $this->setOption($option); } } /** * Applies the style to a given text. * * @param string $text The text to style * * @return string */ 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)); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter interface for console output. * * @author Konstantin Kudryashov <ever.zet@gmail.com> */ interface OutputFormatterInterface { /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages or not */ public function setDecorated($decorated); /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise */ public function isDecorated(); /** * Sets a new style. * * @param string $name The style name * @param OutputFormatterStyleInterface $style The style instance */ public function setStyle($name, OutputFormatterStyleInterface $style); /** * Checks if output formatter has style with specified name. * * @param string $name * * @return bool */ public function hasStyle($name); /** * Gets style options from style with specified name. * * @param string $name * * @return OutputFormatterStyleInterface * * @throws \InvalidArgumentException When style isn't defined */ public function getStyle($name); /** * Formats a message according to the given styles. * * @param string $message The message to style * * @return string The styled message */ public function format($message); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter style interface for defining styles. * * @author Konstantin Kudryashov <ever.zet@gmail.com> */ interface OutputFormatterStyleInterface { /** * Sets style foreground color. * * @param string $color The color name */ public function setForeground($color = null); /** * Sets style background color. * * @param string $color The color name */ public function setBackground($color = null); /** * Sets some specific style option. * * @param string $option The option name */ public function setOption($option); /** * Unsets some specific style option. * * @param string $option The option name */ public function unsetOption($option); /** * Sets multiple style options at once. * * @param array $options */ public function setOptions(array $options); /** * Applies the style to a given text. * * @param string $text The text to style * * @return string */ public function apply($text); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; use Symfony\Component\Console\Application; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\SymfonyQuestionHelper; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; /** * Output decorator helpers for the Symfony Style Guide. * * @author Kevin Bond <kevinbond@gmail.com> */ class SymfonyStyle extends OutputStyle { const MAX_LINE_LENGTH = 120; private $input; private $questionHelper; private $progressBar; private $lineLength; private $bufferedOutput; /** * @param InputInterface $input * @param OutputInterface $output */ public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($output); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * @param string|null $type The block type (added in [] on first line) * @param string|null $style The style to apply to the whole block * @param string $prefix The prefix for the block * @param bool $padding Whether to add vertical padding */ 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(); } /** * {@inheritdoc} */ public function title($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)), sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), )); $this->newLine(); } /** * {@inheritdoc} */ public function section($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)), sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), )); $this->newLine(); } /** * {@inheritdoc} */ public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } /** * {@inheritdoc} */ public function text($message) { $this->autoPrependText(); $messages = is_array($message) ? array_values($message) : array($message); foreach ($messages as $message) { $this->writeln(sprintf(' %s', $message)); } } /** * Formats a command comment. * * @param string|array $message */ public function comment($message) { $messages = is_array($message) ? array_values($message) : array($message); $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, null, null, '<fg=default;bg=default> // </>')); $this->newLine(); } /** * {@inheritdoc} */ public function success($message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } /** * {@inheritdoc} */ public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } /** * {@inheritdoc} */ public function warning($message) { $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); } /** * {@inheritdoc} */ public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } /** * {@inheritdoc} */ public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } /** * {@inheritdoc} */ public function table(array $headers, array $rows) { $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('<info>%s</info>'); $table = new Table($this); $table->setHeaders($headers); $table->setRows($rows); $table->setStyle($style); $table->render(); $this->newLine(); } /** * {@inheritdoc} */ public function ask($question, $default = null, $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function askHidden($question, $validator = null) { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function confirm($question, $default = true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } /** * {@inheritdoc} */ 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)); } /** * {@inheritdoc} */ public function progressStart($max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } /** * {@inheritdoc} */ public function progressAdvance($step = 1) { $this->getProgressBar()->advance($step); } /** * {@inheritdoc} */ public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } /** * {@inheritdoc} */ public function createProgressBar($max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' !== DIRECTORY_SEPARATOR) { $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); // dark shade character \u2593 } return $progressBar; } /** * @param Question $question * * @return string */ 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; } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { parent::writeln($messages, $type); $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { parent::write($messages, $newline, $type); $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); } /** * {@inheritdoc} */ public function newLine($count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } /** * @return ProgressBar */ private function getProgressBar() { if (!$this->progressBar) { throw new RuntimeException('The ProgressBar is not started.'); } return $this->progressBar; } private function getTerminalWidth() { $application = new Application(); $dimensions = $application->getTerminalDimensions(); return $dimensions[0] ?: self::MAX_LINE_LENGTH; } private function autoPrependBlock() { $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { return $this->newLine(); //empty history, so we should start with a new line. } //Prepend new line for each non LF chars (This means no blank line was output before) $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText() { $fetched = $this->bufferedOutput->fetch(); //Prepend new line if last char isn't EOL: if ("\n" !== substr($fetched, -1)) { $this->newLine(); } } private function reduceBuffer($messages) { // We need to know if the two last chars are PHP_EOL // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer 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); } // wrap and add newlines for each element 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; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\OutputInterface; /** * Decorates output to add console style guide helpers. * * @author Kevin Bond <kevinbond@gmail.com> */ abstract class OutputStyle implements OutputInterface, StyleInterface { private $output; /** * @param OutputInterface $output */ public function __construct(OutputInterface $output) { $this->output = $output; } /** * {@inheritdoc} */ public function newLine($count = 1) { $this->output->write(str_repeat(PHP_EOL, $count)); } /** * @param int $max * * @return ProgressBar */ public function createProgressBar($max = 0) { return new ProgressBar($this->output, $max); } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } /** * {@inheritdoc} */ public function setVerbosity($level) { $this->output->setVerbosity($level); } /** * {@inheritdoc} */ public function getVerbosity() { return $this->output->getVerbosity(); } /** * {@inheritdoc} */ public function setDecorated($decorated) { $this->output->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated() { return $this->output->isDecorated(); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } /** * {@inheritdoc} */ public function getFormatter() { return $this->output->getFormatter(); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; /** * Output style helpers. * * @author Kevin Bond <kevinbond@gmail.com> */ interface StyleInterface { /** * Formats a command title. * * @param string $message */ public function title($message); /** * Formats a section title. * * @param string $message */ public function section($message); /** * Formats a list. * * @param array $elements */ public function listing(array $elements); /** * Formats informational text. * * @param string|array $message */ public function text($message); /** * Formats a success result bar. * * @param string|array $message */ public function success($message); /** * Formats an error result bar. * * @param string|array $message */ public function error($message); /** * Formats an warning result bar. * * @param string|array $message */ public function warning($message); /** * Formats a note admonition. * * @param string|array $message */ public function note($message); /** * Formats a caution admonition. * * @param string|array $message */ public function caution($message); /** * Formats a table. * * @param array $headers * @param array $rows */ public function table(array $headers, array $rows); /** * Asks a question. * * @param string $question * @param string|null $default * @param callable|null $validator * * @return string */ public function ask($question, $default = null, $validator = null); /** * Asks a question with the user input hidden. * * @param string $question * @param callable|null $validator * * @return string */ public function askHidden($question, $validator = null); /** * Asks for confirmation. * * @param string $question * @param bool $default * * @return bool */ public function confirm($question, $default = true); /** * Asks a choice question. * * @param string $question * @param array $choices * @param string|int|null $default * * @return string */ public function choice($question, array $choices, $default = null); /** * Add newline(s). * * @param int $count The number of newlines */ public function newLine($count = 1); /** * Starts the progress output. * * @param int $max Maximum steps (0 if unknown) */ public function progressStart($max = 0); /** * Advances the progress output X steps. * * @param int $step Number of steps to advance */ public function progressAdvance($step = 1); /** * Finishes the progress output. */ public function progressFinish(); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to manipulate the exit code of a command after its execution. * * @author Francesco Levorato <git@flevour.net> */ class ConsoleTerminateEvent extends ConsoleEvent { /** * The exit code of the command. * * @var int */ private $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) { parent::__construct($command, $input, $output); $this->setExitCode($exitCode); } /** * Sets the exit code. * * @param int $exitCode The command exit code */ public function setExitCode($exitCode) { $this->exitCode = (int) $exitCode; } /** * Gets the exit code. * * @return int The command exit code */ public function getExitCode() { return $this->exitCode; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; /** * Allows to do things before the command is executed, like skipping the command or changing the input. * * @author Fabien Potencier <fabien@symfony.com> */ class ConsoleCommandEvent extends ConsoleEvent { /** * The return code for skipped commands, this will also be passed into the terminate event. */ const RETURN_CODE_DISABLED = 113; /** * Indicates if the command should be run or skipped. * * @var bool */ private $commandShouldRun = true; /** * Disables the command, so it won't be run. * * @return bool */ public function disableCommand() { return $this->commandShouldRun = false; } /** * Enables the command. * * @return bool */ public function enableCommand() { return $this->commandShouldRun = true; } /** * Returns true if the command is runnable, false otherwise. * * @return bool */ public function commandShouldRun() { return $this->commandShouldRun; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\Event; /** * Allows to inspect input and output of a command. * * @author Francesco Levorato <git@flevour.net> */ class ConsoleEvent extends Event { protected $command; private $input; private $output; public function __construct(Command $command, InputInterface $input, OutputInterface $output) { $this->command = $command; $this->input = $input; $this->output = $output; } /** * Gets the command that is executed. * * @return Command A Command instance */ public function getCommand() { return $this->command; } /** * Gets the input instance. * * @return InputInterface An InputInterface instance */ public function getInput() { return $this->input; } /** * Gets the output instance. * * @return OutputInterface An OutputInterface instance */ public function getOutput() { return $this->output; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to handle exception thrown in a command. * * @author Fabien Potencier <fabien@symfony.com> */ class ConsoleExceptionEvent extends ConsoleEvent { private $exception; private $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) { parent::__construct($command, $input, $output); $this->setException($exception); $this->exitCode = (int) $exitCode; } /** * Returns the thrown exception. * * @return \Exception The thrown exception */ public function getException() { return $this->exception; } /** * Replaces the thrown exception. * * This exception will be thrown if no response is set in the event. * * @param \Exception $exception The thrown exception */ public function setException(\Exception $exception) { $this->exception = $exception; } /** * Gets the exit code. * * @return int The command exit code */ public function getExitCode() { return $this->exitCode; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Eases the testing of console commands. * * @author Fabien Potencier <fabien@symfony.com> */ class CommandTester { private $command; private $input; private $output; private $statusCode; /** * @param Command $command A Command instance to test */ public function __construct(Command $command) { $this->command = $command; } /** * Executes the command. * * Available execution options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of command arguments and options * @param array $options An array of execution options * * @return int The command exit code */ public function execute(array $input, array $options = array()) { // set the command name automatically if the application requires // this argument and no command name was passed 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 (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); } /** * Gets the display returned by the last execution of the command. * * @param bool $normalize Whether to normalize end of lines to \n or not * * @return string The display */ 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; } /** * Gets the input instance used by the last execution of the command. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the command. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } /** * Gets the status code returned by the last execution of the application. * * @return int The status code */ public function getStatusCode() { return $this->statusCode; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\StreamOutput; /** * Eases the testing of console applications. * * When testing an application, don't forget to disable the auto exit flag: * * $application = new Application(); * $application->setAutoExit(false); * * @author Fabien Potencier <fabien@symfony.com> */ class ApplicationTester { private $application; private $input; private $output; private $statusCode; /** * @param Application $application An Application instance to test */ public function __construct(Application $application) { $this->application = $application; } /** * Executes the application. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of arguments and options * @param array $options An array of options * * @return int The command exit code */ public function run(array $input, $options = array()) { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $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']); } return $this->statusCode = $this->application->run($this->input, $this->output); } /** * Gets the display returned by the last execution of the application. * * @param bool $normalize Whether to normalize end of lines to \n or not * * @return string The display */ 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; } /** * Gets the input instance used by the last execution of the application. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the application. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } /** * Gets the status code returned by the last execution of the application. * * @return int The status code */ public function getStatusCode() { return $this->statusCode; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Process\ProcessBuilder; use Symfony\Component\Process\PhpExecutableFinder; /** * A Shell wraps an Application to add shell capabilities to it. * * Support for history and completion only works with a PHP compiled * with readline support (either --with-readline or --with-libedit) * * @deprecated since version 2.8, to be removed in 3.0. * * @author Fabien Potencier <fabien@symfony.com> * @author Martin Hasoň <martin.hason@gmail.com> */ class Shell { private $application; private $history; private $output; private $hasReadline; private $processIsolation = false; /** * If there is no readline support for the current PHP executable * a \RuntimeException exception is thrown. * * @param Application $application An application instance */ public function __construct(Application $application) { @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); $this->hasReadline = function_exists('readline'); $this->application = $application; $this->history = getenv('HOME').'/.history_'.$application->getName(); $this->output = new ConsoleOutput(); } /** * Runs the shell. */ public function run() { $this->application->setAutoExit(false); $this->application->setCatchExceptions(true); if ($this->hasReadline) { readline_read_history($this->history); readline_completion_function(array($this, 'autocompleter')); } $this->output->writeln($this->getHeader()); $php = null; if ($this->processIsolation) { $finder = new PhpExecutableFinder(); $php = $finder->find(); $this->output->writeln(<<<'EOF' <info>Running with process isolation, you should consider this:</info> * each command is executed as separate process, * commands don't support interactivity, all params must be passed explicitly, * commands output is not colorized. EOF ); } while (true) { $command = $this->readline(); if (false === $command) { $this->output->writeln("\n"); break; } if ($this->hasReadline) { readline_add_history($command); readline_write_history($this->history); } if ($this->processIsolation) { $pb = new ProcessBuilder(); $process = $pb ->add($php) ->add($_SERVER['argv'][0]) ->add($command) ->inheritEnvironmentVariables(true) ->getProcess() ; $output = $this->output; $process->run(function ($type, $data) use ($output) { $output->writeln($data); }); $ret = $process->getExitCode(); } else { $ret = $this->application->run(new StringInput($command), $this->output); } if (0 !== $ret) { $this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret)); } } } /** * Returns the shell header. * * @return string The header string */ protected function getHeader() { return <<<EOF Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>). At the prompt, type <comment>help</comment> for some help, or <comment>list</comment> to get a list of available commands. To exit the shell, type <comment>^D</comment>. EOF; } /** * Renders a prompt. * * @return string The prompt */ protected function getPrompt() { // using the formatter here is required when using readline return $this->output->getFormatter()->format($this->application->getName().' > '); } protected function getOutput() { return $this->output; } protected function getApplication() { return $this->application; } /** * Tries to return autocompletion for the current entered text. * * @param string $text The last segment of the entered text * * @return bool|array A list of guessed strings or true */ private function autocompleter($text) { $info = readline_info(); $text = substr($info['line_buffer'], 0, $info['end']); if ($info['point'] !== $info['end']) { return true; } // task name? if (false === strpos($text, ' ') || !$text) { return array_keys($this->application->all()); } // options and arguments? try { $command = $this->application->find(substr($text, 0, strpos($text, ' '))); } catch (\Exception $e) { return true; } $list = array('--help'); foreach ($command->getDefinition()->getOptions() as $option) { $list[] = '--'.$option->getName(); } return $list; } /** * Reads a single line from standard input. * * @return string The single line from standard input */ private function readline() { if ($this->hasReadline) { $line = readline($this->getPrompt()); } else { $this->output->write($this->getPrompt()); $line = fgets(STDIN, 1024); $line = (false === $line || '' === $line) ? false : rtrim($line); } return $line; } public function getProcessIsolation() { return $this->processIsolation; } public function setProcessIsolation($processIsolation) { $this->processIsolation = (bool) $processIsolation; if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) { throw new RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.'); } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\CommandNotFoundException; /** * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class ApplicationDescription { const GLOBAL_NAMESPACE = '_global'; /** * @var Application */ private $application; /** * @var null|string */ private $namespace; /** * @var array */ private $namespaces; /** * @var Command[] */ private $commands; /** * @var Command[] */ private $aliases; /** * @param Application $application * @param string|null $namespace */ public function __construct(Application $application, $namespace = null) { $this->application = $application; $this->namespace = $namespace; } /** * @return array */ public function getNamespaces() { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } /** * @return Command[] */ public function getCommands() { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } /** * @param string $name * * @return Command * * @throws CommandNotFoundException */ 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(); /** @var Command $command */ foreach ($commands as $name => $command) { if (!$command->getName()) { continue; } if ($command->getName() === $name) { $this->commands[$name] = $command; } else { $this->aliases[$name] = $command; } $names[] = $name; } $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); } } /** * @param array $commands * * @return array */ 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 reference to keep scope clear unset($commandsSet); return $namespacedCommands; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * XML descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class XmlDescriptor extends Descriptor { /** * @param InputDefinition $definition * * @return \DOMDocument */ public function getInputDefinitionDocument(InputDefinition $definition) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->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; } /** * @param Command $command * * @return \DOMDocument */ 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; } /** * @param Application $application * @param string|null $namespace * * @return \DOMDocument */ public function getApplicationDocument(Application $application, $namespace = null) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); if ('UNKNOWN' !== $application->getName()) { $rootXml->setAttribute('name', $application->getName()); if ('UNKNOWN' !== $application->getVersion()) { $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; } /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->writeDocument($this->getInputArgumentDocument($argument)); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeDocument($this->getInputOptionDocument($option)); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $this->writeDocument($this->getCommandDocument($command)); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); } /** * Appends document children to parent node. * * @param \DOMNode $parentNode * @param \DOMNode $importedParent */ private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } /** * Writes DOM document. * * @param \DOMDocument $dom * * @return \DOMDocument|string */ private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } /** * @param InputArgument $argument * * @return \DOMDocument */ 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; } /** * @param InputOption $option * * @return \DOMDocument */ 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; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * Text descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class TextDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { $default = sprintf('<comment> [default: %s]</comment>', $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(' <info>%s</info> %s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), // + 4 = 2 spaces before <info>, 2 spaces after </info> preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), $default ), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { $default = sprintf('<comment> [default: %s]</comment>', $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(' <info>%s</info> %s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), // + 4 = 2 spaces before <info>, 2 spaces after </info> preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '' ), $options); } /** * {@inheritdoc} */ 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('<comment>Arguments:</comment>', $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('<comment>Options:</comment>', $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))); } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $command->getSynopsis(true); $command->getSynopsis(false); $command->mergeApplicationDefinition(false); $this->writeText('<comment>Usage:</comment>', $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('<comment>Help:</comment>', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } /** * {@inheritdoc} */ 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("<comment>Usage:</comment>\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('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options); } else { $this->writeText('<comment>Available commands:</comment>', $options); } // add commands by namespace foreach ($description->getNamespaces() as $namespace) { if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->writeText("\n"); $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options); } foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $spacingWidth = $width - Helper::strlen($name); $this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); } } $this->writeText("\n"); } } /** * {@inheritdoc} */ 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 ); } /** * Formats input option/argument default value. * * @param mixed $default * * @return string */ private function formatDefaultValue($default) { if (INF === $default) { return 'INF'; } 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); } } } if (\PHP_VERSION_ID < 50400) { return str_replace(array('\/', '\\\\'), array('/', '\\'), json_encode($default)); } return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } /** * @param Command[] $commands * * @return int */ 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; } /** * @param InputOption[] $options * * @return int */ private function calculateTotalWidthForOptions($options) { $totalWidth = 0; foreach ($options as $option) { // "-" + shortcut + ", --" + name $nameLength = 1 + max(strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); if ($option->acceptValue()) { $valueLength = 1 + Helper::strlen($option->getName()); // = + value $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] $nameLength += $valueLength; } $totalWidth = max($totalWidth, $nameLength); } return $totalWidth; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * Markdown descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class MarkdownDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->write( '**'.$argument->getName().':**'."\n\n" .'* Name: '.($argument->getName() ?: '<none>')."\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() ?: '<none>')."\n" .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ 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())).'`' : '<none>')."\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() ?: '<none>')."\n" .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ 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)); } } } /** * {@inheritdoc} */ 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() ?: '<none>')."\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()); } } /** * {@inheritdoc} */ 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)); } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ abstract class Descriptor implements DescriptorInterface { /** * @var OutputInterface */ private $output; /** * {@inheritdoc} */ public function describe(OutputInterface $output, $object, array $options = array()) { $this->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))); } } /** * Writes content to output. * * @param string $content * @param bool $decorated */ protected function write($content, $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } /** * Describes an InputArgument instance. * * @param InputArgument $argument * @param array $options * * @return string|mixed */ abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); /** * Describes an InputOption instance. * * @param InputOption $option * @param array $options * * @return string|mixed */ abstract protected function describeInputOption(InputOption $option, array $options = array()); /** * Describes an InputDefinition instance. * * @param InputDefinition $definition * @param array $options * * @return string|mixed */ abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); /** * Describes a Command instance. * * @param Command $command * @param array $options * * @return string|mixed */ abstract protected function describeCommand(Command $command, array $options = array()); /** * Describes an Application instance. * * @param Application $application * @param array $options * * @return string|mixed */ abstract protected function describeApplication(Application $application, array $options = array()); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Output\OutputInterface; /** * Descriptor interface. * * @author Jean-François Simon <contact@jfsimon.fr> */ interface DescriptorInterface { /** * Describes an InputArgument instance. * * @param OutputInterface $output * @param object $object * @param array $options */ public function describe(OutputInterface $output, $object, array $options = array()); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * JSON descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class JsonDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->writeData($this->getInputArgumentData($argument), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeData($this->getInputOptionData($option), $options); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeData($this->getInputDefinitionData($definition), $options); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $this->writeData($this->getCommandData($command), $options); } /** * {@inheritdoc} */ 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); } /** * Writes data as json. * * @param array $data * @param array $options * * @return array|string */ private function writeData(array $data, array $options) { $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); } /** * @param InputArgument $argument * * @return array */ 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' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), ); } /** * @param InputOption $option * * @return array */ 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' => INF === $option->getDefault() ? 'INF' : $option->getDefault(), ); } /** * @param InputDefinition $definition * * @return array */ 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); } /** * @param Command $command * * @return array */ 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()), ); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Logger; use Psr\Log\AbstractLogger; use Psr\Log\InvalidArgumentException; use Psr\Log\LogLevel; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; /** * PSR-3 compliant console logger. * * @author Kévin Dunglas <dunglas@gmail.com> * * @see http://www.php-fig.org/psr/psr-3/ */ class ConsoleLogger extends AbstractLogger { const INFO = 'info'; const ERROR = 'error'; /** * @var OutputInterface */ private $output; /** * @var array */ private $verbosityLevelMap = array( LogLevel::EMERGENCY => 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, ); /** * @var array */ 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, ); /** * @param OutputInterface $output * @param array $verbosityLevelMap * @param array $formatLevelMap */ public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) { $this->output = $output; $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } /** * {@inheritdoc} */ 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)); } // Write to the error output if necessary and available if (self::ERROR === $this->formatLevelMap[$level] && $this->output instanceof ConsoleOutputInterface) { $output = $this->output->getErrorOutput(); } else { $output = $this->output; } if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context))); } } /** * Interpolates context values into the message placeholders. * * @author PHP Framework Interoperability Group * * @param string $message * @param array $context * * @return string */ private function interpolate($message, array $context) { // build a replacement array with braces around the context keys $replace = array(); foreach ($context as $key => $val) { if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { $replace[sprintf('{%s}', $key)] = $val; } } // interpolate replacement values into the message and return return strtr($message, $replace); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Exception\LogicException; /** * The ProgressBar provides helpers to display progress output. * * @author Fabien Potencier <fabien@symfony.com> * @author Chris Jones <leeked@gmail.com> */ class ProgressBar { // options private $barWidth = 28; private $barChar; private $emptyBarChar = '-'; private $progressChar = '>'; private $format; private $internalFormat; private $redrawFreq = 1; /** * @var OutputInterface */ private $output; private $step = 0; private $max; private $startTime; private $stepWidth; private $percent = 0.0; private $formatLineCount; private $messages = array(); private $overwrite = true; private $firstRun = true; private static $formatters; private static $formats; /** * @param OutputInterface $output An OutputInterface instance * @param int $max Maximum steps (0 if unknown) */ public function __construct(OutputInterface $output, $max = 0) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->output = $output; $this->setMaxSteps($max); if (!$this->output->isDecorated()) { // disable overwrite when output does not support ANSI codes. $this->overwrite = false; // set a reasonable redraw frequency so output isn't flooded $this->setRedrawFrequency($max / 10); } $this->startTime = time(); } /** * Sets a placeholder formatter for a given name. * * This method also allow you to override an existing placeholder. * * @param string $name The placeholder name (including the delimiter char like %) * @param callable $callable A PHP callable */ public static function setPlaceholderFormatterDefinition($name, $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } /** * Gets the placeholder formatter for a given name. * * @param string $name The placeholder name (including the delimiter char like %) * * @return callable|null A PHP callable */ public static function getPlaceholderFormatterDefinition($name) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; } /** * Sets a format for a given name. * * This method also allow you to override an existing format. * * @param string $name The format name * @param string $format A format string */ public static function setFormatDefinition($name, $format) { if (!self::$formats) { self::$formats = self::initFormats(); } self::$formats[$name] = $format; } /** * Gets the format for a given name. * * @param string $name The format name * * @return string|null A format string */ public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return isset(self::$formats[$name]) ? self::$formats[$name] : null; } /** * Associates a text with a named placeholder. * * The text is displayed when the progress bar is rendered but only * when the corresponding placeholder is part of the custom format line * (by wrapping the name with %). * * @param string $message The text to associate with the placeholder * @param string $name The name of the placeholder */ public function setMessage($message, $name = 'message') { $this->messages[$name] = $message; } public function getMessage($name = 'message') { return $this->messages[$name]; } /** * Gets the progress bar start time. * * @return int The progress bar start time */ public function getStartTime() { return $this->startTime; } /** * Gets the progress bar maximal steps. * * @return int The progress bar max steps */ public function getMaxSteps() { return $this->max; } /** * Gets the progress bar step. * * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead. * * @return int The progress bar step */ public function getStep() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED); return $this->getProgress(); } /** * Gets the current step position. * * @return int The progress bar step */ public function getProgress() { return $this->step; } /** * Gets the progress bar step width. * * @internal This method is public for PHP 5.3 compatibility, it should not be used. * * @return int The progress bar step width */ public function getStepWidth() { return $this->stepWidth; } /** * Gets the current progress bar percent. * * @return float The current progress bar percent */ public function getProgressPercent() { return $this->percent; } /** * Sets the progress bar width. * * @param int $size The progress bar size */ public function setBarWidth($size) { $this->barWidth = (int) $size; } /** * Gets the progress bar width. * * @return int The progress bar size */ public function getBarWidth() { return $this->barWidth; } /** * Sets the bar character. * * @param string $char A character */ public function setBarCharacter($char) { $this->barChar = $char; } /** * Gets the bar character. * * @return string A character */ public function getBarCharacter() { if (null === $this->barChar) { return $this->max ? '=' : $this->emptyBarChar; } return $this->barChar; } /** * Sets the empty bar character. * * @param string $char A character */ public function setEmptyBarCharacter($char) { $this->emptyBarChar = $char; } /** * Gets the empty bar character. * * @return string A character */ public function getEmptyBarCharacter() { return $this->emptyBarChar; } /** * Sets the progress bar character. * * @param string $char A character */ public function setProgressCharacter($char) { $this->progressChar = $char; } /** * Gets the progress bar character. * * @return string A character */ public function getProgressCharacter() { return $this->progressChar; } /** * Sets the progress bar format. * * @param string $format The format */ public function setFormat($format) { $this->format = null; $this->internalFormat = $format; } /** * Sets the redraw frequency. * * @param int|float $freq The frequency in steps */ public function setRedrawFrequency($freq) { $this->redrawFreq = max((int) $freq, 1); } /** * Starts the progress output. * * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged */ public function start($max = null) { $this->startTime = time(); $this->step = 0; $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); } $this->display(); } /** * Advances the progress output X steps. * * @param int $step Number of steps to advance * * @throws LogicException */ public function advance($step = 1) { $this->setProgress($this->step + $step); } /** * Sets the current progress. * * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead. * * @param int $step The current progress * * @throws LogicException */ public function setCurrent($step) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED); $this->setProgress($step); } /** * Sets whether to overwrite the progressbar, false for new line. * * @param bool $overwrite */ public function setOverwrite($overwrite) { $this->overwrite = (bool) $overwrite; } /** * Sets the current progress. * * @param int $step The current progress * * @throws LogicException */ public function setProgress($step) { $step = (int) $step; if ($step < $this->step) { throw new LogicException('You can\'t regress the progress bar.'); } if ($this->max && $step > $this->max) { $this->max = $step; } $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(); } } /** * Finishes the progress output. */ public function finish() { if (!$this->max) { $this->max = $this->step; } if ($this->step === $this->max && !$this->overwrite) { // prevent double 100% output return; } $this->setProgress($this->max); } /** * Outputs the current progress string. */ public function display() { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped. $self = $this; $output = $this->output; $messages = $this->messages; $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) { if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { $text = call_user_func($formatter, $self, $output); } elseif (isset($messages[$matches[1]])) { $text = $messages[$matches[1]]; } else { return $matches[0]; } if (isset($matches[2])) { $text = sprintf('%'.$matches[2], $text); } return $text; }, $this->format)); } /** * Removes the progress bar from the current line. * * This is useful if you wish to write some output * while a progress bar is running. * Call display() to show the progress bar again. */ public function clear() { if (!$this->overwrite) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite(''); } /** * Sets the progress bar format. * * @param string $format The format */ private function setRealFormat($format) { // try to use the _nomax variant if available 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"); } /** * Sets the progress bar maximal steps. * * @param int $max The progress bar max steps */ private function setMaxSteps($max) { $this->max = max(0, (int) $max); $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; } /** * Overwrites a previous message to the output. * * @param string $message The message */ private function overwrite($message) { if ($this->overwrite) { if (!$this->firstRun) { // Move the cursor to the beginning of the line $this->output->write("\x0D"); // Erase the line $this->output->write("\x1B[2K"); // Erase previous lines 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()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway 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%', ); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Question\ChoiceQuestion; /** * The QuestionHelper class provides helpers to interact with the user. * * @author Fabien Potencier <fabien@symfony.com> */ class QuestionHelper extends Helper { private $inputStream; private static $shell; private static $stty; /** * Asks a question to the user. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * @param Question $question The question to ask * * @return mixed The user answer * * @throws RuntimeException If there is no data to read in the input stream */ public function ask(InputInterface $input, OutputInterface $output, Question $question) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } if (!$input->isInteractive()) { return $question->getDefault(); } if (!$question->getValidator()) { return $this->doAsk($output, $question); } $that = $this; $interviewer = function () use ($output, $question, $that) { return $that->doAsk($output, $question); }; return $this->validateAttempts($interviewer, $output, $question); } /** * Sets the input stream to read from when interacting with the user. * * This is mainly useful for testing purpose. * * @param resource $stream The input stream * * @throws InvalidArgumentException In case the stream is not a resource */ public function setInputStream($stream) { if (!is_resource($stream)) { throw new InvalidArgumentException('Input stream must be a valid resource.'); } $this->inputStream = $stream; } /** * Returns the helper's input stream. * * @return resource */ public function getInputStream() { return $this->inputStream; } /** * {@inheritdoc} */ public function getName() { return 'question'; } /** * Asks the question to the user. * * This method is public for PHP 5.3 compatibility, it should be private. * * @param OutputInterface $output * @param Question $question * * @return bool|mixed|null|string * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ public 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; } /** * Outputs the question prompt. * * @param OutputInterface $output * @param Question $question */ 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[] = ' [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value; } $output->writeln($messages); $message = $question->getPrompt(); } $output->write($message); } /** * Outputs an error message. * * @param OutputInterface $output * @param \Exception $error */ 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>'.$error->getMessage().'</error>'; } $output->writeln($message); } /** * Autocompletes a question. * * @param OutputInterface $output * @param Question $question * @param resource $inputStream * * @return string */ 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'); // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) shell_exec('stty -icanon -echo'); // Add highlighted text style $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); // Read a keypress while (!feof($inputStream)) { $c = fread($inputStream, 1); // Backspace Character if ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; // Move cursor backwards $output->write("\033[1D"); } if (0 === $i) { $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); } else { $numMatches = 0; } // Pop the last character off the end of our string $ret = substr($ret, 0, $i); } elseif ("\033" === $c) { // Did we read an escape sequence? $c .= fread($inputStream, 2); // A = Up Arrow. B = Down Arrow 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]; // Echo out remaining chars for current match $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 typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) if (0 === strpos($value, $ret) && $i !== strlen($value)) { $matches[$numMatches++] = $value; } } } // Erase characters from cursor to end of line $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { // Save cursor position $output->write("\0337"); // Write highlighted text $output->write('<hl>'.substr($matches[$ofs], $i).'</hl>'); // Restore cursor position $output->write("\0338"); } } // Reset stty so it behaves normally again shell_exec(sprintf('stty %s', $sttyMode)); return $ret; } /** * Gets a hidden response from user. * * @param OutputInterface $output An Output instance * @param resource $inputStream The handler resource * * @return string The answer * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ private function getHiddenResponse(OutputInterface $output, $inputStream) { if ('\\' === DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; // handle code running from a phar 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 = 'csh' === $shell ? '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.'); } /** * Validates an attempt. * * @param callable $interviewer A callable that will ask for a question and return the result * @param OutputInterface $output An Output instance * @param Question $question A Question instance * * @return mixed The validated response * * @throws \Exception In case the max number of attempts has been reached and no valid response has been given */ private function validateAttempts($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; } /** * Returns a valid unix shell. * * @return string|bool The valid shell name, false in case no valid shell is found */ private function getShell() { if (null !== self::$shell) { return self::$shell; } self::$shell = false; if (file_exists('/usr/bin/env')) { // handle other OSs with bash/zsh/ksh/csh if available to hide the answer $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; } /** * Returns whether Stty is available or not. * * @return bool */ private function hasSttyAvailable() { if (null !== self::$stty) { return self::$stty; } exec('stty 2>&1', $output, $exitcode); return self::$stty = 0 === $exitcode; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Output\OutputInterface; /** * @author Kevin Bond <kevinbond@gmail.com> */ class ProgressIndicator { private $output; private $startTime; private $format; private $message; private $indicatorValues; private $indicatorCurrent; private $indicatorChangeInterval; private $indicatorUpdateTime; private $started = false; private static $formatters; private static $formats; /** * @param OutputInterface $output * @param string|null $format Indicator format * @param int $indicatorChangeInterval Change interval in milliseconds * @param array|null $indicatorValues Animated indicator characters */ public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null) { $this->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(); } /** * Sets the current indicator message. * * @param string|null $message */ public function setMessage($message) { $this->message = $message; $this->display(); } /** * Gets the current indicator message. * * @return string|null * * @internal for PHP 5.3 compatibility */ public function getMessage() { return $this->message; } /** * Gets the progress bar start time. * * @return int The progress bar start time * * @internal for PHP 5.3 compatibility */ public function getStartTime() { return $this->startTime; } /** * Gets the current animated indicator character. * * @return string * * @internal for PHP 5.3 compatibility */ public function getCurrentValue() { return $this->indicatorValues[$this->indicatorCurrent % count($this->indicatorValues)]; } /** * Starts the indicator output. * * @param $message */ 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(); } /** * Advances the indicator. */ 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(); } /** * Finish the indicator with message. * * @param $message */ 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; } /** * Gets the format for a given name. * * @param string $name The format name * * @return string|null A format string */ public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return isset(self::$formats[$name]) ? self::$formats[$name] : null; } /** * Sets a placeholder formatter for a given name. * * This method also allow you to override an existing placeholder. * * @param string $name The placeholder name (including the delimiter char like %) * @param callable $callable A PHP callable */ public static function setPlaceholderFormatterDefinition($name, $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } /** * Gets the placeholder formatter for a given name. * * @param string $name The placeholder name (including the delimiter char like %) * * @return callable|null A PHP 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()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway 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'; } } /** * Overwrites a previous message to the output. * * @param string $message The message */ 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->getCurrentValue(); }, 'message' => function (ProgressIndicator $indicator) { return $indicator->getMessage(); }, 'elapsed' => function (ProgressIndicator $indicator) { return Helper::formatTime(time() - $indicator->getStartTime()); }, '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%)', ); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Formatter\OutputFormatter; /** * The Formatter class provides helpers to format messages. * * @author Fabien Potencier <fabien@symfony.com> */ class FormatterHelper extends Helper { /** * Formats a message within a section. * * @param string $section The section name * @param string $message The message * @param string $style The style to apply to the section * * @return string The format section */ public function formatSection($section, $message, $style = 'info') { return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * @param string $style The style to apply to the whole block * @param bool $large Whether to return a large block * * @return string The formatter 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</%s>', $style, $messages[$i], $style); } return implode("\n", $messages); } /** * {@inheritdoc} */ public function getName() { return 'formatter'; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatterStyle; /** * The Dialog class provides helpers to interact with the user. * * @author Fabien Potencier <fabien@symfony.com> * * @deprecated since version 2.5, to be removed in 3.0. * Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead. */ class DialogHelper extends InputAwareHelper { private $inputStream; private static $shell; private static $stty; public function __construct($triggerDeprecationError = true) { if ($triggerDeprecationError) { @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); } } /** * Asks the user to select a value. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param array $choices List of choices to pick from * @param bool|string $default The default answer if the user enters nothing * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked * @param bool $multiselect Select more than one value separated by comma * * @return int|string|array The selected value or values (the key of the choices array) * * @throws InvalidArgumentException */ public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $width = max(array_map('strlen', array_keys($choices))); $messages = (array) $question; foreach ($choices as $key => $value) { $messages[] = sprintf(" [<info>%-{$width}s</info>] %s", $key, $value); } $output->writeln($messages); $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) { // Collapse all spaces. $selectedChoices = str_replace(' ', '', $picked); if ($multiselect) { // Check for a separated comma values if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $picked)); } $selectedChoices = explode(',', $selectedChoices); } else { $selectedChoices = array($picked); } $multiselectChoices = array(); foreach ($selectedChoices as $value) { if (empty($choices[$value])) { throw new InvalidArgumentException(sprintf($errorMessage, $value)); } $multiselectChoices[] = $value; } if ($multiselect) { return $multiselectChoices; } return $picked; }, $attempts, $default); return $result; } /** * Asks a question to the user. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param string $default The default answer if none is given by the user * @param array $autocomplete List of values to autocomplete * * @return string The user answer * * @throws RuntimeException If there is no data to read in the input stream */ public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null) { if ($this->input && !$this->input->isInteractive()) { return $default; } if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $output->write($question); $inputStream = $this->inputStream ?: STDIN; if (null === $autocomplete || !$this->hasSttyAvailable()) { $ret = fgets($inputStream, 4096); if (false === $ret) { throw new RuntimeException('Aborted'); } $ret = trim($ret); } else { $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); $sttyMode = shell_exec('stty -g'); // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) shell_exec('stty -icanon -echo'); // Add highlighted text style $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); // Read a keypress while (!feof($inputStream)) { $c = fread($inputStream, 1); // Backspace Character if ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; // Move cursor backwards $output->write("\033[1D"); } if (0 === $i) { $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); } else { $numMatches = 0; } // Pop the last character off the end of our string $ret = substr($ret, 0, $i); } elseif ("\033" === $c) { // Did we read an escape sequence? $c .= fread($inputStream, 2); // A = Up Arrow. B = Down Arrow 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]; // Echo out remaining chars for current match $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 typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) if (0 === strpos($value, $ret) && $i !== strlen($value)) { $matches[$numMatches++] = $value; } } } // Erase characters from cursor to end of line $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { // Save cursor position $output->write("\0337"); // Write highlighted text $output->write('<hl>'.substr($matches[$ofs], $i).'</hl>'); // Restore cursor position $output->write("\0338"); } } // Reset stty so it behaves normally again shell_exec(sprintf('stty %s', $sttyMode)); } return strlen($ret) > 0 ? $ret : $default; } /** * Asks a confirmation to the user. * * The question will be asked until the user answers by nothing, yes, or no. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param bool $default The default answer if the user enters nothing * * @return bool true if the user has confirmed, false otherwise */ public function askConfirmation(OutputInterface $output, $question, $default = true) { $answer = 'z'; while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) { $answer = $this->ask($output, $question); } if (false === $default) { return $answer && 'y' == strtolower($answer[0]); } return !$answer || 'y' == strtolower($answer[0]); } /** * Asks a question to the user, the response is hidden. * * @param OutputInterface $output An Output instance * @param string|array $question The question * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not * * @return string The answer * * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden */ public function askHiddenResponse(OutputInterface $output, $question, $fallback = true) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } if ('\\' === DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; // handle code running from a phar if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; } $output->write($question); $value = rtrim(shell_exec($exe)); $output->writeln(''); if (isset($tmpExe)) { unlink($tmpExe); } return $value; } if ($this->hasSttyAvailable()) { $output->write($question); $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); $value = fgets($this->inputStream ?: STDIN, 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()) { $output->write($question); $readCmd = 'csh' === $shell ? '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; } if ($fallback) { return $this->ask($output, $question); } throw new RuntimeException('Unable to hide the response'); } /** * Asks for a value and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param callable $validator A PHP callback * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $default The default answer if none is given by the user * @param array $autocomplete List of values to autocomplete * * @return mixed * * @throws \Exception When any of the validators return an error */ public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null) { $that = $this; $interviewer = function () use ($output, $question, $default, $autocomplete, $that) { return $that->ask($output, $question, $default, $autocomplete); }; return $this->validateAttempts($interviewer, $output, $validator, $attempts); } /** * Asks for a value, hide and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param callable $validator A PHP callback * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not * * @return string The response * * @throws \Exception When any of the validators return an error * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden */ public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true) { $that = $this; $interviewer = function () use ($output, $question, $fallback, $that) { return $that->askHiddenResponse($output, $question, $fallback); }; return $this->validateAttempts($interviewer, $output, $validator, $attempts); } /** * Sets the input stream to read from when interacting with the user. * * This is mainly useful for testing purpose. * * @param resource $stream The input stream */ public function setInputStream($stream) { $this->inputStream = $stream; } /** * Returns the helper's input stream. * * @return resource|null The input stream or null if the default STDIN is used */ public function getInputStream() { return $this->inputStream; } /** * {@inheritdoc} */ public function getName() { return 'dialog'; } /** * Return a valid Unix shell. * * @return string|bool The valid shell name, false in case no valid shell is found */ private function getShell() { if (null !== self::$shell) { return self::$shell; } self::$shell = false; if (file_exists('/usr/bin/env')) { // handle other OSs with bash/zsh/ksh/csh if available to hide the answer $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 = 0 === $exitcode; } /** * Validate an attempt. * * @param callable $interviewer A callable that will ask for a question and return the result * @param OutputInterface $output An Output instance * @param callable $validator A PHP callback * @param int|false $attempts Max number of times to ask before giving up; false will ask infinitely * * @return string The validated response * * @throws \Exception In case the max number of attempts has been reached and no valid response has been given */ private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $e = null; while (false === $attempts || $attempts--) { if (null !== $e) { $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error')); } try { return call_user_func($validator, $interviewer()); } catch (\Exception $e) { } } throw $e; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * HelperSet represents a set of helpers to be used with a command. * * @author Fabien Potencier <fabien@symfony.com> */ class HelperSet implements \IteratorAggregate { /** * @var Helper[] */ private $helpers = array(); private $command; /** * @param Helper[] $helpers An array of helper */ public function __construct(array $helpers = array()) { foreach ($helpers as $alias => $helper) { $this->set($helper, is_int($alias) ? null : $alias); } } /** * Sets a helper. * * @param HelperInterface $helper The helper instance * @param string $alias An alias */ public function set(HelperInterface $helper, $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } /** * Returns true if the helper if defined. * * @param string $name The helper name * * @return bool true if the helper is defined, false otherwise */ public function has($name) { return isset($this->helpers[$name]); } /** * Gets a helper value. * * @param string $name The helper name * * @return HelperInterface The helper instance * * @throws InvalidArgumentException if the helper is not defined */ public function get($name) { if (!$this->has($name)) { throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); } if ('dialog' === $name && $this->helpers[$name] instanceof DialogHelper) { @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); } elseif ('progress' === $name && $this->helpers[$name] instanceof ProgressHelper) { @trigger_error('"Symfony\Component\Console\Helper\ProgressHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\ProgressBar" instead.', E_USER_DEPRECATED); } elseif ('table' === $name && $this->helpers[$name] instanceof TableHelper) { @trigger_error('"Symfony\Component\Console\Helper\TableHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\Table" instead.', E_USER_DEPRECATED); } return $this->helpers[$name]; } /** * Sets the command associated with this helper set. * * @param Command $command A Command instance */ public function setCommand(Command $command = null) { $this->command = $command; } /** * Gets the command associated with this helper set. * * @return Command A Command instance */ public function getCommand() { return $this->command; } /** * @return Helper[] */ public function getIterator() { return new \ArrayIterator($this->helpers); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Provides helpers to display table output. * * @author Саша Стаменковић <umpirsky@gmail.com> * @author Fabien Potencier <fabien@symfony.com> * * @deprecated since version 2.5, to be removed in 3.0 * Use {@link Table} instead. */ class TableHelper extends Helper { const LAYOUT_DEFAULT = 0; const LAYOUT_BORDERLESS = 1; const LAYOUT_COMPACT = 2; /** * @var Table */ private $table; public function __construct($triggerDeprecationError = true) { if ($triggerDeprecationError) { @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\Table class instead.', E_USER_DEPRECATED); } $this->table = new Table(new NullOutput()); } /** * Sets table layout type. * * @param int $layout self::LAYOUT_* * * @return $this * * @throws InvalidArgumentException when the table layout is not known */ public function setLayout($layout) { switch ($layout) { case self::LAYOUT_BORDERLESS: $this->table->setStyle('borderless'); break; case self::LAYOUT_COMPACT: $this->table->setStyle('compact'); break; case self::LAYOUT_DEFAULT: $this->table->setStyle('default'); break; default: throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); } return $this; } public function setHeaders(array $headers) { $this->table->setHeaders($headers); return $this; } public function setRows(array $rows) { $this->table->setRows($rows); return $this; } public function addRows(array $rows) { $this->table->addRows($rows); return $this; } public function addRow(array $row) { $this->table->addRow($row); return $this; } public function setRow($column, array $row) { $this->table->setRow($column, $row); return $this; } /** * Sets padding character, used for cell padding. * * @param string $paddingChar * * @return $this */ public function setPaddingChar($paddingChar) { $this->table->getStyle()->setPaddingChar($paddingChar); return $this; } /** * Sets horizontal border character. * * @param string $horizontalBorderChar * * @return $this */ public function setHorizontalBorderChar($horizontalBorderChar) { $this->table->getStyle()->setHorizontalBorderChar($horizontalBorderChar); return $this; } /** * Sets vertical border character. * * @param string $verticalBorderChar * * @return $this */ public function setVerticalBorderChar($verticalBorderChar) { $this->table->getStyle()->setVerticalBorderChar($verticalBorderChar); return $this; } /** * Sets crossing character. * * @param string $crossingChar * * @return $this */ public function setCrossingChar($crossingChar) { $this->table->getStyle()->setCrossingChar($crossingChar); return $this; } /** * Sets header cell format. * * @param string $cellHeaderFormat * * @return $this */ public function setCellHeaderFormat($cellHeaderFormat) { $this->table->getStyle()->setCellHeaderFormat($cellHeaderFormat); return $this; } /** * Sets row cell format. * * @param string $cellRowFormat * * @return $this */ public function setCellRowFormat($cellRowFormat) { $this->table->getStyle()->setCellHeaderFormat($cellRowFormat); return $this; } /** * Sets row cell content format. * * @param string $cellRowContentFormat * * @return $this */ public function setCellRowContentFormat($cellRowContentFormat) { $this->table->getStyle()->setCellRowContentFormat($cellRowContentFormat); return $this; } /** * Sets table border format. * * @param string $borderFormat * * @return $this */ public function setBorderFormat($borderFormat) { $this->table->getStyle()->setBorderFormat($borderFormat); return $this; } /** * Sets cell padding type. * * @param int $padType STR_PAD_* * * @return $this */ public function setPadType($padType) { $this->table->getStyle()->setPadType($padType); return $this; } /** * Renders table to output. * * Example: * +---------------+-----------------------+------------------+ * | ISBN | Title | Author | * +---------------+-----------------------+------------------+ * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ * * @param OutputInterface $output */ public function render(OutputInterface $output) { $p = new \ReflectionProperty($this->table, 'output'); $p->setAccessible(true); $p->setValue($this->table, $output); $this->table->render(); } /** * {@inheritdoc} */ public function getName() { return 'table'; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputAwareInterface; /** * An implementation of InputAwareInterface for Helpers. * * @author Wouter J <waldio.webdesign@gmail.com> */ abstract class InputAwareHelper extends Helper implements InputAwareInterface { protected $input; /** * {@inheritdoc} */ public function setInput(InputInterface $input) { $this->input = $input; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Exception\LogicException; /** * The Progress class provides helpers to display progress output. * * @author Chris Jones <leeked@gmail.com> * @author Fabien Potencier <fabien@symfony.com> * * @deprecated since version 2.5, to be removed in 3.0 * Use {@link ProgressBar} instead. */ class ProgressHelper extends Helper { const FORMAT_QUIET = ' %percent%%'; const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%'; const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%'; const FORMAT_QUIET_NOMAX = ' %current%'; const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]'; const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%'; // options private $barWidth = 28; private $barChar = '='; private $emptyBarChar = '-'; private $progressChar = '>'; private $format = null; private $redrawFreq = 1; private $lastMessagesLength; private $barCharOriginal; /** * @var OutputInterface */ private $output; /** * Current step. * * @var int */ private $current; /** * Maximum number of steps. * * @var int */ private $max; /** * Start time of the progress bar. * * @var int */ private $startTime; /** * List of formatting variables. * * @var array */ private $defaultFormatVars = array( 'current', 'max', 'bar', 'percent', 'elapsed', ); /** * Available formatting variables. * * @var array */ private $formatVars; /** * Stored format part widths (used for padding). * * @var array */ private $widths = array( 'current' => 4, 'max' => 4, 'percent' => 3, 'elapsed' => 6, ); /** * Various time formats. * * @var array */ private $timeFormats = array( array(0, '???'), array(2, '1 sec'), array(59, 'secs', 1), array(60, '1 min'), array(3600, 'mins', 60), array(5400, '1 hr'), array(86400, 'hrs', 3600), array(129600, '1 day'), array(604800, 'days', 86400), ); public function __construct($triggerDeprecationError = true) { if ($triggerDeprecationError) { @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED); } } /** * Sets the progress bar width. * * @param int $size The progress bar size */ public function setBarWidth($size) { $this->barWidth = (int) $size; } /** * Sets the bar character. * * @param string $char A character */ public function setBarCharacter($char) { $this->barChar = $char; } /** * Sets the empty bar character. * * @param string $char A character */ public function setEmptyBarCharacter($char) { $this->emptyBarChar = $char; } /** * Sets the progress bar character. * * @param string $char A character */ public function setProgressCharacter($char) { $this->progressChar = $char; } /** * Sets the progress bar format. * * @param string $format The format */ public function setFormat($format) { $this->format = $format; } /** * Sets the redraw frequency. * * @param int $freq The frequency in steps */ public function setRedrawFrequency($freq) { $this->redrawFreq = (int) $freq; } /** * Starts the progress output. * * @param OutputInterface $output An Output instance * @param int|null $max Maximum steps */ public function start(OutputInterface $output, $max = null) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->startTime = time(); $this->current = 0; $this->max = (int) $max; // Disabling output when it does not support ANSI codes as it would result in a broken display anyway. $this->output = $output->isDecorated() ? $output : new NullOutput(); $this->lastMessagesLength = 0; $this->barCharOriginal = ''; if (null === $this->format) { switch ($output->getVerbosity()) { case OutputInterface::VERBOSITY_QUIET: $this->format = self::FORMAT_QUIET_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_QUIET; } break; case OutputInterface::VERBOSITY_VERBOSE: case OutputInterface::VERBOSITY_VERY_VERBOSE: case OutputInterface::VERBOSITY_DEBUG: $this->format = self::FORMAT_VERBOSE_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_VERBOSE; } break; default: $this->format = self::FORMAT_NORMAL_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_NORMAL; } break; } } $this->initialize(); } /** * Advances the progress output X steps. * * @param int $step Number of steps to advance * @param bool $redraw Whether to redraw or not * * @throws LogicException */ public function advance($step = 1, $redraw = false) { $this->setCurrent($this->current + $step, $redraw); } /** * Sets the current progress. * * @param int $current The current progress * @param bool $redraw Whether to redraw or not * * @throws LogicException */ public function setCurrent($current, $redraw = false) { if (null === $this->startTime) { throw new LogicException('You must start the progress bar before calling setCurrent().'); } $current = (int) $current; if ($current < $this->current) { throw new LogicException('You can\'t regress the progress bar'); } if (0 === $this->current) { $redraw = true; } $prevPeriod = (int) ($this->current / $this->redrawFreq); $this->current = $current; $currPeriod = (int) ($this->current / $this->redrawFreq); if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) { $this->display(); } } /** * Outputs the current progress string. * * @param bool $finish Forces the end result * * @throws LogicException */ public function display($finish = false) { if (null === $this->startTime) { throw new LogicException('You must start the progress bar before calling display().'); } $message = $this->format; foreach ($this->generate($finish) as $name => $value) { $message = str_replace("%{$name}%", $value, $message); } $this->overwrite($this->output, $message); } /** * Removes the progress bar from the current line. * * This is useful if you wish to write some output * while a progress bar is running. * Call display() to show the progress bar again. */ public function clear() { $this->overwrite($this->output, ''); } /** * Finishes the progress output. */ public function finish() { if (null === $this->startTime) { throw new LogicException('You must start the progress bar before calling finish().'); } if (null !== $this->startTime) { if (!$this->max) { $this->barChar = $this->barCharOriginal; $this->display(true); } $this->startTime = null; $this->output->writeln(''); $this->output = null; } } /** * Initializes the progress helper. */ private function initialize() { $this->formatVars = array(); foreach ($this->defaultFormatVars as $var) { if (false !== strpos($this->format, "%{$var}%")) { $this->formatVars[$var] = true; } } if ($this->max > 0) { $this->widths['max'] = $this->strlen($this->max); $this->widths['current'] = $this->widths['max']; } else { $this->barCharOriginal = $this->barChar; $this->barChar = $this->emptyBarChar; } } /** * Generates the array map of format variables to values. * * @param bool $finish Forces the end result * * @return array Array of format vars and values */ private function generate($finish = false) { $vars = array(); $percent = 0; if ($this->max > 0) { $percent = (float) $this->current / $this->max; } if (isset($this->formatVars['bar'])) { if ($this->max > 0) { $completeBars = floor($percent * $this->barWidth); } else { if (!$finish) { $completeBars = floor($this->current % $this->barWidth); } else { $completeBars = $this->barWidth; } } $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar); $bar = str_repeat($this->barChar, $completeBars); if ($completeBars < $this->barWidth) { $bar .= $this->progressChar; $bar .= str_repeat($this->emptyBarChar, $emptyBars); } $vars['bar'] = $bar; } if (isset($this->formatVars['elapsed'])) { $elapsed = time() - $this->startTime; $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT); } if (isset($this->formatVars['current'])) { $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT); } if (isset($this->formatVars['max'])) { $vars['max'] = $this->max; } if (isset($this->formatVars['percent'])) { $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT); } return $vars; } /** * Converts seconds into human-readable format. * * @param int $secs Number of seconds * * @return string Time in readable format */ private function humaneTime($secs) { $text = ''; foreach ($this->timeFormats as $format) { if ($secs < $format[0]) { if (2 == count($format)) { $text = $format[1]; break; } else { $text = ceil($secs / $format[2]).' '.$format[1]; break; } } } return $text; } /** * Overwrites a previous message to the output. * * @param OutputInterface $output An Output instance * @param string $message The message */ private function overwrite(OutputInterface $output, $message) { $length = $this->strlen($message); // append whitespace to match the last line's length if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); } // carriage return $output->write("\x0D"); $output->write($message); $this->lastMessagesLength = $this->strlen($message); } /** * {@inheritdoc} */ public function getName() { return 'progress'; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; use Symfony\Component\Process\ProcessBuilder; /** * The ProcessHelper class provides helpers to run external processes. * * @author Fabien Potencier <fabien@symfony.com> */ class ProcessHelper extends Helper { /** * Runs an external process. * * @param OutputInterface $output An OutputInterface instance * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run * @param string|null $error An error message that must be displayed if something went wrong * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * @param int $verbosity The threshold for verbosity * * @return Process The process that ran */ public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) { if ($output instanceof ConsoleOutputInterface) { $output = $output->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('<error>%s</error>', $this->escapeString($error))); } return $process; } /** * Runs the process. * * This is identical to run() except that an exception is thrown if the process * exits with a non-zero exit code. * * @param OutputInterface $output An OutputInterface instance * @param string|Process $cmd An instance of Process or a command to run * @param string|null $error An error message that must be displayed if something went wrong * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @return Process The process that ran * * @throws ProcessFailedException * * @see run() */ public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) { $process = $this->run($output, $cmd, $error, $callback); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process; } /** * Wraps a Process callback to add debugging output. * * @param OutputInterface $output An OutputInterface interface * @param Process $process The Process * @param callable|null $callback A PHP callable * * @return callable */ public function wrapCallback(OutputInterface $output, Process $process, $callback = null) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); $that = $this; return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) { $output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type)); if (null !== $callback) { call_user_func($callback, $type, $buffer); } }; } /** * This method is public for PHP 5.3 compatibility, it should be private. * * @internal */ public function escapeString($str) { return str_replace('<', '\\<', $str); } /** * {@inheritdoc} */ public function getName() { return 'process'; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Provides helpers to display a table. * * @author Fabien Potencier <fabien@symfony.com> * @author Саша Стаменковић <umpirsky@gmail.com> * @author Abdellatif Ait boudad <a.aitboudad@gmail.com> * @author Max Grigorian <maxakawizard@gmail.com> */ class Table { /** * Table headers. * * @var array */ private $headers = array(); /** * Table rows. * * @var array */ private $rows = array(); /** * Column widths cache. * * @var array */ private $columnWidths = array(); /** * Number of columns cache. * * @var array */ private $numberOfColumns; /** * @var OutputInterface */ private $output; /** * @var TableStyle */ private $style; /** * @var array */ private $columnStyles = array(); private static $styles; public function __construct(OutputInterface $output) { $this->output = $output; if (!self::$styles) { self::$styles = self::initStyles(); } $this->setStyle('default'); } /** * Sets a style definition. * * @param string $name The style name * @param TableStyle $style A TableStyle instance */ public static function setStyleDefinition($name, TableStyle $style) { if (!self::$styles) { self::$styles = self::initStyles(); } self::$styles[$name] = $style; } /** * Gets a style definition by name. * * @param string $name The style name * * @return TableStyle */ 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)); } /** * Sets table style. * * @param TableStyle|string $name The style name or a TableStyle instance * * @return $this */ public function setStyle($name) { $this->style = $this->resolveStyle($name); return $this; } /** * Gets the current table style. * * @return TableStyle */ public function getStyle() { return $this->style; } /** * Sets table column style. * * @param int $columnIndex Column index * @param TableStyle|string $name The style name or a TableStyle instance * * @return $this */ public function setColumnStyle($columnIndex, $name) { $columnIndex = (int) $columnIndex; $this->columnStyles[$columnIndex] = $this->resolveStyle($name); return $this; } /** * Gets the current style for a column. * * If style was not set, it returns the global table style. * * @param int $columnIndex Column index * * @return TableStyle */ public function getColumnStyle($columnIndex) { if (isset($this->columnStyles[$columnIndex])) { return $this->columnStyles[$columnIndex]; } return $this->getStyle(); } 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; } /** * Renders table to output. * * Example: * +---------------+-----------------------+------------------+ * | ISBN | Title | Author | * +---------------+-----------------------+------------------+ * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ */ 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(); } /** * Renders horizontal header separator. * * Example: +-----+-----------+-------+ */ 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->columnWidths[$column]).$this->style->getCrossingChar(); } $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); } /** * Renders vertical column separator. */ private function renderColumnSeparator() { return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); } /** * Renders table row. * * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * * @param array $row * @param string $cellFormat */ 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); } /** * Renders table cell with padding. * * @param array $row * @param int $column * @param string $cellFormat */ private function renderCell(array $row, $column, $cellFormat) { $cell = isset($row[$column]) ? $row[$column] : ''; $width = $this->columnWidths[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { // add the width of the following columns(numbers of colspan). foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { $width += $this->getColumnSeparatorWidth() + $this->columnWidths[$nextColumn]; } } // str_pad won't work properly with multi-byte strings, we need to fix the padding 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())); } /** * Calculate number of columns for this table. */ 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); // Remove any new line breaks and replace it with a new line foreach ($rows[$rowKey] as $column => $cell) { if (!strstr($cell, "\n")) { continue; } $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\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; } /** * fill rows that contains rowspan > 1. * * @param array $rows * @param int $line * * @return array */ 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", "<fg=default;bg=default>\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]); } // create a two dimensional array (rowspan x colspan) $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) { // we need to know if $unmergedRow will be merged or inserted into $rows if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { // insert cell into row at cellKey position 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; } /** * fill cells for a row that contains colspan > 1. * * @param array $row * * @return array */ 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) { // insert empty value at column position $newRow[] = ''; } } } return $newRow ?: $row; } /** * @param array $rows * @param int $line * * @return array */ 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; } /** * Gets number of columns by row. * * @param array $row * * @return int */ private function getNumberOfColumns(array $row) { $columns = count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; } return $columns; } /** * Gets list of columns for the given row. * * @param array $row * * @return array */ private function getRowColumns($row) { $columns = range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { if ($cell instanceof TableCell && $cell->getColspan() > 1) { // exclude grouped columns. $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); } } return $columns; } /** * Calculates columns widths. * * @param array $rows */ 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->columnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; } } /** * Gets column width. * * @return int */ private function getColumnSeparatorWidth() { return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); } /** * Gets cell width. * * @param array $row * @param int $column * * @return int */ private function getCellWidth(array $row, $column) { if (isset($row[$column])) { $cell = $row[$column]; $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); return $cellWidth; } return 0; } /** * Called after rendering to cleanup cache data. */ private function cleanup() { $this->columnWidths = 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)); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Descriptor\JsonDescriptor; use Symfony\Component\Console\Descriptor\MarkdownDescriptor; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * This class adds helper method to describe objects in various formats. * * @author Jean-François Simon <contact@jfsimon.fr> */ class DescriptorHelper extends Helper { /** * @var DescriptorInterface[] */ private $descriptors = array(); public function __construct() { $this ->register('txt', new TextDescriptor()) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) ; } /** * Describes an object if supported. * * Available options are: * * format: string, the output format name * * raw_text: boolean, sets output type as raw * * @param OutputInterface $output * @param object $object * @param array $options * * @throws InvalidArgumentException when the given format is not supported */ 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); } /** * Registers a descriptor. * * @param string $format * @param DescriptorInterface $descriptor * * @return $this */ public function register($format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } /** * {@inheritdoc} */ public function getName() { return 'descriptor'; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * Marks a row as being a separator. * * @author Fabien Potencier <fabien@symfony.com> */ class TableSeparator extends TableCell { /** * @param array $options */ public function __construct(array $options = array()) { parent::__construct('', $options); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * @author Abdellatif Ait boudad <a.aitboudad@gmail.com> */ class TableCell { /** * @var string */ private $value; /** * @var array */ private $options = array( 'rowspan' => 1, 'colspan' => 1, ); /** * @param string $value * @param array $options */ public function __construct($value = '', array $options = array()) { if (is_numeric($value) && !is_string($value)) { $value = (string) $value; } $this->value = $value; // check option names 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); } /** * Returns the cell value. * * @return string */ public function __toString() { return $this->value; } /** * Gets number of colspan. * * @return int */ public function getColspan() { return (int) $this->options['colspan']; } /** * Gets number of rowspan. * * @return int */ public function getRowspan() { return (int) $this->options['rowspan']; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * Helper is the base class for all helper classes. * * @author Fabien Potencier <fabien@symfony.com> */ abstract class Helper implements HelperInterface { protected $helperSet = null; /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet = null) { $this->helperSet = $helperSet; } /** * Gets the helper set associated with this helper. * * @return HelperSet|null */ public function getHelperSet() { return $this->helperSet; } /** * Returns the length of a string, using mb_strwidth if it is available. * * @param string $string The string to check its length * * @return int The length of the string */ 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); // remove <...> formatting $string = $formatter->format($string); // remove already formatted characters $string = preg_replace("/\033\[[^m]*m/", '', $string); $formatter->setDecorated($isDecorated); return $string; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Formatter\OutputFormatter; /** * Symfony Style Guide compliant question helper. * * @author Kevin Bond <kevinbond@gmail.com> */ class SymfonyQuestionHelper extends QuestionHelper { /** * {@inheritdoc} */ public function ask(InputInterface $input, OutputInterface $output, Question $question) { $validator = $question->getValidator(); $question->setValidator(function ($value) use ($validator) { if (null !== $validator) { $value = $validator($value); } else { // make required 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); } /** * {@inheritdoc} */ protected function writePrompt(OutputInterface $output, Question $question) { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); $default = $question->getDefault(); switch (true) { case null === $default: $text = sprintf(' <info>%s</info>:', $text); break; case $question instanceof ConfirmationQuestion: $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $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(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default))); break; case $question instanceof ChoiceQuestion: $choices = $question->getChoices(); $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default])); break; default: $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $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(" [<comment>%-${width}s</comment>] %s", $key, $value)); } } $output->write(' > '); } /** * {@inheritdoc} */ protected function writeError(OutputInterface $output, \Exception $error) { if ($output instanceof SymfonyStyle) { $output->newLine(); $output->error($error->getMessage()); return; } parent::writeError($output, $error); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Defines the styles for a Table. * * @author Fabien Potencier <fabien@symfony.com> * @author Саша Стаменковић <umpirsky@gmail.com> */ class TableStyle { private $paddingChar = ' '; private $horizontalBorderChar = '-'; private $verticalBorderChar = '|'; private $crossingChar = '+'; private $cellHeaderFormat = '<info>%s</info>'; private $cellRowFormat = '%s'; private $cellRowContentFormat = ' %s '; private $borderFormat = '%s'; private $padType = STR_PAD_RIGHT; /** * Sets padding character, used for cell padding. * * @param string $paddingChar * * @return $this */ public function setPaddingChar($paddingChar) { if (!$paddingChar) { throw new LogicException('The padding char must not be empty'); } $this->paddingChar = $paddingChar; return $this; } /** * Gets padding character, used for cell padding. * * @return string */ public function getPaddingChar() { return $this->paddingChar; } /** * Sets horizontal border character. * * @param string $horizontalBorderChar * * @return $this */ public function setHorizontalBorderChar($horizontalBorderChar) { $this->horizontalBorderChar = $horizontalBorderChar; return $this; } /** * Gets horizontal border character. * * @return string */ public function getHorizontalBorderChar() { return $this->horizontalBorderChar; } /** * Sets vertical border character. * * @param string $verticalBorderChar * * @return $this */ public function setVerticalBorderChar($verticalBorderChar) { $this->verticalBorderChar = $verticalBorderChar; return $this; } /** * Gets vertical border character. * * @return string */ public function getVerticalBorderChar() { return $this->verticalBorderChar; } /** * Sets crossing character. * * @param string $crossingChar * * @return $this */ public function setCrossingChar($crossingChar) { $this->crossingChar = $crossingChar; return $this; } /** * Gets crossing character. * * @return string $crossingChar */ public function getCrossingChar() { return $this->crossingChar; } /** * Sets header cell format. * * @param string $cellHeaderFormat * * @return $this */ public function setCellHeaderFormat($cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } /** * Gets header cell format. * * @return string */ public function getCellHeaderFormat() { return $this->cellHeaderFormat; } /** * Sets row cell format. * * @param string $cellRowFormat * * @return $this */ public function setCellRowFormat($cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } /** * Gets row cell format. * * @return string */ public function getCellRowFormat() { return $this->cellRowFormat; } /** * Sets row cell content format. * * @param string $cellRowContentFormat * * @return $this */ public function setCellRowContentFormat($cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } /** * Gets row cell content format. * * @return string */ public function getCellRowContentFormat() { return $this->cellRowContentFormat; } /** * Sets table border format. * * @param string $borderFormat * * @return $this */ public function setBorderFormat($borderFormat) { $this->borderFormat = $borderFormat; return $this; } /** * Gets table border format. * * @return string */ public function getBorderFormat() { return $this->borderFormat; } /** * Sets cell padding type. * * @param int $padType STR_PAD_* * * @return $this */ 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; } /** * Gets cell padding type. * * @return int */ public function getPadType() { return $this->padType; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * HelperInterface is the interface all helpers must implement. * * @author Fabien Potencier <fabien@symfony.com> */ interface HelperInterface { /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet = null); /** * Gets the helper set associated with this helper. * * @return HelperSet A HelperSet instance */ public function getHelperSet(); /** * Returns the canonical name of this helper. * * @return string The canonical name */ public function getName(); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * Helps outputting debug information when running an external program from a command. * * An external program can be a Process, an HTTP request, or anything else. * * @author Fabien Potencier <fabien@symfony.com> */ class DebugFormatterHelper extends Helper { private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'); private $started = array(); private $count = -1; /** * Starts a debug formatting session. * * @param string $id The id of the formatting session * @param string $message The message to display * @param string $prefix The prefix to use * * @return string */ public function start($id, $message, $prefix = 'RUN') { $this->started[$id] = array('border' => ++$this->count % count($this->colors)); return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message); } /** * Adds progress to a formatting session. * * @param string $id The id of the formatting session * @param string $buffer The message to display * @param bool $error Whether to consider the buffer as error * @param string $prefix The prefix for output * @param string $errorPrefix The prefix for error output * * @return string */ 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<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = true; } $message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %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<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix); $this->started[$id]['out'] = true; } $message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer); } return $message; } /** * Stops a formatting session. * * @param string $id The id of the formatting session * @param string $message The message to display * @param bool $successful Whether to consider the result as success * @param string $prefix The prefix for the end output * * @return string */ 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<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } $message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message); unset($this->started[$id]['out'], $this->started[$id]['err']); return $message; } /** * @param string $id The id of the formatting session * * @return string */ private function getBorder($id) { return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]); } /** * {@inheritdoc} */ public function getName() { return 'debug_formatter'; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * StreamOutput writes the output to a given stream. * * Usage: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * As `StreamOutput` can use any stream, you can also use a file: * * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); * * @author Fabien Potencier <fabien@symfony.com> */ class StreamOutput extends Output { private $stream; /** * @param resource $stream A stream resource * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @throws InvalidArgumentException When first argument is not a real stream */ public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) { if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); } $this->stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } /** * Gets the stream attached to this StreamOutput instance. * * @return resource A stream resource */ public function getStream() { return $this->stream; } /** * {@inheritdoc} */ protected function doWrite($message, $newline) { if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) { // should never happen throw new RuntimeException('Unable to write output.'); } fflush($this->stream); } /** * Returns true if the stream supports colorization. * * Colorization is disabled if not supported by the stream: * * - Windows != 10.0.10586 without Ansicon, ConEmu or Mintty * - non tty consoles * * @return bool true if the stream supports colorization, false otherwise */ 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); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. * This adds information about stderr output stream. * * @author Dariusz Górecki <darek.krk@gmail.com> */ interface ConsoleOutputInterface extends OutputInterface { /** * Gets the OutputInterface for errors. * * @return OutputInterface */ public function getErrorOutput(); /** * Sets the OutputInterface used for errors. * * @param OutputInterface $error */ public function setErrorOutput(OutputInterface $error); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Formatter\OutputFormatter; /** * Base class for output classes. * * There are five levels of verbosity: * * * normal: no option passed (normal output) * * verbose: -v (more output) * * very verbose: -vv (highly extended output) * * debug: -vvv (all debug output) * * quiet: -q (no output) * * @author Fabien Potencier <fabien@symfony.com> */ abstract class Output implements OutputInterface { private $verbosity; private $formatter; /** * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool $decorated Whether to decorate messages * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) { $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; $this->formatter = $formatter ?: new OutputFormatter(); $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } /** * {@inheritdoc} */ public function getFormatter() { return $this->formatter; } /** * {@inheritdoc} */ public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated() { return $this->formatter->isDecorated(); } /** * {@inheritdoc} */ public function setVerbosity($level) { $this->verbosity = (int) $level; } /** * {@inheritdoc} */ public function getVerbosity() { return $this->verbosity; } /** * {@inheritdoc} */ public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } /** * {@inheritdoc} */ public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } /** * {@inheritdoc} */ public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } /** * {@inheritdoc} */ public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } /** * {@inheritdoc} */ public function writeln($messages, $options = self::OUTPUT_NORMAL) { $this->write($messages, true, $options); } /** * {@inheritdoc} */ 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); } } /** * Writes a message to the output. * * @param string $message A message to write to the output * @param bool $newline Whether to add a newline or not */ abstract protected function doWrite($message, $newline); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * OutputInterface is the interface implemented by all Output classes. * * @author Fabien Potencier <fabien@symfony.com> */ interface OutputInterface { const VERBOSITY_QUIET = 16; const VERBOSITY_NORMAL = 32; const VERBOSITY_VERBOSE = 64; const VERBOSITY_VERY_VERBOSE = 128; const VERBOSITY_DEBUG = 256; const OUTPUT_NORMAL = 1; const OUTPUT_RAW = 2; const OUTPUT_PLAIN = 4; /** * Writes a message to the output. * * @param string|array $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL */ public function write($messages, $newline = false, $options = 0); /** * Writes a message to the output and adds a newline at the end. * * @param string|array $messages The message as an array of lines of a single string * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL */ public function writeln($messages, $options = 0); /** * Sets the verbosity of the output. * * @param int $level The level of verbosity (one of the VERBOSITY constants) */ public function setVerbosity($level); /** * Gets the current verbosity of the output. * * @return int The current level of verbosity (one of the VERBOSITY constants) */ public function getVerbosity(); /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages */ public function setDecorated($decorated); /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise */ public function isDecorated(); /** * Sets output formatter. * * @param OutputFormatterInterface $formatter */ public function setFormatter(OutputFormatterInterface $formatter); /** * Returns current output formatter instance. * * @return OutputFormatterInterface */ public function getFormatter(); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * ConsoleOutput is the default class for all CLI output. It uses STDOUT. * * This class is a convenient wrapper around `StreamOutput`. * * $output = new ConsoleOutput(); * * This is equivalent to: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * @author Fabien Potencier <fabien@symfony.com> */ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface { /** * @var StreamOutput */ private $stderr; /** * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) { parent::__construct($this->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()); } } /** * {@inheritdoc} */ public function setDecorated($decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } /** * {@inheritdoc} */ public function setVerbosity($level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } /** * {@inheritdoc} */ public function getErrorOutput() { return $this->stderr; } /** * {@inheritdoc} */ public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } /** * Returns true if current environment supports writing console output to * STDOUT. * * @return bool */ protected function hasStdoutSupport() { return false === $this->isRunningOS400(); } /** * Returns true if current environment supports writing console output to * STDERR. * * @return bool */ protected function hasStderrSupport() { return false === $this->isRunningOS400(); } /** * Checks if current executing environment is IBM iSeries (OS400), which * doesn't properly convert character-encodings between ASCII to EBCDIC. * * @return bool */ private function isRunningOS400() { $checks = array( function_exists('php_uname') ? php_uname('s') : '', getenv('OSTYPE'), PHP_OS, ); return false !== stripos(implode(';', $checks), 'OS400'); } /** * @return resource */ private function openOutputStream() { $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output'; return @fopen($outputStream, 'w') ?: fopen('php://output', 'w'); } /** * @return resource */ private function openErrorStream() { $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output'; return fopen($errorStream, 'w'); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * NullOutput suppresses all output. * * $output = new NullOutput(); * * @author Fabien Potencier <fabien@symfony.com> * @author Tobias Schultze <http://tobion.de> */ class NullOutput implements OutputInterface { /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { // do nothing } /** * {@inheritdoc} */ public function getFormatter() { // to comply with the interface we must return a OutputFormatterInterface return new OutputFormatter(); } /** * {@inheritdoc} */ public function setDecorated($decorated) { // do nothing } /** * {@inheritdoc} */ public function isDecorated() { return false; } /** * {@inheritdoc} */ public function setVerbosity($level) { // do nothing } /** * {@inheritdoc} */ public function getVerbosity() { return self::VERBOSITY_QUIET; } /** * {@inheritdoc} */ public function isQuiet() { return true; } /** * {@inheritdoc} */ public function isVerbose() { return false; } /** * {@inheritdoc} */ public function isVeryVerbose() { return false; } /** * {@inheritdoc} */ public function isDebug() { return false; } /** * {@inheritdoc} */ public function writeln($messages, $options = self::OUTPUT_NORMAL) { // do nothing } /** * {@inheritdoc} */ public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) { // do nothing } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * @author Jean-François Simon <contact@jfsimon.fr> */ class BufferedOutput extends Output { /** * @var string */ private $buffer = ''; /** * Empties buffer and returns its content. * * @return string */ public function fetch() { $content = $this->buffer; $this->buffer = ''; return $content; } /** * {@inheritdoc} */ protected function doWrite($message, $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= PHP_EOL; } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * Represents an incorrect option name typed in the console. * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * ExceptionInterface. * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ interface ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class LogicException extends \LogicException implements ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * Represents an incorrect command name typed in the console. * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface { private $alternatives; /** * @param string $message Exception message to throw * @param array $alternatives List of similar defined names * @param int $code Exception code * @param Exception $previous previous exception used for the exception chaining */ public function __construct($message, array $alternatives = array(), $code = 0, \Exception $previous = null) { parent::__construct($message, $code, $previous); $this->alternatives = $alternatives; } /** * @return array A list of similar defined names */ public function getAlternatives() { return $this->alternatives; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Filesystem; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Exception\FileNotFoundException; /** * Provides basic utility to manipulate the file system. * * @author Fabien Potencier <fabien@symfony.com> */ class Filesystem { /** * Copies a file. * * If the target file is older than the origin file, it's always overwritten. * If the target file is newer, it is overwritten only when the * $overwriteNewerFiles option is set to true. * * @param string $originFile The original filename * @param string $targetFile The target filename * @param bool $overwriteNewerFiles If true, target files newer than origin files are overwritten * * @throws FileNotFoundException When originFile doesn't exist * @throws IOException When copy fails */ public function copy($originFile, $targetFile, $overwriteNewerFiles = false) { $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://'); if ($originIsLocal && !is_file($originFile)) { throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile); } $this->mkdir(dirname($targetFile)); $doCopy = true; if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) { $doCopy = filemtime($originFile) > filemtime($targetFile); } if ($doCopy) { // https://bugs.php.net/bug.php?id=64634 if (false === $source = @fopen($originFile, 'r')) { throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile); } // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))))) { throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile); } $bytesCopied = stream_copy_to_stream($source, $target); fclose($source); fclose($target); unset($source, $target); if (!is_file($targetFile)) { throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile); } if ($originIsLocal) { // Like `cp`, preserve executable permission bits @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); if ($bytesCopied !== $bytesOrigin = filesize($originFile)) { throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); } } } } /** * Creates a directory recursively. * * @param string|array|\Traversable $dirs The directory path * @param int $mode The directory mode * * @throws IOException On any directory creation failure */ public function mkdir($dirs, $mode = 0777) { foreach ($this->toIterator($dirs) as $dir) { if (is_dir($dir)) { continue; } if (true !== @mkdir($dir, $mode, true)) { $error = error_get_last(); if (!is_dir($dir)) { // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one if ($error) { throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message']), 0, null, $dir); } throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir); } } } } /** * Checks the existence of files or directories. * * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check * * @return bool true if the file exists, false otherwise */ public function exists($files) { $maxPathLength = PHP_MAXPATHLEN - 2; foreach ($this->toIterator($files) as $file) { if (strlen($file) > $maxPathLength) { throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file); } if (!file_exists($file)) { return false; } } return true; } /** * Sets access and modification time of file. * * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create * @param int $time The touch time as a Unix timestamp * @param int $atime The access time as a Unix timestamp * * @throws IOException When touch fails */ public function touch($files, $time = null, $atime = null) { foreach ($this->toIterator($files) as $file) { $touch = $time ? @touch($file, $time, $atime) : @touch($file); if (true !== $touch) { throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file); } } } /** * Removes files or directories. * * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove * * @throws IOException When removal fails */ public function remove($files) { if ($files instanceof \Traversable) { $files = iterator_to_array($files, false); } elseif (!is_array($files)) { $files = array($files); } $files = array_reverse($files); foreach ($files as $file) { if (is_link($file)) { // See https://bugs.php.net/52176 if (!@(unlink($file) || '\\' !== DIRECTORY_SEPARATOR || rmdir($file)) && file_exists($file)) { $error = error_get_last(); throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message'])); } } elseif (is_dir($file)) { $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); if (!@rmdir($file) && file_exists($file)) { $error = error_get_last(); throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, $error['message'])); } } elseif (!@unlink($file) && file_exists($file)) { $error = error_get_last(); throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message'])); } } } /** * Change mode for an array of files or directories. * * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change mode * @param int $mode The new mode (octal) * @param int $umask The mode mask (octal) * @param bool $recursive Whether change the mod recursively or not * * @throws IOException When the change fail */ public function chmod($files, $mode, $umask = 0000, $recursive = false) { foreach ($this->toIterator($files) as $file) { if (true !== @chmod($file, $mode & ~$umask)) { throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file); } if ($recursive && is_dir($file) && !is_link($file)) { $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); } } } /** * Change the owner of an array of files or directories. * * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change owner * @param string $user The new owner user name * @param bool $recursive Whether change the owner recursively or not * * @throws IOException When the change fail */ public function chown($files, $user, $recursive = false) { foreach ($this->toIterator($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { $this->chown(new \FilesystemIterator($file), $user, true); } if (is_link($file) && function_exists('lchown')) { if (true !== @lchown($file, $user)) { throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); } } else { if (true !== @chown($file, $user)) { throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); } } } } /** * Change the group of an array of files or directories. * * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change group * @param string $group The group name * @param bool $recursive Whether change the group recursively or not * * @throws IOException When the change fail */ public function chgrp($files, $group, $recursive = false) { foreach ($this->toIterator($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { $this->chgrp(new \FilesystemIterator($file), $group, true); } if (is_link($file) && function_exists('lchgrp')) { if (true !== @lchgrp($file, $group) || (defined('HHVM_VERSION') && !posix_getgrnam($group))) { throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); } } else { if (true !== @chgrp($file, $group)) { throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); } } } } /** * Renames a file or a directory. * * @param string $origin The origin filename or directory * @param string $target The new filename or directory * @param bool $overwrite Whether to overwrite the target if it already exists * * @throws IOException When target file or directory already exists * @throws IOException When origin cannot be renamed */ public function rename($origin, $target, $overwrite = false) { // we check that target does not exist if (!$overwrite && $this->isReadable($target)) { throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); } if (true !== @rename($origin, $target)) { if (is_dir($origin)) { // See https://bugs.php.net/bug.php?id=54097 & http://php.net/manual/en/function.rename.php#113943 $this->mirror($origin, $target, null, array('override' => $overwrite, 'delete' => $overwrite)); $this->remove($origin); return; } throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target); } } /** * Tells whether a file exists and is readable. * * @param string $filename Path to the file * * @return bool * * @throws IOException When windows path is longer than 258 characters */ private function isReadable($filename) { $maxPathLength = PHP_MAXPATHLEN - 2; if (strlen($filename) > $maxPathLength) { throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename); } return is_readable($filename); } /** * Creates a symbolic link or copy a directory. * * @param string $originDir The origin directory path * @param string $targetDir The symbolic link name * @param bool $copyOnWindows Whether to copy files if on Windows * * @throws IOException When symlink fails */ public function symlink($originDir, $targetDir, $copyOnWindows = false) { if ('\\' === DIRECTORY_SEPARATOR) { $originDir = strtr($originDir, '/', '\\'); $targetDir = strtr($targetDir, '/', '\\'); if ($copyOnWindows) { $this->mirror($originDir, $targetDir); return; } } $this->mkdir(dirname($targetDir)); $ok = false; if (is_link($targetDir)) { if (readlink($targetDir) != $originDir) { $this->remove($targetDir); } else { $ok = true; } } if (!$ok && true !== @symlink($originDir, $targetDir)) { $report = error_get_last(); if (is_array($report)) { if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) { throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', 0, null, $targetDir); } } throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir); } } /** * Given an existing path, convert it to a path relative to a given starting path. * * @param string $endPath Absolute path of target * @param string $startPath Absolute path where traversal begins * * @return string Path of target relative to starting path */ public function makePathRelative($endPath, $startPath) { // Normalize separators on Windows if ('\\' === DIRECTORY_SEPARATOR) { $endPath = str_replace('\\', '/', $endPath); $startPath = str_replace('\\', '/', $startPath); } $stripDriveLetter = function ($path) { if (strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) { return substr($path, 2); } return $path; }; $endPath = $stripDriveLetter($endPath); $startPath = $stripDriveLetter($startPath); // Split the paths into arrays $startPathArr = explode('/', trim($startPath, '/')); $endPathArr = explode('/', trim($endPath, '/')); $normalizePathArray = function ($pathSegments, $absolute) { $result = array(); foreach ($pathSegments as $segment) { if ('..' === $segment && ($absolute || count($result))) { array_pop($result); } elseif ('.' !== $segment) { $result[] = $segment; } } return $result; }; $startPathArr = $normalizePathArray($startPathArr, static::isAbsolutePath($startPath)); $endPathArr = $normalizePathArray($endPathArr, static::isAbsolutePath($endPath)); // Find for which directory the common path stops $index = 0; while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { ++$index; } // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) if (1 === count($startPathArr) && '' === $startPathArr[0]) { $depth = 0; } else { $depth = count($startPathArr) - $index; } // Repeated "../" for each level need to reach the common path $traverser = str_repeat('../', $depth); $endPathRemainder = implode('/', array_slice($endPathArr, $index)); // Construct $endPath from traversing to the common path, then to the remaining $endPath $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : ''); return '' === $relativePath ? './' : $relativePath; } /** * Mirrors a directory to another. * * @param string $originDir The origin directory * @param string $targetDir The target directory * @param \Traversable $iterator A Traversable instance * @param array $options An array of boolean options * Valid options are: * - $options['override'] Whether to override an existing file on copy or not (see copy()) * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink()) * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) * * @throws IOException When file type is unknown */ public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array()) { $targetDir = rtrim($targetDir, '/\\'); $originDir = rtrim($originDir, '/\\'); $originDirLen = strlen($originDir); // Iterate in destination folder to remove obsolete entries if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { $deleteIterator = $iterator; if (null === $deleteIterator) { $flags = \FilesystemIterator::SKIP_DOTS; $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); } $targetDirLen = strlen($targetDir); foreach ($deleteIterator as $file) { $origin = $originDir.substr($file->getPathname(), $targetDirLen); if (!$this->exists($origin)) { $this->remove($file); } } } $copyOnWindows = false; if (isset($options['copy_on_windows'])) { $copyOnWindows = $options['copy_on_windows']; } if (null === $iterator) { $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); } if ($this->exists($originDir)) { $this->mkdir($targetDir); } foreach ($iterator as $file) { $target = $targetDir.substr($file->getPathname(), $originDirLen); if ($copyOnWindows) { if (is_file($file)) { $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); } elseif (is_dir($file)) { $this->mkdir($target); } else { throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); } } else { if (is_link($file)) { $this->symlink($file->getLinkTarget(), $target); } elseif (is_dir($file)) { $this->mkdir($target); } elseif (is_file($file)) { $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); } else { throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); } } } } /** * Returns whether the file path is an absolute path. * * @param string $file A file path * * @return bool */ public function isAbsolutePath($file) { return strspn($file, '/\\', 0, 1) || (strlen($file) > 3 && ctype_alpha($file[0]) && ':' === substr($file, 1, 1) && strspn($file, '/\\', 2, 1) ) || null !== parse_url($file, PHP_URL_SCHEME) ; } /** * Creates a temporary file with support for custom stream wrappers. * * @param string $dir The directory where the temporary filename will be created * @param string $prefix The prefix of the generated temporary filename * Note: Windows uses only the first three characters of prefix * * @return string The new temporary filename (with path), or throw an exception on failure */ public function tempnam($dir, $prefix) { list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir); // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) { $tmpFile = @tempnam($hierarchy, $prefix); // If tempnam failed or no scheme return the filename otherwise prepend the scheme if (false !== $tmpFile) { if (null !== $scheme && 'gs' !== $scheme) { return $scheme.'://'.$tmpFile; } return $tmpFile; } throw new IOException('A temporary file could not be created.'); } // Loop until we create a valid temp file or have reached 10 attempts for ($i = 0; $i < 10; ++$i) { // Create a unique filename $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); // Use fopen instead of file_exists as some streams do not support stat // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability $handle = @fopen($tmpFile, 'x+'); // If unsuccessful restart the loop if (false === $handle) { continue; } // Close the file if it was successfully opened @fclose($handle); return $tmpFile; } throw new IOException('A temporary file could not be created.'); } /** * Atomically dumps content into a file. * * @param string $filename The file to be written to * @param string $content The data to write into the file * @param null|int $mode The file mode (octal). If null, file permissions are not modified * Deprecated since version 2.3.12, to be removed in 3.0. * * @throws IOException if the file cannot be written to */ public function dumpFile($filename, $content, $mode = 0666) { $dir = dirname($filename); if (!is_dir($dir)) { $this->mkdir($dir); } if (!is_writable($dir)) { throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); } $tmpFile = $this->tempnam($dir, basename($filename)); if (false === @file_put_contents($tmpFile, $content)) { throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); } if (null !== $mode) { if (func_num_args() > 2) { @trigger_error('Support for modifying file permissions is deprecated since version 2.3.12 and will be removed in 3.0.', E_USER_DEPRECATED); } $this->chmod($tmpFile, $mode); } elseif (file_exists($filename)) { @chmod($tmpFile, fileperms($filename)); } $this->rename($tmpFile, $filename, true); } /** * @param mixed $files * * @return \Traversable */ private function toIterator($files) { if (!$files instanceof \Traversable) { $files = new \ArrayObject(is_array($files) ? $files : array($files)); } return $files; } /** * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)). * * @param string $filename The filename to be parsed * * @return array The filename scheme and hierarchical part */ private function getSchemeAndHierarchy($filename) { $components = explode('://', $filename, 2); return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Filesystem; use Symfony\Component\Filesystem\Exception\IOException; /** * LockHandler class provides a simple abstraction to lock anything by means of * a file lock. * * A locked file is created based on the lock name when calling lock(). Other * lock handlers will not be able to lock the same name until it is released * (explicitly by calling release() or implicitly when the instance holding the * lock is destroyed). * * @author Grégoire Pineau <lyrixx@lyrixx.info> * @author Romain Neutron <imprec@gmail.com> * @author Nicolas Grekas <p@tchwork.com> */ class LockHandler { private $file; private $handle; /** * @param string $name The lock name * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory * * @throws IOException If the lock directory could not be created or is not writable */ public function __construct($name, $lockPath = null) { $lockPath = $lockPath ?: sys_get_temp_dir(); if (!is_dir($lockPath)) { $fs = new Filesystem(); $fs->mkdir($lockPath); } if (!is_writable($lockPath)) { throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath); } $this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name)); } /** * Lock the resource. * * @param bool $blocking wait until the lock is released * * @return bool Returns true if the lock was acquired, false otherwise * * @throws IOException If the lock file could not be created or opened */ public function lock($blocking = false) { if ($this->handle) { return true; } $error = null; // Silence error reporting set_error_handler(function ($errno, $msg) use (&$error) { $error = $msg; }); if (!$this->handle = fopen($this->file, 'r')) { if ($this->handle = fopen($this->file, 'x')) { chmod($this->file, 0444); } elseif (!$this->handle = fopen($this->file, 'r')) { usleep(100); // Give some time for chmod() to complete $this->handle = fopen($this->file, 'r'); } } restore_error_handler(); if (!$this->handle) { throw new IOException($error, 0, null, $this->file); } // On Windows, even if PHP doc says the contrary, LOCK_NB works, see // https://bugs.php.net/54129 if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) { fclose($this->handle); $this->handle = null; return false; } return true; } /** * Release the resource. */ public function release() { if ($this->handle) { flock($this->handle, LOCK_UN | LOCK_NB); fclose($this->handle); $this->handle = null; } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Filesystem\Exception; /** * Exception class thrown when a filesystem operation failure happens. * * @author Romain Neutron <imprec@gmail.com> * @author Christian Gärtner <christiangaertner.film@googlemail.com> * @author Fabien Potencier <fabien@symfony.com> */ class IOException extends \RuntimeException implements IOExceptionInterface { private $path; public function __construct($message, $code = 0, \Exception $previous = null, $path = null) { $this->path = $path; parent::__construct($message, $code, $previous); } /** * {@inheritdoc} */ public function getPath() { return $this->path; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Filesystem\Exception; /** * Exception interface for all exceptions thrown by the component. * * @author Romain Neutron <imprec@gmail.com> */ interface ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Filesystem\Exception; /** * IOException interface for file and input/output stream related exceptions thrown by the component. * * @author Christian Gärtner <christiangaertner.film@googlemail.com> */ interface IOExceptionInterface extends ExceptionInterface { /** * Returns the associated path for the exception. * * @return string The path */ public function getPath(); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Filesystem\Exception; /** * Exception class thrown when a file couldn't be found. * * @author Fabien Potencier <fabien@symfony.com> * @author Christian Gärtner <christiangaertner.film@googlemail.com> */ class FileNotFoundException extends IOException { public function __construct($message = null, $code = 0, \Exception $previous = null, $path = null) { if (null === $message) { if (null === $path) { $message = 'File could not be found.'; } else { $message = sprintf('File "%s" could not be found.', $path); } } parent::__construct($message, $code, $previous, $path); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process; use Symfony\Component\Process\Exception\InvalidArgumentException; use Symfony\Component\Process\Exception\LogicException; /** * @author Kris Wallsmith <kris@symfony.com> */ class ProcessBuilder { private $arguments; private $cwd; private $env = array(); private $input; private $timeout = 60; private $options = array(); private $inheritEnv = true; private $prefix = array(); private $outputDisabled = false; /** * @param string[] $arguments An array of arguments */ public function __construct(array $arguments = array()) { $this->arguments = $arguments; } /** * Creates a process builder instance. * * @param string[] $arguments An array of arguments * * @return static */ public static function create(array $arguments = array()) { return new static($arguments); } /** * Adds an unescaped argument to the command string. * * @param string $argument A command argument * * @return $this */ public function add($argument) { $this->arguments[] = $argument; return $this; } /** * Adds a prefix to the command string. * * The prefix is preserved when resetting arguments. * * @param string|array $prefix A command prefix or an array of command prefixes * * @return $this */ public function setPrefix($prefix) { $this->prefix = is_array($prefix) ? $prefix : array($prefix); return $this; } /** * Sets the arguments of the process. * * Arguments must not be escaped. * Previous arguments are removed. * * @param string[] $arguments * * @return $this */ public function setArguments(array $arguments) { $this->arguments = $arguments; return $this; } /** * Sets the working directory. * * @param null|string $cwd The working directory * * @return $this */ public function setWorkingDirectory($cwd) { $this->cwd = $cwd; return $this; } /** * Sets whether environment variables will be inherited or not. * * @param bool $inheritEnv * * @return $this */ public function inheritEnvironmentVariables($inheritEnv = true) { $this->inheritEnv = $inheritEnv; return $this; } /** * Sets an environment variable. * * Setting a variable overrides its previous value. Use `null` to unset a * defined environment variable. * * @param string $name The variable name * @param null|string $value The variable value * * @return $this */ public function setEnv($name, $value) { $this->env[$name] = $value; return $this; } /** * Adds a set of environment variables. * * Already existing environment variables with the same name will be * overridden by the new values passed to this method. Pass `null` to unset * a variable. * * @param array $variables The variables * * @return $this */ public function addEnvironmentVariables(array $variables) { $this->env = array_replace($this->env, $variables); return $this; } /** * Sets the input of the process. * * @param mixed $input The input as a string * * @return $this * * @throws InvalidArgumentException In case the argument is invalid * * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0. */ public function setInput($input) { $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } /** * Sets the process timeout. * * To disable the timeout, set this value to null. * * @param float|null $timeout * * @return $this * * @throws InvalidArgumentException */ 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; } /** * Adds a proc_open option. * * @param string $name The option name * @param string $value The option value * * @return $this */ public function setOption($name, $value) { $this->options[$name] = $value; return $this; } /** * Disables fetching output and error output from the underlying process. * * @return $this */ public function disableOutput() { $this->outputDisabled = true; return $this; } /** * Enables fetching output and error output from the underlying process. * * @return $this */ public function enableOutput() { $this->outputDisabled = false; return $this; } /** * Creates a Process instance and returns it. * * @return Process * * @throws LogicException In case no arguments have been provided */ 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)); if ($this->inheritEnv) { // include $_ENV for BC purposes $env = array_replace($_ENV, $_SERVER, $this->env); } else { $env = $this->env; } $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); if ($this->outputDisabled) { $process->disableOutput(); } return $process; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process; use Symfony\Component\Process\Exception\InvalidArgumentException; use Symfony\Component\Process\Exception\LogicException; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Exception\ProcessTimedOutException; use Symfony\Component\Process\Exception\RuntimeException; use Symfony\Component\Process\Pipes\PipesInterface; use Symfony\Component\Process\Pipes\UnixPipes; use Symfony\Component\Process\Pipes\WindowsPipes; /** * Process is a thin wrapper around proc_* functions to easily * start independent PHP processes. * * @author Fabien Potencier <fabien@symfony.com> * @author Romain Neutron <imprec@gmail.com> */ class Process { const ERR = 'err'; const OUT = 'out'; const STATUS_READY = 'ready'; const STATUS_STARTED = 'started'; const STATUS_TERMINATED = 'terminated'; const STDIN = 0; const STDOUT = 1; const STDERR = 2; // Timeout Precision in seconds. const TIMEOUT_PRECISION = 0.2; private $callback; private $commandline; private $cwd; private $env; private $input; private $starttime; private $lastOutputTime; private $timeout; private $idleTimeout; private $options; private $exitcode; private $fallbackStatus = array(); private $processInformation; private $outputDisabled = false; private $stdout; private $stderr; private $enhanceWindowsCompatibility = true; private $enhanceSigchildCompatibility; private $process; private $status = self::STATUS_READY; private $incrementalOutputOffset = 0; private $incrementalErrorOutputOffset = 0; private $tty; private $pty; private $useFileHandles = false; /** @var PipesInterface */ private $processPipes; private $latestSignal; private static $sigchild; /** * Exit codes translation table. * * User-defined errors must use exit codes in the 64-113 range. * * @var array */ public static $exitCodes = array( 0 => 'OK', 1 => 'General error', 2 => 'Misuse of shell builtins', 126 => 'Invoked command cannot execute', 127 => 'Command not found', 128 => 'Invalid exit argument', // signals 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)', // 144 - not defined 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', // 156 - not defined 157 => 'Pollable event', // 158 - not defined 159 => 'Bad syscall', ); /** * @param string $commandline The command line to run * @param string|null $cwd The working directory or null to use the working dir of the current PHP process * @param array|null $env The environment variables or null to use the same environment as the current PHP process * @param string|null $input The input * @param int|float|null $timeout The timeout in seconds or null to disable * @param array $options An array of options for proc_open * * @throws RuntimeException When proc_open is not installed */ 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; // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected // @see : https://bugs.php.net/bug.php?id=51800 // @see : https://bugs.php.net/bug.php?id=50524 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(); } /** * Runs the process. * * The callback receives the type of output (out or err) and * some bytes from the output in real-time. It allows to have feedback * from the independent process during execution. * * The STDOUT and STDERR are also available after the process is finished * via the getOutput() and getErrorOutput() methods. * * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @return int The exit status code * * @throws RuntimeException When process can't be launched * @throws RuntimeException When process stopped after receiving signal * @throws LogicException In case a callback is provided and output has been disabled */ public function run($callback = null) { $this->start($callback); return $this->wait(); } /** * Runs the process. * * This is identical to run() except that an exception is thrown if the process * exits with a non-zero exit code. * * @param callable|null $callback * * @return self * * @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled * @throws ProcessFailedException if the process didn't terminate successfully */ public function mustRun($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; } /** * Starts the process and returns after writing the input to STDIN. * * This method blocks until all STDIN data is sent to the process then it * returns while the process runs in the background. * * The termination of the process can be awaited with wait(). * * The callback receives the type of output (out or err) and some bytes from * the output in real-time while writing the standard input to the process. * It allows to have feedback from the independent process during execution. * * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @throws RuntimeException When process can't be launched * @throws RuntimeException When process is already running * @throws LogicException In case a callback is provided and output has been disabled */ public function start($callback = null) { if ($this->isRunning()) { throw new RuntimeException('Process is already running'); } if ($this->outputDisabled && null !== $callback) { throw new LogicException('Output has been disabled, enable it to allow the use of a callback.'); } $this->resetProcessData(); $this->starttime = $this->lastOutputTime = microtime(true); $this->callback = $this->buildCallback($callback); $descriptors = $this->getDescriptors(); $commandline = $this->commandline; 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()) { // last exit code is output on the fourth pipe and caught to work around --enable-sigchild $descriptors[3] = array('pipe', 'w'); // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input $commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; // Workaround for the bug, when PTS functionality is enabled. // @see : https://bugs.php.net/69442 $ptsWorkaround = fopen(__FILE__, 'r'); } $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); 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(); } /** * Restarts the process. * * Be warned that the process is cloned before being started. * * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @return $this * * @throws RuntimeException When process can't be launched * @throws RuntimeException When process is already running * * @see start() */ public function restart($callback = null) { if ($this->isRunning()) { throw new RuntimeException('Process is already running'); } $process = clone $this; $process->start($callback); return $process; } /** * Waits for the process to terminate. * * The callback receives the type of output (out or err) and some bytes * from the output in real-time while writing the standard input to the process. * It allows to have feedback from the independent process during execution. * * @param callable|null $callback A valid PHP callback * * @return int The exitcode of the process * * @throws RuntimeException When process timed out * @throws RuntimeException When process stopped after receiving signal * @throws LogicException When process is not yet started */ public function wait($callback = null) { $this->requireProcessIsStarted(__FUNCTION__); $this->updateStatus(false); if (null !== $callback) { $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; } /** * Returns the Pid (process identifier), if applicable. * * @return int|null The process id if running, null otherwise */ public function getPid() { return $this->isRunning() ? $this->processInformation['pid'] : null; } /** * Sends a POSIX signal to the process. * * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) * * @return $this * * @throws LogicException In case the process is not running * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ public function signal($signal) { $this->doSignal($signal, true); return $this; } /** * Disables fetching output and error output from the underlying process. * * @return $this * * @throws RuntimeException In case the process is already running * @throws LogicException if an idle timeout is set */ 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; } /** * Enables fetching output and error output from the underlying process. * * @return $this * * @throws RuntimeException In case the process is already running */ public function enableOutput() { if ($this->isRunning()) { throw new RuntimeException('Enabling output while the process is running is not possible.'); } $this->outputDisabled = false; return $this; } /** * Returns true in case the output is disabled, false otherwise. * * @return bool */ public function isOutputDisabled() { return $this->outputDisabled; } /** * Returns the current output of the process (STDOUT). * * @return string The process output * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ public function getOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { return ''; } return $ret; } /** * Returns the output incrementally. * * In comparison with the getOutput method which always return the whole * output, this one returns the new output since the last call. * * @return string The process output since the last call * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ 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; } /** * Clears the process output. * * @return $this */ public function clearOutput() { ftruncate($this->stdout, 0); fseek($this->stdout, 0); $this->incrementalOutputOffset = 0; return $this; } /** * Returns the current error output of the process (STDERR). * * @return string The process error output * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ public function getErrorOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { return ''; } return $ret; } /** * Returns the errorOutput incrementally. * * In comparison with the getErrorOutput method which always return the * whole error output, this one returns the new error output since the last * call. * * @return string The process error output since the last call * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ 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; } /** * Clears the process output. * * @return $this */ public function clearErrorOutput() { ftruncate($this->stderr, 0); fseek($this->stderr, 0); $this->incrementalErrorOutputOffset = 0; return $this; } /** * Returns the exit code returned by the process. * * @return null|int The exit status code, null if the Process is not terminated * * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled */ 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; } /** * Returns a string representation for the exit code returned by the process. * * This method relies on the Unix exit code status standardization * and might not be relevant for other operating systems. * * @return null|string A string representation for the exit status code, null if the Process is not terminated * * @see http://tldp.org/LDP/abs/html/exitcodes.html * @see http://en.wikipedia.org/wiki/Unix_signal */ public function getExitCodeText() { if (null === $exitcode = $this->getExitCode()) { return; } return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; } /** * Checks if the process ended successfully. * * @return bool true if the process ended successfully, false otherwise */ public function isSuccessful() { return 0 === $this->getExitCode(); } /** * Returns true if the child process has been terminated by an uncaught signal. * * It always returns false on Windows. * * @return bool * * @throws RuntimeException In case --enable-sigchild is activated * @throws LogicException In case the process is not terminated */ 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']; } /** * Returns the number of the signal that caused the child process to terminate its execution. * * It is only meaningful if hasBeenSignaled() returns true. * * @return int * * @throws RuntimeException In case --enable-sigchild is activated * @throws LogicException In case the process is not terminated */ 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']; } /** * Returns true if the child process has been stopped by a signal. * * It always returns false on Windows. * * @return bool * * @throws LogicException In case the process is not terminated */ public function hasBeenStopped() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopped']; } /** * Returns the number of the signal that caused the child process to stop its execution. * * It is only meaningful if hasBeenStopped() returns true. * * @return int * * @throws LogicException In case the process is not terminated */ public function getStopSignal() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopsig']; } /** * Checks if the process is currently running. * * @return bool true if the process is currently running, false otherwise */ public function isRunning() { if (self::STATUS_STARTED !== $this->status) { return false; } $this->updateStatus(false); return $this->processInformation['running']; } /** * Checks if the process has been started with no regard to the current state. * * @return bool true if status is ready, false otherwise */ public function isStarted() { return self::STATUS_READY != $this->status; } /** * Checks if the process is terminated. * * @return bool true if process is terminated, false otherwise */ public function isTerminated() { $this->updateStatus(false); return self::STATUS_TERMINATED == $this->status; } /** * Gets the process status. * * The status is one of: ready, started, terminated. * * @return string The current process status */ public function getStatus() { $this->updateStatus(false); return $this->status; } /** * Stops the process. * * @param int|float $timeout The timeout in seconds * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) * * @return int The exit-code of the process */ public function stop($timeout = 10, $signal = null) { $timeoutMicro = microtime(true) + $timeout; if ($this->isRunning()) { // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here $this->doSignal(15, false); do { usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); if ($this->isRunning()) { // Avoid exception here: process is supposed to be running, but it might have stopped just // after this line. In any case, let's silently discard the error, we cannot do anything. $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; } /** * Adds a line to the STDOUT stream. * * @internal * * @param string $line The line to append */ public function addOutput($line) { $this->lastOutputTime = microtime(true); fseek($this->stdout, 0, SEEK_END); fwrite($this->stdout, $line); fseek($this->stdout, $this->incrementalOutputOffset); } /** * Adds a line to the STDERR stream. * * @internal * * @param string $line The line to append */ public function addErrorOutput($line) { $this->lastOutputTime = microtime(true); fseek($this->stderr, 0, SEEK_END); fwrite($this->stderr, $line); fseek($this->stderr, $this->incrementalErrorOutputOffset); } /** * Gets the command line to be executed. * * @return string The command to execute */ public function getCommandLine() { return $this->commandline; } /** * Sets the command line to be executed. * * @param string $commandline The command to execute * * @return self The current Process instance */ public function setCommandLine($commandline) { $this->commandline = $commandline; return $this; } /** * Gets the process timeout (max. runtime). * * @return float|null The timeout in seconds or null if it's disabled */ public function getTimeout() { return $this->timeout; } /** * Gets the process idle timeout (max. time since last output). * * @return float|null The timeout in seconds or null if it's disabled */ public function getIdleTimeout() { return $this->idleTimeout; } /** * Sets the process timeout (max. runtime). * * To disable the timeout, set this value to null. * * @param int|float|null $timeout The timeout in seconds * * @return self The current Process instance * * @throws InvalidArgumentException if the timeout is negative */ public function setTimeout($timeout) { $this->timeout = $this->validateTimeout($timeout); return $this; } /** * Sets the process idle timeout (max. time since last output). * * To disable the timeout, set this value to null. * * @param int|float|null $timeout The timeout in seconds * * @return self The current Process instance * * @throws LogicException if the output is disabled * @throws InvalidArgumentException if the timeout is negative */ 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; } /** * Enables or disables the TTY mode. * * @param bool $tty True to enabled and false to disable * * @return self The current Process instance * * @throws RuntimeException In case the TTY mode is not supported */ 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; } /** * Checks if the TTY mode is enabled. * * @return bool true if the TTY mode is enabled, false otherwise */ public function isTty() { return $this->tty; } /** * Sets PTY mode. * * @param bool $bool * * @return self */ public function setPty($bool) { $this->pty = (bool) $bool; return $this; } /** * Returns PTY state. * * @return bool */ public function isPty() { return $this->pty; } /** * Gets the working directory. * * @return string|null The current working directory or null on failure */ public function getWorkingDirectory() { if (null === $this->cwd) { // getcwd() will return false if any one of the parent directories does not have // the readable or search mode set, even if the current directory does return getcwd() ?: null; } return $this->cwd; } /** * Sets the current working directory. * * @param string $cwd The new working directory * * @return self The current Process instance */ public function setWorkingDirectory($cwd) { $this->cwd = $cwd; return $this; } /** * Gets the environment variables. * * @return array The current environment variables */ public function getEnv() { return $this->env; } /** * Sets the environment variables. * * An environment variable value should be a string. * If it is an array, the variable is ignored. * * That happens in PHP when 'argv' is registered into * the $_ENV array for instance. * * @param array $env The new environment variables * * @return self The current Process instance */ public function setEnv(array $env) { // Process can not handle env values that are arrays $env = array_filter($env, function ($value) { return !is_array($value); }); $this->env = array(); foreach ($env as $key => $value) { $this->env[$key] = (string) $value; } return $this; } /** * Gets the contents of STDIN. * * @return string|null The current contents * * @deprecated since version 2.5, to be removed in 3.0. * Use setInput() instead. * This method is deprecated in favor of getInput. */ public function getStdin() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0. Use the getInput() method instead.', E_USER_DEPRECATED); return $this->getInput(); } /** * Gets the Process input. * * @return null|string The Process input */ public function getInput() { return $this->input; } /** * Sets the contents of STDIN. * * @param string|null $stdin The new contents * * @return self The current Process instance * * @deprecated since version 2.5, to be removed in 3.0. * Use setInput() instead. * * @throws LogicException In case the process is running * @throws InvalidArgumentException In case the argument is invalid */ public function setStdin($stdin) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0. Use the setInput() method instead.', E_USER_DEPRECATED); return $this->setInput($stdin); } /** * Sets the input. * * This content will be passed to the underlying process standard input. * * @param mixed $input The content * * @return self The current Process instance * * @throws LogicException In case the process is running * * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0. */ 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; } /** * Gets the options for proc_open. * * @return array The current options */ public function getOptions() { return $this->options; } /** * Sets the options for proc_open. * * @param array $options The new options * * @return self The current Process instance */ public function setOptions(array $options) { $this->options = $options; return $this; } /** * Gets whether or not Windows compatibility is enabled. * * This is true by default. * * @return bool */ public function getEnhanceWindowsCompatibility() { return $this->enhanceWindowsCompatibility; } /** * Sets whether or not Windows compatibility is enabled. * * @param bool $enhance * * @return self The current Process instance */ public function setEnhanceWindowsCompatibility($enhance) { $this->enhanceWindowsCompatibility = (bool) $enhance; return $this; } /** * Returns whether sigchild compatibility mode is activated or not. * * @return bool */ public function getEnhanceSigchildCompatibility() { return $this->enhanceSigchildCompatibility; } /** * Activates sigchild compatibility mode. * * Sigchild compatibility mode is required to get the exit code and * determine the success of a process when PHP has been compiled with * the --enable-sigchild option * * @param bool $enhance * * @return self The current Process instance */ public function setEnhanceSigchildCompatibility($enhance) { $this->enhanceSigchildCompatibility = (bool) $enhance; return $this; } /** * Performs a check between the timeout definition and the time the process started. * * In case you run a background process (with the start method), you should * trigger this method regularly to ensure the process timeout * * @throws ProcessTimedOutException In case the timeout was reached */ public function checkTimeout() { if (self::STATUS_STARTED !== $this->status) { 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); } } /** * Returns whether PTY is supported on the current operating system. * * @return bool */ 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); } /** * Creates the descriptors needed by the proc_open. * * @return array */ private function getDescriptors() { if ('\\' === DIRECTORY_SEPARATOR) { $this->processPipes = WindowsPipes::create($this, $this->input); } else { $this->processPipes = UnixPipes::create($this, $this->input); } return $this->processPipes->getDescriptors(); } /** * Builds up the callback used by wait(). * * The callbacks adds all occurred output to the specific buffer and calls * the user callback (if present) with the received output. * * @param callable|null $callback The user defined PHP callback * * @return \Closure A PHP closure */ protected function buildCallback($callback) { $that = $this; $out = self::OUT; $callback = function ($type, $data) use ($that, $callback, $out) { if ($out == $type) { $that->addOutput($data); } else { $that->addErrorOutput($data); } if (null !== $callback) { call_user_func($callback, $type, $data); } }; return $callback; } /** * Updates the status of the process, reads pipes. * * @param bool $blocking Whether to use a blocking read call */ 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(); } } /** * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. * * @return bool */ 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'); } /** * Reads pipes for the freshest output. * * @param $caller The name of the method that needs fresh outputs * * @throws LogicException in case output has been disabled or process is not started */ private function readPipesForOutput($caller) { if ($this->outputDisabled) { throw new LogicException('Output has been disabled.'); } $this->requireProcessIsStarted($caller); $this->updateStatus(false); } /** * Validates and returns the filtered timeout. * * @param int|float|null $timeout * * @return float|null * * @throws InvalidArgumentException if the given timeout is a negative number */ 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; } /** * Reads pipes, executes callback. * * @param bool $blocking Whether to use blocking calls or not * @param bool $close Whether to close file handles or not */ private function readPipes($blocking, $close) { $result = $this->processPipes->readAndWrite($blocking, $close); $callback = $this->callback; foreach ($result as $type => $data) { if (3 !== $type) { $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); } elseif (!isset($this->fallbackStatus['signaled'])) { $this->fallbackStatus['exitcode'] = (int) $data; } } } /** * Closes process resource, closes file handles, sets the exitcode. * * @return int The exitcode */ 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']) { // if process has been signaled, no exitcode but a valid termsig, apply Unix convention $this->exitcode = 128 + $this->processInformation['termsig']; } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { $this->processInformation['signaled'] = true; $this->processInformation['termsig'] = -1; } } // Free memory from self-reference callback created by buildCallback // Doing so in other contexts like __destruct or by garbage collector is ineffective // Now pipes are closed, so the callback is no longer necessary $this->callback = null; return $this->exitcode; } /** * Resets data related to the latest run of the process. */ 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; } /** * Sends a POSIX signal to the process. * * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) * @param bool $throwException Whether to throw exception in case signal failed * * @return bool True if the signal was sent successfully, false otherwise * * @throws LogicException In case the process is not running * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ 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; } /** * Ensures the process is running or terminated, throws a LogicException if the process has a not started. * * @param string $functionName The function name that was called * * @throws LogicException if the process has not run */ private function requireProcessIsStarted($functionName) { if (!$this->isStarted()) { throw new LogicException(sprintf('Process must be started before calling %s.', $functionName)); } } /** * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`. * * @param string $functionName The function name that was called * * @throws LogicException if the process is not yet terminated */ private function requireProcessIsTerminated($functionName) { if (!$this->isTerminated()) { throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process; /** * An executable finder specifically designed for the PHP executable. * * @author Fabien Potencier <fabien@symfony.com> * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ class PhpExecutableFinder { private $executableFinder; public function __construct() { $this->executableFinder = new ExecutableFinder(); } /** * Finds The PHP executable. * * @param bool $includeArgs Whether or not include command arguments * * @return string|false The PHP executable path or false if it cannot be found */ public function find($includeArgs = true) { $args = $this->findArguments(); $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; // HHVM support if (defined('HHVM_VERSION')) { return (getenv('PHP_BINARY') ?: PHP_BINARY).$args; } // PHP_BINARY return the current sapi executable if (defined('PHP_BINARY') && 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); } /** * Finds the PHP executable arguments. * * @return array The PHP executable arguments */ public function findArguments() { $arguments = array(); if (defined('HHVM_VERSION')) { $arguments[] = '--php'; } elseif ('phpdbg' === PHP_SAPI) { $arguments[] = '-qrr'; } return $arguments; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Pipes; /** * @author Romain Neutron <imprec@gmail.com> * * @internal */ abstract class AbstractPipes implements PipesInterface { /** @var array */ public $pipes = array(); /** @var string */ private $inputBuffer = ''; /** @var resource|null */ private $input; /** @var bool */ private $blocked = true; public function __construct($input) { if (is_resource($input)) { $this->input = $input; } elseif (is_string($input)) { $this->inputBuffer = $input; } else { $this->inputBuffer = (string) $input; } } /** * {@inheritdoc} */ public function close() { foreach ($this->pipes as $pipe) { fclose($pipe); } $this->pipes = array(); } /** * Returns true if a system call has been interrupted. * * @return bool */ protected function hasSystemCallBeenInterrupted() { $lastError = error_get_last(); // stream_select returns false when the `select` system call is interrupted by an incoming signal return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); } /** * Unblocks streams. */ protected function unblock() { if (!$this->blocked) { return; } foreach ($this->pipes as $pipe) { stream_set_blocking($pipe, 0); } if (null !== $this->input) { stream_set_blocking($this->input, 0); } $this->blocked = false; } /** * Writes input to stdin. */ protected function write() { if (!isset($this->pipes[0])) { return; } $input = $this->input; $r = $e = array(); $w = array($this->pipes[0]); // let's have a look if something changed in streams 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)) { // no more data to read on input resource // use an empty buffer in the next reads $this->input = null; } } } // no input to read on resource, buffer is empty if (null === $this->input && !isset($this->inputBuffer[0])) { fclose($this->pipes[0]); unset($this->pipes[0]); } elseif (!$w) { return array($this->pipes[0]); } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Pipes; use Symfony\Component\Process\Process; use Symfony\Component\Process\Exception\RuntimeException; /** * WindowsPipes implementation uses temporary files as handles. * * @see https://bugs.php.net/bug.php?id=51800 * @see https://bugs.php.net/bug.php?id=65650 * * @author Romain Neutron <imprec@gmail.com> * * @internal */ class WindowsPipes extends AbstractPipes { /** @var array */ private $files = array(); /** @var array */ private $fileHandles = array(); /** @var array */ private $readBytes = array( Process::STDOUT => 0, Process::STDERR => 0, ); /** @var bool */ private $disableOutput; public function __construct($disableOutput, $input) { $this->disableOutput = (bool) $disableOutput; if (!$this->disableOutput) { // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. // Workaround for this problem is to use temporary files instead of pipes on Windows platform. // // @see https://bugs.php.net/bug.php?id=51800 $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(); } /** * {@inheritdoc} */ public function getDescriptors() { if ($this->disableOutput) { $nullstream = fopen('NUL', 'c'); return array( array('pipe', 'r'), $nullstream, $nullstream, ); } // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800) // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650 // So we redirect output within the commandline and pass the nul device to the process return array( array('pipe', 'r'), array('file', 'NUL', 'w'), array('file', 'NUL', 'w'), ); } /** * {@inheritdoc} */ public function getFiles() { return $this->files; } /** * {@inheritdoc} */ 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; } /** * {@inheritdoc} */ public function areOpen() { return $this->pipes && $this->fileHandles; } /** * {@inheritdoc} */ public function close() { parent::close(); foreach ($this->fileHandles as $handle) { fclose($handle); } $this->fileHandles = array(); } /** * Creates a new WindowsPipes instance. * * @param Process $process The process * @param $input * * @return static */ public static function create(Process $process, $input) { return new static($process->isOutputDisabled(), $input); } /** * Removes temporary files. */ private function removeFiles() { foreach ($this->files as $filename) { if (file_exists($filename)) { @unlink($filename); } } $this->files = array(); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Pipes; use Symfony\Component\Process\Process; /** * UnixPipes implementation uses unix pipes as handles. * * @author Romain Neutron <imprec@gmail.com> * * @internal */ class UnixPipes extends AbstractPipes { /** @var bool */ private $ttyMode; /** @var bool */ private $ptyMode; /** @var bool */ private $disableOutput; public function __construct($ttyMode, $ptyMode, $input, $disableOutput) { $this->ttyMode = (bool) $ttyMode; $this->ptyMode = (bool) $ptyMode; $this->disableOutput = (bool) $disableOutput; parent::__construct($input); } public function __destruct() { $this->close(); } /** * {@inheritdoc} */ public function getDescriptors() { if ($this->disableOutput) { $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'), // stdout array('pipe', 'w'), // stderr ); } /** * {@inheritdoc} */ public function getFiles() { return array(); } /** * {@inheritdoc} */ public function readAndWrite($blocking, $close = false) { $this->unblock(); $w = $this->write(); $read = $e = array(); $r = $this->pipes; unset($r[0]); // let's have a look if something changed in streams if (($r || $w) && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { // if a system call has been interrupted, forget about it, let's try again // otherwise, an error occurred, let's reset pipes if (!$this->hasSystemCallBeenInterrupted()) { $this->pipes = array(); } return $read; } foreach ($r as $pipe) { // prior PHP 5.4 the array passed to stream_select is modified and // lose key association, we have to find back the key $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; } /** * {@inheritdoc} */ public function areOpen() { return (bool) $this->pipes; } /** * Creates a new UnixPipes instance. * * @param Process $process * @param string|resource $input * * @return static */ public static function create(Process $process, $input) { return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Pipes; /** * PipesInterface manages descriptors and pipes for the use of proc_open. * * @author Romain Neutron <imprec@gmail.com> * * @internal */ interface PipesInterface { const CHUNK_SIZE = 16384; /** * Returns an array of descriptors for the use of proc_open. * * @return array */ public function getDescriptors(); /** * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. * * @return string[] */ public function getFiles(); /** * Reads data in file handles and pipes. * * @param bool $blocking Whether to use blocking calls or not * @param bool $close Whether to close pipes if they've reached EOF * * @return string[] An array of read data indexed by their fd */ public function readAndWrite($blocking, $close = false); /** * Returns if the current state has open file handles or pipes. * * @return bool */ public function areOpen(); /** * Closes file handles and pipes. */ public function close(); } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process; use Symfony\Component\Process\Exception\InvalidArgumentException; /** * ProcessUtils is a bunch of utility methods. * * This class contains static methods only and is not meant to be instantiated. * * @author Martin Hasoň <martin.hason@gmail.com> */ class ProcessUtils { /** * This class should not be instantiated. */ private function __construct() { } /** * Escapes a string to be used as a shell argument. * * @param string $argument The argument that will be escaped * * @return string The escaped argument */ public static function escapeArgument($argument) { //Fix for PHP bug #43784 escapeshellarg removes % from given string //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows //@see https://bugs.php.net/bug.php?id=43784 //@see https://bugs.php.net/bug.php?id=49446 if ('\\' === DIRECTORY_SEPARATOR) { if ('' === $argument) { return escapeshellarg($argument); } $escapedArgument = ''; $quote = false; foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { if ('"' === $part) { $escapedArgument .= '\\"'; } elseif (self::isSurroundedBy($part, '%')) { // Avoid environment variable expansion $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%'; } else { // escape trailing backslash if ('\\' === substr($part, -1)) { $part .= '\\'; } $quote = true; $escapedArgument .= $part; } } if ($quote) { $escapedArgument = '"'.$escapedArgument.'"'; } return $escapedArgument; } return "'".str_replace("'", "'\\''", $argument)."'"; } /** * Validates and normalizes a Process input. * * @param string $caller The name of method call that validates the input * @param mixed $input The input to validate * * @return mixed The validated input * * @throws InvalidArgumentException In case the input is not valid * * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0. */ public static function validateInput($caller, $input) { if (null !== $input) { if (is_resource($input)) { return $input; } if (is_string($input)) { return $input; } if (is_scalar($input)) { return (string) $input; } // deprecated as of Symfony 2.5, to be removed in 3.0 if (is_object($input) && method_exists($input, '__toString')) { @trigger_error('Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); return (string) $input; } throw new InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); } return $input; } private static function isSurroundedBy($arg, $char) { return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process; /** * Generic executable finder. * * @author Fabien Potencier <fabien@symfony.com> * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ class ExecutableFinder { private $suffixes = array('.exe', '.bat', '.cmd', '.com'); /** * Replaces default suffixes of executable. * * @param array $suffixes */ public function setSuffixes(array $suffixes) { $this->suffixes = $suffixes; } /** * Adds new possible suffix to check for executable. * * @param string $suffix */ public function addSuffix($suffix) { $this->suffixes[] = $suffix; } /** * Finds an executable by name. * * @param string $name The executable name (without the extension) * @param string $default The default to return if no executable is found * @param array $extraDirs Additional dirs to check into * * @return string The executable path or default value */ 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) { // Silencing against https://bugs.php.net/69240 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; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process; use Symfony\Component\Process\Exception\RuntimeException; /** * PhpProcess runs a PHP script in an independent process. * * $p = new PhpProcess('<?php echo "foo"; ?>'); * $p->run(); * print $p->getOutput()."\n"; * * @author Fabien Potencier <fabien@symfony.com> */ class PhpProcess extends Process { /** * @param string $script The PHP script to run (as a string) * @param string|null $cwd The working directory or null to use the working dir of the current PHP process * @param array|null $env The environment variables or null to use the same environment as the current PHP process * @param int $timeout The timeout in seconds * @param array $options An array of options for proc_open */ public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = array()) { $executableFinder = new PhpExecutableFinder(); if (false === $php = $executableFinder->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) { // exec is mandatory to deal with sending a signal to the process // see https://github.com/symfony/symfony/issues/5030 about prepending // command with exec $php = 'exec '.$php; } parent::__construct($php, $cwd, $env, $script, $timeout, $options); } /** * Sets the path to the PHP binary to use. */ public function setPhpBinary($php) { $this->setCommandLine($php); } /** * {@inheritdoc} */ public function start($callback = null) { if (null === $this->getCommandLine()) { throw new RuntimeException('Unable to find the PHP executable.'); } parent::start($callback); } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Exception; /** * Marker Interface for the Process Component. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ interface ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Exception; use Symfony\Component\Process\Process; /** * Exception that is thrown when a process times out. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ class ProcessTimedOutException extends RuntimeException { const TYPE_GENERAL = 1; const TYPE_IDLE = 2; private $process; private $timeoutType; public function __construct(Process $process, $timeoutType) { $this->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 self::TYPE_GENERAL === $this->timeoutType; } public function isIdleTimeout() { return self::TYPE_IDLE === $this->timeoutType; } 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)); } } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Exception; use Symfony\Component\Process\Process; /** * Exception for failed processes. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ class ProcessFailedException extends RuntimeException { private $process; public function __construct(Process $process) { if ($process->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; } } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Exception; /** * LogicException for the Process Component. * * @author Romain Neutron <imprec@gmail.com> */ class LogicException extends \LogicException implements ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Exception; /** * RuntimeException for the Process Component. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Exception; /** * InvalidArgumentException for the Process Component. * * @author Romain Neutron <imprec@gmail.com> */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } <?php require __DIR__ . '/../vendor/autoload.php'; $data = json_decode(file_get_contents('data.json')); // Validate $validator = new JsonSchema\Validator(); $validator->check($data, (object) array('$ref' => 'file://' . realpath('schema.json'))); if ($validator->isValid()) { echo "The supplied JSON validates against the schema.\n"; } else { echo "JSON does not validate. Violations:\n"; foreach ($validator->getErrors() as $error) { echo sprintf("[%s] %s\n", $error['property'], $error['message']); } } <?php namespace JsonSchema; use JsonSchema\Constraints\BaseConstraint; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\UnresolvableJsonPointerException; use JsonSchema\Uri\UriResolver; use JsonSchema\Uri\UriRetriever; class SchemaStorage implements SchemaStorageInterface { const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema'; protected $uriRetriever; protected $uriResolver; protected $schemas = array(); public function __construct( UriRetrieverInterface $uriRetriever = null, UriResolverInterface $uriResolver = null ) { $this->uriRetriever = $uriRetriever ?: new UriRetriever(); $this->uriResolver = $uriResolver ?: new UriResolver(); } /** * @return UriRetrieverInterface */ public function getUriRetriever() { return $this->uriRetriever; } /** * @return UriResolverInterface */ public function getUriResolver() { return $this->uriResolver; } /** * {@inheritdoc} */ public function addSchema($id, $schema = null) { if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { // if the schema was user-provided to Validator and is still null, then assume this is // what the user intended, as there's no way for us to retrieve anything else. User-supplied // schemas do not have an associated URI when passed via Validator::validate(). $schema = $this->uriRetriever->retrieve($id); } // cast array schemas to object if (is_array($schema)) { $schema = BaseConstraint::arrayToObjectRecursive($schema); } // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format) // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367 if (is_object($schema) && property_exists($schema, 'id')) { if ($schema->id == 'http://json-schema.org/draft-04/schema#') { $schema->properties->id->format = 'uri-reference'; } elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') { $schema->properties->id->format = 'uri-reference'; $schema->properties->{'$ref'}->format = 'uri-reference'; } } // resolve references $this->expandRefs($schema, $id); $this->schemas[$id] = $schema; } /** * Recursively resolve all references against the provided base * * @param mixed $schema * @param string $base */ private function expandRefs(&$schema, $base = null) { if (!is_object($schema)) { if (is_array($schema)) { foreach ($schema as &$member) { $this->expandRefs($member, $base); } } return; } if (property_exists($schema, 'id') && is_string($schema->id) && $base != $schema->id) { $base = $this->uriResolver->resolve($schema->id, $base); } if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base)); $schema->{'$ref'} = (string) $refPointer; } foreach ($schema as &$member) { $this->expandRefs($member, $base); } } /** * {@inheritdoc} */ public function getSchema($id) { if (!array_key_exists($id, $this->schemas)) { $this->addSchema($id); } return $this->schemas[$id]; } /** * {@inheritdoc} */ public function resolveRef($ref) { $jsonPointer = new JsonPointer($ref); // resolve filename for pointer $fileName = $jsonPointer->getFilename(); if (!strlen($fileName)) { throw new UnresolvableJsonPointerException(sprintf( "Could not resolve fragment '%s': no file is defined", $jsonPointer->getPropertyPathAsString() )); } // get & process the schema $refSchema = $this->getSchema($fileName); foreach ($jsonPointer->getPropertyPaths() as $path) { if (is_object($refSchema) && property_exists($refSchema, $path)) { $refSchema = $this->resolveRefSchema($refSchema->{$path}); } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) { $refSchema = $this->resolveRefSchema($refSchema[$path]); } else { throw new UnresolvableJsonPointerException(sprintf( 'File: %s is found, but could not resolve fragment: %s', $jsonPointer->getFilename(), $jsonPointer->getPropertyPathAsString() )); } } return $refSchema; } /** * {@inheritdoc} */ public function resolveRefSchema($refSchema) { if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) { $newSchema = $this->resolveRef($refSchema->{'$ref'}); $refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema)); unset($refSchema->{'$ref'}); } return $refSchema; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Iterator; /** * @package JsonSchema\Iterator * * @author Joost Nijhuis <jnijhuis81@gmail.com> */ class ObjectIterator implements \Iterator, \Countable { /** @var object */ private $object; /** @var int */ private $position = 0; /** @var array */ private $data = array(); /** @var bool */ private $initialized = false; /** * @param object $object */ public function __construct($object) { $this->object = $object; } /** * {@inheritdoc} */ public function current() { $this->initialize(); return $this->data[$this->position]; } /** * {@inheritdoc} */ public function next() { $this->initialize(); $this->position++; } /** * {@inheritdoc} */ public function key() { $this->initialize(); return $this->position; } /** * {@inheritdoc} */ public function valid() { $this->initialize(); return isset($this->data[$this->position]); } /** * {@inheritdoc} */ public function rewind() { $this->initialize(); $this->position = 0; } /** * {@inheritdoc} */ public function count() { $this->initialize(); return count($this->data); } /** * Initializer */ private function initialize() { if (!$this->initialized) { $this->data = $this->buildDataFromObject($this->object); $this->initialized = true; } } /** * @param object $object * * @return array */ private function buildDataFromObject($object) { $result = array(); $stack = new \SplStack(); $stack->push($object); while (!$stack->isEmpty()) { $current = $stack->pop(); if (is_object($current)) { array_push($result, $current); } foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) { if (is_object($propertyValue) || is_array($propertyValue)) { $stack->push($propertyValue); } } } return $result; } /** * @param object|array $item * * @return array */ private function getDataFromItem($item) { if (!is_object($item) && !is_array($item)) { return array(); } return is_object($item) ? get_object_vars($item) : $item; } } <?php namespace JsonSchema; class Rfc3339 { const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ]{1}\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/'; /** * Try creating a DateTime instance * * @param string $string * * @return \DateTime|null */ public static function createFromString($string) { if (!preg_match(self::REGEX, strtoupper($string), $matches)) { return null; } $dateAndTime = $matches[1]; $microseconds = $matches[2] ?: '.000000'; $timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00'; $dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP'; $dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC')); return $dateTime ?: null; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Uri; use JsonSchema\Exception\UriResolverException; use JsonSchema\UriResolverInterface; /** * Resolves JSON Schema URIs * * @author Sander Coolen <sander@jibber.nl> */ class UriResolver implements UriResolverInterface { /** * Parses a URI into five main components * * @param string $uri * * @return array */ public function parse($uri) { preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); $components = array(); if (5 < count($match)) { $components = array( 'scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5] ); } if (7 < count($match)) { $components['query'] = $match[7]; } if (9 < count($match)) { $components['fragment'] = $match[9]; } return $components; } /** * Builds a URI based on n array with the main components * * @param array $components * * @return string */ public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (array_key_exists('query', $components) && strlen($components['query'])) { $uri .= '?' . $components['query']; } if (array_key_exists('fragment', $components)) { $uri .= '#' . $components['fragment']; } return $uri; } /** * {@inheritdoc} */ public function resolve($uri, $baseUri = null) { // treat non-uri base as local file path if ( !is_null($baseUri) && !filter_var($baseUri, \FILTER_VALIDATE_URL) && !preg_match('|^[^/]+://|u', $baseUri) ) { if (is_file($baseUri)) { $baseUri = 'file://' . realpath($baseUri); } elseif (is_dir($baseUri)) { $baseUri = 'file://' . realpath($baseUri) . '/'; } else { $baseUri = 'file://' . getcwd() . '/' . $baseUri; } } if ($uri == '') { return $baseUri; } $components = $this->parse($uri); $path = $components['path']; if (!empty($components['scheme'])) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath); if (isset($components['fragment'])) { $baseComponents['fragment'] = $components['fragment']; } return $this->generate($baseComponents); } /** * Tries to glue a relative path onto an absolute one * * @param string $relativePath * @param string $basePath * * @throws UriResolverException * * @return string Merged path */ public static function combineRelativePathWithBasePath($relativePath, $basePath) { $relativePath = self::normalizePath($relativePath); if ($relativePath == '') { return $basePath; } if ($relativePath[0] == '/') { return $relativePath; } $basePathSegments = explode('/', $basePath); preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match); $numLevelUp = strlen($match[0]) /3 + 1; if ($numLevelUp >= count($basePathSegments)) { throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); } $basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp); $path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath); return implode('/', $basePathSegments) . '/' . $path; } /** * Normalizes a URI path component by removing dot-slash and double slashes * * @param string $path * * @return string */ private static function normalizePath($path) { $path = preg_replace('|((?<!\.)\./)*|', '', $path); $path = preg_replace('|//|', '/', $path); return $path; } /** * @param string $uri * * @return bool */ public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } } <?php /** * JsonSchema * * @filesource */ namespace JsonSchema\Uri\Retrievers; /** * AbstractRetriever implements the default shared behavior * that all descendant Retrievers should inherit * * @author Steven Garcia <webwhammy@gmail.com> */ abstract class AbstractRetriever implements UriRetrieverInterface { /** * Media content type * * @var string */ protected $contentType; /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType() */ public function getContentType() { return $this->contentType; } } <?php namespace JsonSchema\Uri\Retrievers; use JsonSchema\Validator; /** * URI retrieved based on a predefined array of schemas * * @example * * $retriever = new PredefinedArray(array( * 'http://acme.com/schemas/person#' => '{ ... }', * 'http://acme.com/schemas/address#' => '{ ... }', * )) * * $schema = $retriever->retrieve('http://acme.com/schemas/person#'); */ class PredefinedArray extends AbstractRetriever { /** * Contains schemas as URI => JSON * * @var array */ private $schemas; /** * Constructor * * @param array $schemas * @param string $contentType */ public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE) { $this->schemas = $schemas; $this->contentType = $contentType; } /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { if (!array_key_exists($uri, $this->schemas)) { throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf( 'The JSON schema "%s" was not found.', $uri )); } return $this->schemas[$uri]; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Uri\Retrievers; /** * Interface for URI retrievers * * @author Sander Coolen <sander@jibber.nl> */ interface UriRetrieverInterface { /** * Retrieve a schema from the specified URI * * @param string $uri URI that resolves to a JSON schema * * @throws \JsonSchema\Exception\ResourceNotFoundException * * @return mixed string|null */ public function retrieve($uri); /** * Get media content type * * @return string */ public function getContentType(); } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Uri\Retrievers; use JsonSchema\Exception\ResourceNotFoundException; /** * Tries to retrieve JSON schemas from a URI using file_get_contents() * * @author Sander Coolen <sander@jibber.nl> */ class FileGetContents extends AbstractRetriever { protected $messageBody; /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { $errorMessage = null; set_error_handler(function ($errno, $errstr) use (&$errorMessage) { $errorMessage = $errstr; }); $response = file_get_contents($uri); restore_error_handler(); if ($errorMessage) { throw new ResourceNotFoundException($errorMessage); } if (false === $response) { throw new ResourceNotFoundException('JSON schema not found at ' . $uri); } if ($response == '' && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/' ) { throw new ResourceNotFoundException('JSON schema not found at ' . $uri); } $this->messageBody = $response; if (!empty($http_response_header)) { // $http_response_header cannot be tested, because it's defined in the method's local scope // See http://php.net/manual/en/reserved.variables.httpresponseheader.php for more info. $this->fetchContentType($http_response_header); // @codeCoverageIgnore } else { // @codeCoverageIgnore // Could be a "file://" url or something else - fake up the response $this->contentType = null; } return $this->messageBody; } /** * @param array $headers HTTP Response Headers * * @return bool Whether the Content-Type header was found or not */ private function fetchContentType(array $headers) { foreach ($headers as $header) { if ($this->contentType = self::getContentTypeMatchInHeader($header)) { return true; } } return false; } /** * @param string $header * * @return string|null */ protected static function getContentTypeMatchInHeader($header) { if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) { return trim($match[1]); } return null; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Uri\Retrievers; use JsonSchema\Exception\RuntimeException; use JsonSchema\Validator; /** * Tries to retrieve JSON schemas from a URI using cURL library * * @author Sander Coolen <sander@jibber.nl> */ class Curl extends AbstractRetriever { protected $messageBody; public function __construct() { if (!function_exists('curl_init')) { // Cannot test this, because curl_init is present on all test platforms plus mock throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore } } /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $uri); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE)); $response = curl_exec($ch); if (false === $response) { throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found'); } $this->fetchMessageBody($response); $this->fetchContentType($response); curl_close($ch); return $this->messageBody; } /** * @param string $response cURL HTTP response */ private function fetchMessageBody($response) { preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); $this->messageBody = $match[1]; } /** * @param string $response cURL HTTP response * * @return bool Whether the Content-Type header was found or not */ protected function fetchContentType($response) { if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { $this->contentType = trim($match[1]); return true; } return false; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Uri; use JsonSchema\Exception\InvalidSchemaMediaTypeException; use JsonSchema\Exception\JsonDecodingException; use JsonSchema\Exception\ResourceNotFoundException; use JsonSchema\Uri\Retrievers\FileGetContents; use JsonSchema\Uri\Retrievers\UriRetrieverInterface; use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface; use JsonSchema\Validator; /** * Retrieves JSON Schema URIs * * @author Tyler Akins <fidian@rumkin.com> */ class UriRetriever implements BaseUriRetrieverInterface { /** * @var array Map of URL translations */ protected $translationMap = array( // use local copies of the spec schemas '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json' ); /** * @var null|UriRetrieverInterface */ protected $uriRetriever = null; /** * @var array|object[] * * @see loadSchema */ private $schemaCache = array(); /** * Guarantee the correct media type was encountered * * @param UriRetrieverInterface $uriRetriever * @param string $uri * * @return bool|void */ public function confirmMediaType($uriRetriever, $uri) { $contentType = $uriRetriever->getContentType(); if (is_null($contentType)) { // Well, we didn't get an invalid one return; } if (in_array($contentType, array(Validator::SCHEMA_MEDIA_TYPE, 'application/json'))) { return; } if (substr($uri, 0, 23) == 'http://json-schema.org/') { //HACK; they deliver broken content types return true; } throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE)); } /** * Get a URI Retriever * * If none is specified, sets a default FileGetContents retriever and * returns that object. * * @return UriRetrieverInterface */ public function getUriRetriever() { if (is_null($this->uriRetriever)) { $this->setUriRetriever(new FileGetContents()); } return $this->uriRetriever; } /** * Resolve a schema based on pointer * * URIs can have a fragment at the end in the format of * #/path/to/object and we are to look up the 'path' property of * the first object then the 'to' and 'object' properties. * * @param object $jsonSchema JSON Schema contents * @param string $uri JSON Schema URI * * @throws ResourceNotFoundException * * @return object JSON Schema after walking down the fragment pieces */ public function resolvePointer($jsonSchema, $uri) { $resolver = new UriResolver(); $parsed = $resolver->parse($uri); if (empty($parsed['fragment'])) { return $jsonSchema; } $path = explode('/', $parsed['fragment']); while ($path) { $pathElement = array_shift($path); if (!empty($pathElement)) { $pathElement = str_replace('~1', '/', $pathElement); $pathElement = str_replace('~0', '~', $pathElement); if (!empty($jsonSchema->$pathElement)) { $jsonSchema = $jsonSchema->$pathElement; } else { throw new ResourceNotFoundException( 'Fragment "' . $parsed['fragment'] . '" not found' . ' in ' . $uri ); } if (!is_object($jsonSchema)) { throw new ResourceNotFoundException( 'Fragment part "' . $pathElement . '" is no object ' . ' in ' . $uri ); } } } return $jsonSchema; } /** * {@inheritdoc} */ public function retrieve($uri, $baseUri = null, $translate = true) { $resolver = new UriResolver(); $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); //fetch URL without #fragment $arParts = $resolver->parse($resolvedUri); if (isset($arParts['fragment'])) { unset($arParts['fragment']); $fetchUri = $resolver->generate($arParts); } // apply URI translations if ($translate) { $fetchUri = $this->translate($fetchUri); } $jsonSchema = $this->loadSchema($fetchUri); // Use the JSON pointer if specified $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); if ($jsonSchema instanceof \stdClass) { $jsonSchema->id = $resolvedUri; } return $jsonSchema; } /** * Fetch a schema from the given URI, json-decode it and return it. * Caches schema objects. * * @param string $fetchUri Absolute URI * * @return object JSON schema object */ protected function loadSchema($fetchUri) { if (isset($this->schemaCache[$fetchUri])) { return $this->schemaCache[$fetchUri]; } $uriRetriever = $this->getUriRetriever(); $contents = $this->uriRetriever->retrieve($fetchUri); $this->confirmMediaType($uriRetriever, $fetchUri); $jsonSchema = json_decode($contents); if (JSON_ERROR_NONE < $error = json_last_error()) { throw new JsonDecodingException($error); } $this->schemaCache[$fetchUri] = $jsonSchema; return $jsonSchema; } /** * Set the URI Retriever * * @param UriRetrieverInterface $uriRetriever * * @return $this for chaining */ public function setUriRetriever(UriRetrieverInterface $uriRetriever) { $this->uriRetriever = $uriRetriever; return $this; } /** * Parses a URI into five main components * * @param string $uri * * @return array */ public function parse($uri) { preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); $components = array(); if (5 < count($match)) { $components = array( 'scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5] ); } if (7 < count($match)) { $components['query'] = $match[7]; } if (9 < count($match)) { $components['fragment'] = $match[9]; } return $components; } /** * Builds a URI based on n array with the main components * * @param array $components * * @return string */ public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (array_key_exists('query', $components)) { $uri .= $components['query']; } if (array_key_exists('fragment', $components)) { $uri .= $components['fragment']; } return $uri; } /** * Resolves a URI * * @param string $uri Absolute or relative * @param string $baseUri Optional base URI * * @return string */ public function resolve($uri, $baseUri = null) { $components = $this->parse($uri); $path = $components['path']; if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath); return $this->generate($baseComponents); } /** * @param string $uri * * @return bool */ public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } /** * Set a URL translation rule */ public function setTranslation($from, $to) { $this->translationMap[$from] = $to; } /** * Apply URI translation rules */ public function translate($uri) { foreach ($this->translationMap as $from => $to) { $uri = preg_replace($from, $to, $uri); } // translate references to local files within the json-schema package $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri); return $uri; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema; /** * @package JsonSchema */ interface UriResolverInterface { /** * Resolves a URI * * @param string $uri Absolute or relative * @param null|string $baseUri Optional base URI * * @return string Absolute URI */ public function resolve($uri, $baseUri = null); } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema; /** * @package JsonSchema */ interface UriRetrieverInterface { /** * Retrieve a URI * * @param string $uri JSON Schema URI * @param null|string $baseUri * * @return object JSON Schema contents */ public function retrieve($uri, $baseUri = null); } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema; use JsonSchema\Constraints\BaseConstraint; use JsonSchema\Constraints\Constraint; use JsonSchema\Exception\InvalidConfigException; use JsonSchema\SchemaStorage; /** * A JsonSchema Constraint * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> * * @see README.md */ class Validator extends BaseConstraint { const SCHEMA_MEDIA_TYPE = 'application/schema+json'; const ERROR_NONE = 0x00000000; const ERROR_ALL = 0xFFFFFFFF; const ERROR_DOCUMENT_VALIDATION = 0x00000001; const ERROR_SCHEMA_VALIDATION = 0x00000002; /** * Validates the given data against the schema and returns an object containing the results * Both the php object and the schema are supposed to be a result of a json_decode call. * The validation works as defined by the schema proposal in http://json-schema.org. * * Note that the first argument is passwd by reference, so you must pass in a variable. * * {@inheritdoc} */ public function validate(&$value, $schema = null, $checkMode = null) { // make sure $schema is an object if (is_array($schema)) { $schema = self::arrayToObjectRecursive($schema); } // set checkMode $initialCheckMode = $this->factory->getConfig(); if ($checkMode !== null) { $this->factory->setConfig($checkMode); } // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution if (is_object($schema) && property_exists($schema, 'id')) { $schemaURI = $schema->id; } else { $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI; } $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema); $validator = $this->factory->createInstanceFor('schema'); $validator->check( $value, $this->factory->getSchemaStorage()->getSchema($schemaURI) ); $this->factory->setConfig($initialCheckMode); $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); return $validator->getErrorMask(); } /** * Alias to validate(), to maintain backwards-compatibility with the previous API */ public function check($value, $schema) { return $this->validate($value, $schema); } /** * Alias to validate(), to maintain backwards-compatibility with the previous API */ public function coerce(&$value, $schema) { return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES); } } <?php namespace JsonSchema; interface SchemaStorageInterface { /** * Adds schema with given identifier * * @param string $id * @param object $schema */ public function addSchema($id, $schema = null); /** * Returns schema for given identifier, or null if it does not exist * * @param string $id * * @return object */ public function getSchema($id); /** * Returns schema for given reference with all sub-references resolved * * @param string $ref * * @return object */ public function resolveRef($ref); /** * Returns schema referenced by '$ref' property * * @param mixed $refSchema * * @return object */ public function resolveRefSchema($refSchema); } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Constraints\Constraint; use JsonSchema\Exception\InvalidArgumentException; use JsonSchema\Exception\InvalidConfigException; use JsonSchema\SchemaStorage; use JsonSchema\SchemaStorageInterface; use JsonSchema\Uri\UriRetriever; use JsonSchema\UriRetrieverInterface; use JsonSchema\Validator; /** * Factory for centralize constraint initialization. */ class Factory { /** * @var SchemaStorage */ protected $schemaStorage; /** * @var UriRetriever */ protected $uriRetriever; /** * @var int */ private $checkMode = Constraint::CHECK_MODE_NORMAL; /** * @var TypeCheck\TypeCheckInterface[] */ private $typeCheck = array(); /** * @var int Validation context */ protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION; /** * @var array */ protected $constraintMap = array( 'array' => 'JsonSchema\Constraints\CollectionConstraint', 'collection' => 'JsonSchema\Constraints\CollectionConstraint', 'object' => 'JsonSchema\Constraints\ObjectConstraint', 'type' => 'JsonSchema\Constraints\TypeConstraint', 'undefined' => 'JsonSchema\Constraints\UndefinedConstraint', 'string' => 'JsonSchema\Constraints\StringConstraint', 'number' => 'JsonSchema\Constraints\NumberConstraint', 'enum' => 'JsonSchema\Constraints\EnumConstraint', 'format' => 'JsonSchema\Constraints\FormatConstraint', 'schema' => 'JsonSchema\Constraints\SchemaConstraint', 'validator' => 'JsonSchema\Validator' ); /** * @var array<ConstraintInterface> */ private $instanceCache = array(); /** * @param SchemaStorage $schemaStorage * @param UriRetrieverInterface $uriRetriever * @param int $checkMode */ public function __construct( SchemaStorageInterface $schemaStorage = null, UriRetrieverInterface $uriRetriever = null, $checkMode = Constraint::CHECK_MODE_NORMAL ) { // set provided config options $this->setConfig($checkMode); $this->uriRetriever = $uriRetriever ?: new UriRetriever(); $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); } /** * Set config values * * @param int $checkMode Set checkMode options - does not preserve existing flags */ public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL) { $this->checkMode = $checkMode; } /** * Enable checkMode flags * * @param int $options */ public function addConfig($options) { $this->checkMode |= $options; } /** * Disable checkMode flags * * @param int $options */ public function removeConfig($options) { $this->checkMode &= ~$options; } /** * Get checkMode option * * @param int $options Options to get, if null then return entire bitmask * * @return int */ public function getConfig($options = null) { if ($options === null) { return $this->checkMode; } return $this->checkMode & $options; } /** * @return UriRetrieverInterface */ public function getUriRetriever() { return $this->uriRetriever; } public function getSchemaStorage() { return $this->schemaStorage; } public function getTypeCheck() { if (!isset($this->typeCheck[$this->checkMode])) { $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST) ? new TypeCheck\LooseTypeCheck() : new TypeCheck\StrictTypeCheck(); } return $this->typeCheck[$this->checkMode]; } /** * @param string $name * @param string $class * * @return Factory */ public function setConstraintClass($name, $class) { // Ensure class exists if (!class_exists($class)) { throw new InvalidArgumentException('Unknown constraint ' . $name); } // Ensure class is appropriate if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { throw new InvalidArgumentException('Invalid class ' . $name); } $this->constraintMap[$name] = $class; return $this; } /** * Create a constraint instance for the given constraint name. * * @param string $constraintName * * @throws InvalidArgumentException if is not possible create the constraint instance * * @return ConstraintInterface|ObjectConstraint */ public function createInstanceFor($constraintName) { if (!isset($this->constraintMap[$constraintName])) { throw new InvalidArgumentException('Unknown constraint ' . $constraintName); } if (!isset($this->instanceCache[$constraintName])) { $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this); } return clone $this->instanceCache[$constraintName]; } /** * Get the error context * * @return string */ public function getErrorContext() { return $this->errorContext; } /** * Set the error context * * @param string $validationContext */ public function setErrorContext($errorContext) { $this->errorContext = $errorContext; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Constraints\TypeCheck\LooseTypeCheck; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\ValidationException; use JsonSchema\Uri\UriResolver; /** * The UndefinedConstraint Constraints * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class UndefinedConstraint extends Constraint { /** * @var array List of properties to which a default value has been applied */ protected $appliedDefaults = array(); /** * {@inheritdoc} */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false) { if (is_null($schema) || !is_object($schema)) { return; } $path = $this->incrementPath($path ?: new JsonPointer(''), $i); if ($fromDefault) { $path->setFromDefault(); } // check special properties $this->validateCommonProperties($value, $schema, $path, $i); // check allOf, anyOf, and oneOf properties $this->validateOfProperties($value, $schema, $path, ''); // check known types $this->validateTypes($value, $schema, $path, $i); } /** * Validates the value against the types * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = null) { // check array if ($this->getTypeCheck()->isArray($value)) { $this->checkArray($value, $schema, $path, $i); } // check object if (LooseTypeCheck::isObject($value)) { // object processing should always be run on assoc arrays, // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST // is not set (i.e. don't use $this->getTypeCheck() here). $this->checkObject( $value, $schema, $path, isset($schema->properties) ? $schema->properties : null, isset($schema->additionalProperties) ? $schema->additionalProperties : null, isset($schema->patternProperties) ? $schema->patternProperties : null, $this->appliedDefaults ); } // check string if (is_string($value)) { $this->checkString($value, $schema, $path, $i); } // check numeric if (is_numeric($value)) { $this->checkNumber($value, $schema, $path, $i); } // check enum if (isset($schema->enum)) { $this->checkEnum($value, $schema, $path, $i); } } /** * Validates common properties * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ protected function validateCommonProperties(&$value, $schema = null, JsonPointer $path, $i = '') { // if it extends another schema, it must pass that schema as well if (isset($schema->extends)) { if (is_string($schema->extends)) { $schema->extends = $this->validateUri($schema, $schema->extends); } if (is_array($schema->extends)) { foreach ($schema->extends as $extends) { $this->checkUndefined($value, $extends, $path, $i); } } else { $this->checkUndefined($value, $schema->extends, $path, $i); } } // Apply default values from schema if (!$path->fromDefault()) { $this->applyDefaultValues($value, $schema, $path); } // Verify required values if ($this->getTypeCheck()->isObject($value)) { if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) { // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...] foreach ($schema->required as $required) { if (!$this->getTypeCheck()->propertyExists($value, $required)) { $this->addError( $this->incrementPath($path ?: new JsonPointer(''), $required), 'The property ' . $required . ' is required', 'required' ); } } } elseif (isset($schema->required) && !is_array($schema->required)) { // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} if ($schema->required && $value instanceof self) { $propertyPaths = $path->getPropertyPaths(); $propertyName = end($propertyPaths); $this->addError( $path, 'The property ' . $propertyName . ' is required', 'required' ); } } } // Verify type if (!($value instanceof self)) { $this->checkType($value, $schema, $path, $i); } // Verify disallowed items if (isset($schema->disallow)) { $initErrors = $this->getErrors(); $typeSchema = new \stdClass(); $typeSchema->type = $schema->disallow; $this->checkType($value, $typeSchema, $path); // if no new errors were raised it must be a disallowed value if (count($this->getErrors()) == count($initErrors)) { $this->addError($path, 'Disallowed value was matched', 'disallow'); } else { $this->errors = $initErrors; } } if (isset($schema->not)) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $schema->not, $path, $i); // if no new errors were raised then the instance validated against the "not" schema if (count($this->getErrors()) == count($initErrors)) { $this->addError($path, 'Matched a schema which it should not', 'not'); } else { $this->errors = $initErrors; } } // Verify that dependencies are met if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) { $this->validateDependencies($value, $schema->dependencies, $path); } } /** * Check whether a default should be applied for this value * * @param mixed $schema * @param mixed $parentSchema * @param bool $requiredOnly * * @return bool */ private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null) { // required-only mode is off if (!$requiredOnly) { return true; } // draft-04 required is set if ( $name !== null && isset($parentSchema->required) && is_array($parentSchema->required) && in_array($name, $parentSchema->required) ) { return true; } // draft-03 required is set if (isset($schema->required) && !is_array($schema->required) && $schema->required) { return true; } // default case return false; } /** * Apply default values * * @param mixed $value * @param mixed $schema * @param JsonPointer $path */ protected function applyDefaultValues(&$value, $schema, $path) { // only apply defaults if feature is enabled if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { return; } // apply defaults if appropriate $requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS); if (isset($schema->properties) && LooseTypeCheck::isObject($value)) { // $value is an object or assoc array, and properties are defined - treat as an object foreach ($schema->properties as $currentProperty => $propertyDefinition) { if ( !LooseTypeCheck::propertyExists($value, $currentProperty) && property_exists($propertyDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema) ) { // assign default value if (is_object($propertyDefinition->default)) { LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default); } else { LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default); } $this->appliedDefaults[] = $currentProperty; } } } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) { $items = array(); if (LooseTypeCheck::isArray($schema->items)) { $items = $schema->items; } elseif (isset($schema->minItems) && count($value) < $schema->minItems) { $items = array_fill(count($value), $schema->minItems - count($value), $schema->items); } // $value is an array, and items are defined - treat as plain array foreach ($items as $currentItem => $itemDefinition) { if ( !array_key_exists($currentItem, $value) && property_exists($itemDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) { if (is_object($itemDefinition->default)) { $value[$currentItem] = clone $itemDefinition->default; } else { $value[$currentItem] = $itemDefinition->default; } } $path->setFromDefault(); } } elseif ( $value instanceof self && property_exists($schema, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $schema)) { // $value is a leaf, not a container - apply the default directly $value = is_object($schema->default) ? clone $schema->default : $schema->default; $path->setFromDefault(); } } /** * Validate allOf, anyOf, and oneOf properties * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '') { // Verify type if ($value instanceof self) { return; } if (isset($schema->allOf)) { $isValid = true; foreach ($schema->allOf as $allOf) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $allOf, $path, $i); $isValid = $isValid && (count($this->getErrors()) == count($initErrors)); } if (!$isValid) { $this->addError($path, 'Failed to match all schemas', 'allOf'); } } if (isset($schema->anyOf)) { $isValid = false; $startErrors = $this->getErrors(); $caughtException = null; foreach ($schema->anyOf as $anyOf) { $initErrors = $this->getErrors(); try { $this->checkUndefined($value, $anyOf, $path, $i); if ($isValid = (count($this->getErrors()) == count($initErrors))) { break; } } catch (ValidationException $e) { $isValid = false; } } if (!$isValid) { $this->addError($path, 'Failed to match at least one schema', 'anyOf'); } else { $this->errors = $startErrors; } } if (isset($schema->oneOf)) { $allErrors = array(); $matchedSchemas = 0; $startErrors = $this->getErrors(); foreach ($schema->oneOf as $oneOf) { try { $this->errors = array(); $this->checkUndefined($value, $oneOf, $path, $i); if (count($this->getErrors()) == 0) { $matchedSchemas++; } $allErrors = array_merge($allErrors, array_values($this->getErrors())); } catch (ValidationException $e) { // deliberately do nothing here - validation failed, but we want to check // other schema options in the OneOf field. } } if ($matchedSchemas !== 1) { $this->addErrors(array_merge($allErrors, $startErrors)); $this->addError($path, 'Failed to match exactly one schema', 'oneOf'); } else { $this->errors = $startErrors; } } } /** * Validate dependencies * * @param mixed $value * @param mixed $dependencies * @param JsonPointer $path * @param string $i */ protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '') { foreach ($dependencies as $key => $dependency) { if ($this->getTypeCheck()->propertyExists($value, $key)) { if (is_string($dependency)) { // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} if (!$this->getTypeCheck()->propertyExists($value, $dependency)) { $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies'); } } elseif (is_array($dependency)) { // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} foreach ($dependency as $d) { if (!$this->getTypeCheck()->propertyExists($value, $d)) { $this->addError($path, "$key depends on $d and $d is missing", 'dependencies'); } } } elseif (is_object($dependency)) { // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}} $this->checkUndefined($value, $dependency, $path, $i); } } } } protected function validateUri($schema, $schemaUri = null) { $resolver = new UriResolver(); $retriever = $this->factory->getUriRetriever(); $jsonSchema = null; if ($resolver->isValid($schemaUri)) { $schemaId = property_exists($schema, 'id') ? $schema->id : null; $jsonSchema = $retriever->retrieve($schemaId, $schemaUri); } return $jsonSchema; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\InvalidArgumentException; use JsonSchema\Exception\ValidationException; use JsonSchema\Validator; /** * A more basic constraint definition - used for the public * interface to avoid exposing library internals. */ class BaseConstraint { /** * @var array Errors */ protected $errors = array(); /** * @var int All error types which have occurred */ protected $errorMask = Validator::ERROR_NONE; /** * @var Factory */ protected $factory; /** * @param Factory $factory */ public function __construct(Factory $factory = null) { $this->factory = $factory ?: new Factory(); } public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null) { $error = array( 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), 'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'), 'message' => $message, 'constraint' => $constraint, 'context' => $this->factory->getErrorContext(), ); if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); } if (is_array($more) && count($more) > 0) { $error += $more; } $this->errors[] = $error; $this->errorMask |= $error['context']; } public function addErrors(array $errors) { if ($errors) { $this->errors = array_merge($this->errors, $errors); $errorMask = &$this->errorMask; array_walk($errors, function ($error) use (&$errorMask) { if (isset($error['context'])) { $errorMask |= $error['context']; } }); } } public function getErrors($errorContext = Validator::ERROR_ALL) { if ($errorContext === Validator::ERROR_ALL) { return $this->errors; } return array_filter($this->errors, function ($error) use ($errorContext) { if ($errorContext & $error['context']) { return true; } }); } public function numErrors($errorContext = Validator::ERROR_ALL) { if ($errorContext === Validator::ERROR_ALL) { return count($this->errors); } return count($this->getErrors($errorContext)); } public function isValid() { return !$this->getErrors(); } /** * Clears any reported errors. Should be used between * multiple validation checks. */ public function reset() { $this->errors = array(); $this->errorMask = Validator::ERROR_NONE; } /** * Get the error mask * * @return int */ public function getErrorMask() { return $this->errorMask; } /** * Recursively cast an associative array to an object * * @param array $array * * @return object */ public static function arrayToObjectRecursive($array) { $json = json_encode($array); if (json_last_error() !== \JSON_ERROR_NONE) { $message = 'Unable to encode schema array as JSON'; if (function_exists('json_last_error_msg')) { $message .= ': ' . json_last_error_msg(); } throw new InvalidArgumentException($message); } return (object) json_decode($json); } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\SchemaStorage; use JsonSchema\Uri\UriRetriever; use JsonSchema\UriRetrieverInterface; /** * The Base Constraints, all Validators should extend this class * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ abstract class Constraint extends BaseConstraint implements ConstraintInterface { protected $inlineSchemaProperty = '$schema'; const CHECK_MODE_NONE = 0x00000000; const CHECK_MODE_NORMAL = 0x00000001; const CHECK_MODE_TYPE_CAST = 0x00000002; const CHECK_MODE_COERCE_TYPES = 0x00000004; const CHECK_MODE_APPLY_DEFAULTS = 0x00000008; const CHECK_MODE_EXCEPTIONS = 0x00000010; const CHECK_MODE_DISABLE_FORMAT = 0x00000020; const CHECK_MODE_ONLY_REQUIRED_DEFAULTS = 0x00000080; const CHECK_MODE_VALIDATE_SCHEMA = 0x00000100; /** * Bubble down the path * * @param JsonPointer|null $path Current path * @param mixed $i What to append to the path * * @return JsonPointer; */ protected function incrementPath(JsonPointer $path = null, $i) { $path = $path ?: new JsonPointer(''); $path = $path->withPropertyPaths( array_merge( $path->getPropertyPaths(), array_filter(array($i), 'strlen') ) ); return $path; } /** * Validates an array * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('collection'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Validates an object * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $properties * @param mixed $additionalProperties * @param mixed $patternProperties */ protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $properties = null, $additionalProperties = null, $patternProperties = null, $appliedDefaults = array()) { $validator = $this->factory->createInstanceFor('object'); $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults); $this->addErrors($validator->getErrors()); } /** * Validates the type of a property * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('type'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a undefined element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false) { $validator = $this->factory->createInstanceFor('undefined'); $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault); $this->addErrors($validator->getErrors()); } /** * Checks a string element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkString($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('string'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a number element * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param mixed $i */ protected function checkNumber($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('number'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a enum element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkEnum($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('enum'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks format of an element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkFormat($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('format'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Get the type check based on the set check mode. * * @return TypeCheck\TypeCheckInterface */ protected function getTypeCheck() { return $this->factory->getTypeCheck(); } /** * @param JsonPointer $pointer * * @return string property path */ protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer) { $result = array_map( function ($path) { return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path); }, $pointer->getPropertyPaths() ); return trim(implode('', $result), '.'); } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; /** * The CollectionConstraint Constraints, validates an array against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class CollectionConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null) { // Verify minItems if (isset($schema->minItems) && count($value) < $schema->minItems) { $this->addError($path, 'There must be a minimum of ' . $schema->minItems . ' items in the array', 'minItems', array('minItems' => $schema->minItems)); } // Verify maxItems if (isset($schema->maxItems) && count($value) > $schema->maxItems) { $this->addError($path, 'There must be a maximum of ' . $schema->maxItems . ' items in the array', 'maxItems', array('maxItems' => $schema->maxItems)); } // Verify uniqueItems if (isset($schema->uniqueItems) && $schema->uniqueItems) { $unique = $value; if (is_array($value) && count($value)) { $unique = array_map(function ($e) { return var_export($e, true); }, $value); } if (count(array_unique($unique)) != count($value)) { $this->addError($path, 'There are no duplicates allowed in the array', 'uniqueItems'); } } // Verify items if (isset($schema->items)) { $this->validateItems($value, $schema, $path, $i); } } /** * Validates the items * * @param array $value * @param \stdClass $schema * @param JsonPointer|null $path * @param string $i */ protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null) { if (is_object($schema->items)) { // just one type definition for the whole array if (isset($schema->items->type) && ( $schema->items->type == 'string' || $schema->items->type == 'number' || $schema->items->type == 'integer' ) && !isset($schema->additionalItems) ) { // performance optimization $type = $schema->items->type; $typeValidator = $this->factory->createInstanceFor('type'); $validator = $this->factory->createInstanceFor($type === 'integer' ? 'number' : $type); foreach ($value as $k => &$v) { $k_path = $this->incrementPath($path, $k); $typeValidator->check($v, $schema->items, $k_path, $i); $validator->check($v, $schema->items, $k_path, $i); } unset($v); /* remove dangling reference to prevent any future bugs * caused by accidentally using $v elsewhere */ $this->addErrors($typeValidator->getErrors()); $this->addErrors($validator->getErrors()); } else { foreach ($value as $k => &$v) { $initErrors = $this->getErrors(); // First check if its defined in "items" $this->checkUndefined($v, $schema->items, $path, $k); // Recheck with "additionalItems" if the first test fails if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) { $secondErrors = $this->getErrors(); $this->checkUndefined($v, $schema->additionalItems, $path, $k); } // Reset errors if needed if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) { $this->errors = $secondErrors; } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { $this->errors = $initErrors; } } unset($v); /* remove dangling reference to prevent any future bugs * caused by accidentally using $v elsewhere */ } } else { // Defined item type definitions foreach ($value as $k => &$v) { if (array_key_exists($k, $schema->items)) { $this->checkUndefined($v, $schema->items[$k], $path, $k); } else { // Additional items if (property_exists($schema, 'additionalItems')) { if ($schema->additionalItems !== false) { $this->checkUndefined($v, $schema->additionalItems, $path, $k); } else { $this->addError( $path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('additionalItems' => $schema->additionalItems)); } } else { // Should be valid against an empty schema $this->checkUndefined($v, new \stdClass(), $path, $k); } } } unset($v); /* remove dangling reference to prevent any future bugs * caused by accidentally using $v elsewhere */ // Treat when we have more schema definitions than values, not for empty arrays if (count($value) > 0) { for ($k = count($value); $k < count($schema->items); $k++) { $undefinedInstance = $this->factory->createInstanceFor('undefined'); $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k); } } } } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\InvalidArgumentException; use UnexpectedValueException as StandardUnexpectedValueException; /** * The TypeConstraint Constraints, validates an element against a given type * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class TypeConstraint extends Constraint { /** * @var array|string[] type wordings for validation error messages */ public static $wording = array( 'integer' => 'an integer', 'number' => 'a number', 'boolean' => 'a boolean', 'object' => 'an object', 'array' => 'an array', 'string' => 'a string', 'null' => 'a null', 'any' => null, // validation of 'any' is always true so is not needed in message wording 0 => null, // validation of a false-y value is always true, so not needed as well ); /** * {@inheritdoc} */ public function check(&$value = null, $schema = null, JsonPointer $path = null, $i = null) { $type = isset($schema->type) ? $schema->type : null; $isValid = false; $wording = array(); if (is_array($type)) { $this->validateTypesArray($value, $type, $wording, $isValid, $path); } elseif (is_object($type)) { $this->checkUndefined($value, $type, $path); return; } else { $isValid = $this->validateType($value, $type); } if ($isValid === false) { if (!is_array($type)) { $this->validateTypeNameWording($type); $wording[] = self::$wording[$type]; } $this->addError($path, ucwords(gettype($value)) . ' value found, but ' . $this->implodeWith($wording, ', ', 'or') . ' is required', 'type'); } } /** * Validates the given $value against the array of types in $type. Sets the value * of $isValid to true, if at least one $type mateches the type of $value or the value * passed as $isValid is already true. * * @param mixed $value Value to validate * @param array $type TypeConstraints to check agains * @param array $validTypesWording An array of wordings of the valid types of the array $type * @param bool $isValid The current validation value * @param $path */ protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path) { foreach ($type as $tp) { // $tp can be an object, if it's a schema instead of a simple type, validate it // with a new type constraint if (is_object($tp)) { if (!$isValid) { $validator = $this->factory->createInstanceFor('type'); $subSchema = new \stdClass(); $subSchema->type = $tp; $validator->check($value, $subSchema, $path, null); $error = $validator->getErrors(); $isValid = !(bool) $error; $validTypesWording[] = self::$wording['object']; } } else { $this->validateTypeNameWording($tp); $validTypesWording[] = self::$wording[$tp]; if (!$isValid) { $isValid = $this->validateType($value, $tp); } } } } /** * Implodes the given array like implode() with turned around parameters and with the * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of * $delimiter. * * @param array $elements The elements to implode * @param string $delimiter The delimiter to use * @param bool $listEnd The last delimiter to use (defaults to $delimiter) * * @return string */ protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false) { if ($listEnd === false || !isset($elements[1])) { return implode($delimiter, $elements); } $lastElement = array_slice($elements, -1); $firsElements = join($delimiter, array_slice($elements, 0, -1)); $implodedElements = array_merge(array($firsElements), $lastElement); return join(" $listEnd ", $implodedElements); } /** * Validates the given $type, if there's an associated self::$wording. If not, throws an * exception. * * @param string $type The type to validate * * @throws StandardUnexpectedValueException */ protected function validateTypeNameWording($type) { if (!isset(self::$wording[$type])) { throw new StandardUnexpectedValueException( sprintf( 'No wording for %s available, expected wordings are: [%s]', var_export($type, true), implode(', ', array_filter(self::$wording))) ); } } /** * Verifies that a given value is of a certain type * * @param mixed $value Value to validate * @param string $type TypeConstraint to check against * * @throws InvalidArgumentException * * @return bool */ protected function validateType(&$value, $type) { //mostly the case for inline schema if (!$type) { return true; } if ('any' === $type) { return true; } if ('object' === $type) { return $this->getTypeCheck()->isObject($value); } if ('array' === $type) { return $this->getTypeCheck()->isArray($value); } $coerce = $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES); if ('integer' === $type) { if ($coerce) { $value = $this->toInteger($value); } return is_int($value); } if ('number' === $type) { if ($coerce) { $value = $this->toNumber($value); } return is_numeric($value) && !is_string($value); } if ('boolean' === $type) { if ($coerce) { $value = $this->toBoolean($value); } return is_bool($value); } if ('string' === $type) { return is_string($value); } if ('email' === $type) { return is_string($value); } if ('null' === $type) { return is_null($value); } throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type); } /** * Converts a value to boolean. For example, "true" becomes true. * * @param $value The value to convert to boolean * * @return bool|mixed */ protected function toBoolean($value) { if ($value === 'true') { return true; } if ($value === 'false') { return false; } return $value; } /** * Converts a numeric string to a number. For example, "4" becomes 4. * * @param mixed $value the value to convert to a number * * @return int|float|mixed */ protected function toNumber($value) { if (is_numeric($value)) { return $value + 0; // cast to number } return $value; } protected function toInteger($value) { if (is_numeric($value) && (int) $value == $value) { return (int) $value; // cast to number } return $value; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; /** * The StringConstraint Constraints, validates an string against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class StringConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify maxLength if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array( 'maxLength' => $schema->maxLength, )); } //verify minLength if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { $this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array( 'minLength' => $schema->minLength, )); } // Verify a regex pattern if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) { $this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array( 'pattern' => $schema->pattern, )); } $this->checkFormat($element, $schema, $path, $i); } private function strlen($string) { if (extension_loaded('mbstring')) { return mb_strlen($string, mb_detect_encoding($string)); } // mbstring is present on all test platforms, so strlen() can be ignored for coverage return strlen($string); // @codeCoverageIgnore } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; /** * The EnumConstraint Constraints, validates an element against a given set of possibilities * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class EnumConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Only validate enum if the attribute exists if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { return; } $type = gettype($element); foreach ($schema->enum as $enum) { $enumType = gettype($enum); if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == 'array' && $enumType == 'object') { if ((object) $element == $enum) { return; } } if ($type === gettype($enum)) { if ($type == 'object') { if ($element == $enum) { return; } } elseif ($element === $enum) { return; } } } $this->addError($path, 'Does not have a value in the enumeration ' . json_encode($schema->enum), 'enum', array('enum' => $schema->enum)); } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; /** * The Constraints Interface * * @author Robert Schönthal <seroscho@googlemail.com> */ interface ConstraintInterface { /** * returns all collected errors * * @return array */ public function getErrors(); /** * adds errors to this validator * * @param array $errors */ public function addErrors(array $errors); /** * adds an error * * @param JsonPointer|null $path * @param string $message * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' * @param array $more more array elements to add to the error */ public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null); /** * checks if the validator has not raised errors * * @return bool */ public function isValid(); /** * invokes the validation of an element * * @abstract * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i * * @throws \JsonSchema\Exception\ExceptionInterface */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null); } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; /** * The NumberConstraint Constraints, validates an number against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class NumberConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify minimum if (isset($schema->exclusiveMinimum)) { if (isset($schema->minimum)) { if ($schema->exclusiveMinimum && $element <= $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum)); } elseif ($element < $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); } } else { $this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum'); } } elseif (isset($schema->minimum) && $element < $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); } // Verify maximum if (isset($schema->exclusiveMaximum)) { if (isset($schema->maximum)) { if ($schema->exclusiveMaximum && $element >= $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum)); } elseif ($element > $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); } } else { $this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum'); } } elseif (isset($schema->maximum) && $element > $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); } // Verify divisibleBy - Draft v3 if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy)); } // Verify multipleOf - Draft v4 if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { $this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf)); } $this->checkFormat($element, $schema, $path, $i); } private function fmod($number1, $number2) { $number1 = abs($number1); $modulus = fmod($number1, $number2); $precision = abs(0.0000000001); $diff = (float) ($modulus - $number2); if (-$precision < $diff && $diff < $precision) { return 0.0; } $decimals1 = mb_strpos($number1, '.') ? mb_strlen($number1) - mb_strpos($number1, '.') - 1 : 0; $decimals2 = mb_strpos($number2, '.') ? mb_strlen($number2) - mb_strpos($number2, '.') - 1 : 0; return (float) round($modulus, max($decimals1, $decimals2)); } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Rfc3339; /** * Validates against the "format" property * * @author Justin Rainbow <justin.rainbow@gmail.com> * * @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 */ class FormatConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { return; } switch ($schema->format) { case 'date': if (!$date = $this->validateDateTime($element, 'Y-m-d')) { $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'time': if (!$this->validateDateTime($element, 'H:i:s')) { $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'date-time': if (null === Rfc3339::createFromString($element)) { $this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'utc-millisec': if (!$this->validateDateTime($element, 'U')) { $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'regex': if (!$this->validateRegex($element)) { $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format)); } break; case 'color': if (!$this->validateColor($element)) { $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format)); } break; case 'style': if (!$this->validateStyle($element)) { $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format)); } break; case 'phone': if (!$this->validatePhone($element)) { $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format)); } break; case 'uri': if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); } break; case 'uriref': case 'uri-reference': if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them. // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information. if (substr($element, 0, 2) === '//') { // network-path reference $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } elseif (substr($element, 0, 1) === '/') { // absolute-path reference $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } elseif (strlen($element)) { // relative-path reference $pathParts = explode('/', $element, 2); if (strpos($pathParts[0], ':') !== false) { $validURL = null; } else { $validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } } else { $validURL = null; } if ($validURL === null) { $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); } } break; case 'email': $filterFlags = FILTER_NULL_ON_FAILURE; if (defined('FILTER_FLAG_EMAIL_UNICODE')) { // Only available from PHP >= 7.1.0, so ignore it for coverage checks $filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore } if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) { $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format)); } break; case 'ip-address': case 'ipv4': if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); } break; case 'ipv6': if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); } break; case 'host-name': case 'hostname': if (!$this->validateHostname($element)) { $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format)); } break; default: // Empty as it should be: // The value of this keyword is called a format attribute. It MUST be a string. // A format attribute can generally only validate a given set of instance types. // If the type of the instance to validate is not in this set, validation for // this format attribute and instance SHOULD succeed. // http://json-schema.org/latest/json-schema-validation.html#anchor105 break; } } protected function validateDateTime($datetime, $format) { $dt = \DateTime::createFromFormat($format, $datetime); if (!$dt) { return false; } if ($datetime === $dt->format($format)) { return true; } // handles the case where a non-6 digit microsecond datetime is passed // which will fail the above string comparison because the passed // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return // '2000-05-01T12:12:12.123000Z' if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) { return true; } return false; } protected function validateRegex($regex) { return false !== @preg_match('/' . $regex . '/u', ''); } protected function validateColor($color) { if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'))) { return true; } return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); } protected function validateStyle($style) { $properties = explode(';', rtrim($style, ';')); $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); return empty($invalidEntries); } protected function validatePhone($phone) { return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); } protected function validateHostname($host) { $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i'; return preg_match($hostnameRegex, $host); } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\InvalidArgumentException; use JsonSchema\Exception\InvalidSchemaException; use JsonSchema\Exception\RuntimeException; use JsonSchema\SchemaStorage; use JsonSchema\Validator; /** * The SchemaConstraint Constraints, validates an element against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class SchemaConstraint extends Constraint { const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#'; /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { if ($schema !== null) { // passed schema $validationSchema = $schema; } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { // inline schema $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); } else { throw new InvalidArgumentException('no schema found to verify against'); } // cast array schemas to object if (is_array($validationSchema)) { $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); } // validate schema against whatever is defined in $validationSchema->$schema. If no // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04). if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) { if (!$this->getTypeCheck()->isObject($validationSchema)) { throw new RuntimeException('Cannot validate the schema of a non-object'); } if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) { $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema'); } else { $schemaSpec = self::DEFAULT_SCHEMA_SPEC; } // get the spec schema $schemaStorage = $this->factory->getSchemaStorage(); if (!$this->getTypeCheck()->isObject($schemaSpec)) { $schemaSpec = $schemaStorage->getSchema($schemaSpec); } // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA $initialErrorCount = $this->numErrors(); $initialConfig = $this->factory->getConfig(); $initialContext = $this->factory->getErrorContext(); $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS); $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST); $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION); // validate schema try { $this->check($validationSchema, $schemaSpec); } catch (\Exception $e) { if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) { throw new InvalidSchemaException('Schema did not pass validation', 0, $e); } } if ($this->numErrors() > $initialErrorCount) { $this->addError($path, 'Schema is not valid', 'schema'); } // restore the initial config $this->factory->setConfig($initialConfig); $this->factory->setErrorContext($initialContext); } // validate element against $validationSchema $this->checkUndefined($element, $validationSchema, $path, $i); } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Constraints; use JsonSchema\Entity\JsonPointer; /** * The ObjectConstraint Constraints, validates an object against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class ObjectConstraint extends Constraint { /** * @var array List of properties to which a default value has been applied */ protected $appliedDefaults = array(); /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null, $additionalProp = null, $patternProperties = null, $appliedDefaults = array()) { if ($element instanceof UndefinedConstraint) { return; } $this->appliedDefaults = $appliedDefaults; $matches = array(); if ($patternProperties) { // validate the element pattern properties $matches = $this->validatePatternProperties($element, $path, $patternProperties); } if ($properties) { // validate the element properties $this->validateProperties($element, $properties, $path); } // validate additional element properties & constraints $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp); } public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties) { $try = array('/', '#', '+', '~', '%'); $matches = array(); foreach ($patternProperties as $pregex => $schema) { $delimiter = '/'; // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error foreach ($try as $delimiter) { if (strpos($pregex, $delimiter) === false) { // safe to use break; } } // Validate the pattern before using it to test for matches if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) { $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex)); continue; } foreach ($element as $i => $value) { if (preg_match($delimiter . $pregex . $delimiter . 'u', $i)) { $matches[] = $i; $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); } } } return $matches; } /** * Validates the element properties * * @param \StdClass $element Element to validate * @param array $matches Matches from patternProperties (if any) * @param \StdClass $schema ObjectConstraint definition * @param JsonPointer|null $path Current test path * @param \StdClass $properties Properties * @param mixed $additionalProp Additional properties */ public function validateElement($element, $matches, $schema = null, JsonPointer $path = null, $properties = null, $additionalProp = null) { $this->validateMinMaxConstraint($element, $schema, $path); foreach ($element as $i => $value) { $definition = $this->getProperty($properties, $i); // no additional properties allowed if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { $this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp'); } // additional properties defined if (!in_array($i, $matches) && $additionalProp && !$definition) { if ($additionalProp === true) { $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults)); } else { $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults)); } } // property requires presence of another $require = $this->getProperty($definition, 'requires'); if ($require && !$this->getProperty($element, $require)) { $this->addError($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires'); } $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); if (is_object($property)) { $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path); } } } /** * Validates the definition properties * * @param \stdClass $element Element to validate * @param \stdClass $properties Property definitions * @param JsonPointer|null $path Path? */ public function validateProperties(&$element, $properties = null, JsonPointer $path = null) { $undefinedConstraint = $this->factory->createInstanceFor('undefined'); foreach ($properties as $i => $value) { $property = &$this->getProperty($element, $i, $undefinedConstraint); $definition = $this->getProperty($properties, $i); if (is_object($definition)) { // Undefined constraint will check for is_object() and quit if is not - so why pass it? $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults)); } } } /** * retrieves a property from an object or array * * @param mixed $element Element to validate * @param string $property Property to retrieve * @param mixed $fallback Default value if property is not found * * @return mixed */ protected function &getProperty(&$element, $property, $fallback = null) { if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) { return $element[$property]; } elseif (is_object($element) && property_exists($element, $property)) { return $element->$property; } return $fallback; } /** * validating minimum and maximum property constraints (if present) against an element * * @param \stdClass $element Element to validate * @param \stdClass $objectDefinition ObjectConstraint definition * @param JsonPointer|null $path Path to test? */ protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null) { // Verify minimum number of properties if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) { if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) { $this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties)); } } // Verify maximum number of properties if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) { if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) { $this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties)); } } } } <?php namespace JsonSchema\Constraints\TypeCheck; class LooseTypeCheck implements TypeCheckInterface { public static function isObject($value) { return is_object($value) || (is_array($value) && (count($value) == 0 || self::isAssociativeArray($value))); } public static function isArray($value) { return is_array($value) && (count($value) == 0 || !self::isAssociativeArray($value)); } public static function propertyGet($value, $property) { if (is_object($value)) { return $value->{$property}; } return $value[$property]; } public static function propertySet(&$value, $property, $data) { if (is_object($value)) { $value->{$property} = $data; } else { $value[$property] = $data; } } public static function propertyExists($value, $property) { if (is_object($value)) { return property_exists($value, $property); } return array_key_exists($property, $value); } public static function propertyCount($value) { if (is_object($value)) { return count(get_object_vars($value)); } return count($value); } /** * Check if the provided array is associative or not * * @param array $arr * * @return bool */ private static function isAssociativeArray($arr) { return array_keys($arr) !== range(0, count($arr) - 1); } } <?php namespace JsonSchema\Constraints\TypeCheck; class StrictTypeCheck implements TypeCheckInterface { public static function isObject($value) { return is_object($value); } public static function isArray($value) { return is_array($value); } public static function propertyGet($value, $property) { return $value->{$property}; } public static function propertySet(&$value, $property, $data) { $value->{$property} = $data; } public static function propertyExists($value, $property) { return property_exists($value, $property); } public static function propertyCount($value) { return count(get_object_vars($value)); } } <?php namespace JsonSchema\Constraints\TypeCheck; interface TypeCheckInterface { public static function isObject($value); public static function isArray($value); public static function propertyGet($value, $property); public static function propertySet(&$value, $property, $data); public static function propertyExists($value, $property); public static function propertyCount($value); } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Entity; use JsonSchema\Exception\InvalidArgumentException; /** * @package JsonSchema\Entity * * @author Joost Nijhuis <jnijhuis81@gmail.com> */ class JsonPointer { /** @var string */ private $filename; /** @var string[] */ private $propertyPaths = array(); /** * @var bool Whether the value at this path was set from a schema default */ private $fromDefault = false; /** * @param string $value * * @throws InvalidArgumentException when $value is not a string */ public function __construct($value) { if (!is_string($value)) { throw new InvalidArgumentException('Ref value must be a string'); } $splitRef = explode('#', $value, 2); $this->filename = $splitRef[0]; if (array_key_exists(1, $splitRef)) { $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); } } /** * @param string $propertyPathString * * @return string[] */ private function decodePropertyPaths($propertyPathString) { $paths = array(); foreach (explode('/', trim($propertyPathString, '/')) as $path) { $path = $this->decodePath($path); if (is_string($path) && '' !== $path) { $paths[] = $path; } } return $paths; } /** * @return array */ private function encodePropertyPaths() { return array_map( array($this, 'encodePath'), $this->getPropertyPaths() ); } /** * @param string $path * * @return string */ private function decodePath($path) { return strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); } /** * @param string $path * * @return string */ private function encodePath($path) { return strtr($path, array('/' => '~1', '~' => '~0', '%' => '%25')); } /** * @return string */ public function getFilename() { return $this->filename; } /** * @return string[] */ public function getPropertyPaths() { return $this->propertyPaths; } /** * @param array $propertyPaths * * @return JsonPointer */ public function withPropertyPaths(array $propertyPaths) { $new = clone $this; $new->propertyPaths = $propertyPaths; return $new; } /** * @return string */ public function getPropertyPathAsString() { return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/'); } /** * @return string */ public function __toString() { return $this->getFilename() . $this->getPropertyPathAsString(); } /** * Mark the value at this path as being set from a schema default */ public function setFromDefault() { $this->fromDefault = true; } /** * Check whether the value at this path was set from a schema default * * @return bool */ public function fromDefault() { return $this->fromDefault; } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * @package JsonSchema\Exception * * @author Joost Nijhuis <jnijhuis81@gmail.com> */ class UnresolvableJsonPointerException extends InvalidArgumentException { } <?php namespace JsonSchema\Exception; interface ExceptionInterface { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the InvalidSchemaMediaType */ class InvalidSchemaMediaTypeException extends RuntimeException { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the InvalidSchemaMediaType */ class InvalidSchemaException extends RuntimeException { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the RuntimeException */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the InvalidSourceUriException */ class InvalidSourceUriException extends InvalidArgumentException { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; class ValidationException extends RuntimeException { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the UriResolverException */ class UriResolverException extends RuntimeException { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the InvalidArgumentException */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the ResourceNotFoundException */ class ResourceNotFoundException extends RuntimeException { } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the JsonDecodingException */ class JsonDecodingException extends RuntimeException { public function __construct($code = JSON_ERROR_NONE, \Exception $previous = null) { switch ($code) { case JSON_ERROR_DEPTH: $message = 'The maximum stack depth has been exceeded'; break; case JSON_ERROR_STATE_MISMATCH: $message = 'Invalid or malformed JSON'; break; case JSON_ERROR_CTRL_CHAR: $message = 'Control character error, possibly incorrectly encoded'; break; case JSON_ERROR_UTF8: $message = 'Malformed UTF-8 characters, possibly incorrectly encoded'; break; case JSON_ERROR_SYNTAX: $message = 'JSON syntax is malformed'; break; default: $message = 'Syntax error'; } parent::__construct($message, $code, $previous); } } <?php /* * This file is part of the JsonSchema package. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace JsonSchema\Exception; /** * Wrapper for the ResourceNotFoundException */ class InvalidConfigException extends RuntimeException { } ## NAME {{name}} {{#shortdesc}} ## DESCRIPTION {{shortdesc}} {{/shortdesc}} ## SYNOPSIS {{synopsis}} {{#alias}} ## ALIAS {{alias}} {{/alias}} {{#has-subcommands}} ## SUBCOMMANDS {{#subcommands}} {{.}} {{/subcommands}} {{/has-subcommands}} Plugin %9{{slug}}%n details: Name: {{name}} Status: {{status}}%n Version: {{version}} Author: {{author}} Description: {{description}} Theme %9{{slug}}%n details: Name: {{name}} Status: {{status}}%n Version: {{version}} Author: {{author}} WordPress version: {{wp-version}} Database revision: {{db-version}} TinyMCE version: {{mce-version}} Package language: {{local-package}} {{#is_subcommand}} {{/is_subcommand}} {{#has_subcommands}} {{/has_subcommands}} ## GLOBAL PARAMETERS {{#parameters}} {{synopsis}} {{desc}} {{/parameters}} {{#root_command}} Run 'wp help <command>' to get more information on a specific command. {{/root_command}} <?php /** * The base configuration for WordPress * * The wp-config.php creation script uses this file during the * installation. You don't have to use the web site, you can * copy this file to "wp-config.php" and fill in the values. * * This file contains the following configurations: * * * MySQL settings * * Secret keys * * Database table prefix * * ABSPATH * * @link https://codex.wordpress.org/Editing_wp-config.php * * @package WordPress */ // ** MySQL settings ** // /** The name of the database for WordPress */ define( 'DB_NAME', '{{dbname}}' ); /** MySQL database username */ define( 'DB_USER', '{{dbuser}}' ); /** MySQL database password */ define( 'DB_PASSWORD', '{{dbpass}}' ); /** MySQL hostname */ define( 'DB_HOST', '{{dbhost}}' ); /** Database Charset to use in creating database tables. */ define( 'DB_CHARSET', '{{dbcharset}}' ); /** The Database Collate type. Don't change this if in doubt. */ define( 'DB_COLLATE', '{{dbcollate}}' ); /** * Authentication Unique Keys and Salts. * * Change these to different unique phrases! * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service} * You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again. * * @since 2.6.0 */ {{#keys-and-salts}} define( 'AUTH_KEY', '{{auth-key}}' ); define( 'SECURE_AUTH_KEY', '{{secure-auth-key}}' ); define( 'LOGGED_IN_KEY', '{{logged-in-key}}' ); define( 'NONCE_KEY', '{{nonce-key}}' ); define( 'AUTH_SALT', '{{auth-salt}}' ); define( 'SECURE_AUTH_SALT', '{{secure-auth-salt}}' ); define( 'LOGGED_IN_SALT', '{{logged-in-salt}}' ); define( 'NONCE_SALT', '{{nonce-salt}}' ); {{/keys-and-salts}} {{keys-and-salts-alt}} /** * WordPress Database Table prefix. * * You can have multiple installations in one database if you give each * a unique prefix. Only numbers, letters, and underscores please! */ $table_prefix = '{{dbprefix}}'; {{#add-wplang}} define( 'WPLANG', '{{locale}}' ); {{/add-wplang}} {{extra-php}} /* That's all, stop editing! Happy blogging. */ /** Absolute path to the WordPress directory. */ if ( ! defined( 'ABSPATH' ) ) define( 'ABSPATH', dirname( __FILE__ ) . '/' ); /** Sets up WordPress vars and included files. */ require_once ABSPATH . 'wp-settings.php'; <phpunit bootstrap="tests/bootstrap.php" backupGlobals="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" > <testsuites> <testsuite> <directory prefix="test-" suffix=".php">./tests/</directory> </testsuite> </testsuites> </phpunit> === {{plugin_name}} === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.4 Tested up to: {{plugin_tested_up_to}} Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Here is a short description of the plugin. This should be no more than 150 characters. No markup here. == Description == This is the long description. No limit, and you can use Markdown (as well as in the following sections). For backwards compatibility, if this section is missing, the full length of the short description will be used, and Markdown parsed. A few notes about the sections above: * "Contributors" is a comma separated list of wp.org/wp-plugins.org usernames * "Tags" is a comma separated list of tags that apply to the plugin * "Requires at least" is the lowest version that the plugin will work on * "Tested up to" is the highest version that you've *successfully used to test the plugin*. Note that it might work on higher versions... this is just the highest one you've verified. * Stable tag should indicate the Subversion "tag" of the latest stable version, or "trunk," if you use `/trunk/` for stable. Note that the `readme.txt` of the stable tag is the one that is considered the defining one for the plugin, so if the `/trunk/readme.txt` file says that the stable tag is `4.3`, then it is `/tags/4.3/readme.txt` that'll be used for displaying information about the plugin. In this situation, the only thing considered from the trunk `readme.txt` is the stable tag pointer. Thus, if you develop in trunk, you can update the trunk `readme.txt` to reflect changes in your in-development version, without having that information incorrectly disclosed about the current stable version that lacks those changes -- as long as the trunk's `readme.txt` points to the correct stable tag. If no stable tag is provided, it is assumed that trunk is stable, but you should specify "trunk" if that's where you put the stable version, in order to eliminate any doubt. == Installation == This section describes how to install the plugin and get it working. e.g. 1. Upload `plugin-name.php` to the `/wp-content/plugins/` directory 1. Activate the plugin through the 'Plugins' menu in WordPress 1. Place `<?php do_action('plugin_name_hook'); ?>` in your templates == Frequently Asked Questions == = A question that someone might have = An answer to that question. = What about foo bar? = Answer to foo bar dilemma. == Screenshots == 1. This screen shot description corresponds to screenshot-1.(png|jpg|jpeg|gif). Note that the screenshot is taken from the /assets directory or the directory that contains the stable readme.txt (tags or trunk). Screenshots in the /assets directory take precedence. For example, `/assets/screenshot-1.png` would win over `/tags/4.3/screenshot-1.png` (or jpg, jpeg, gif). 2. This is the second screen shot == Changelog == = 1.0 = * A change since the previous version. * Another change. = 0.5 = * List versions from most recent at top to oldest at bottom. == Upgrade Notice == = 1.0 = Upgrade notices describe the reason a user should upgrade. No more than 300 characters. = 0.5 = This version fixes a security related bug. Upgrade immediately. == Arbitrary section == You may provide arbitrary sections, in the same format as the ones above. This may be of use for extremely complicated plugins where more information needs to be conveyed that doesn't fit into the categories of "description" or "installation." Arbitrary sections will be shown below the built-in sections outlined above. == A brief Markdown Example == Ordered list: 1. Some feature 1. Another feature 1. Something else about the plugin Unordered list: * something * something else * third thing Here's a link to [WordPress](https://wordpress.org/ "Your favorite software") and one to [Markdown's Syntax Documentation][markdown syntax]. Titles are optional, naturally. [markdown syntax]: https://daringfireball.net/projects/markdown/syntax "Markdown is what the parser uses to process much of the readme file" Markdown uses email style notation for blockquotes and I've been told: > Asterisks for *emphasis*. Double it up for **strong**. `<?php code(); // goes in backticks ?>` # A set of files you probably don't want in your WordPress.org distribution .distignore .editorconfig .git .gitignore .gitlab-ci.yml .travis.yml .DS_Store Thumbs.db behat.yml bin circle.yml composer.json composer.lock Gruntfile.js package.json package-lock.json phpunit.xml phpunit.xml.dist multisite.xml multisite.xml.dist phpcs.xml phpcs.xml.dist README.md wp-cli.local.yml yarn.lock tests vendor node_modules *.sql *.tar.gz *.zip sudo: false language: php notifications: email: on_success: never on_failure: change branches: only: - master cache: directories: - vendor - $HOME/.composer/cache matrix: include: - php: 7.1 env: WP_VERSION=latest - php: 7.0 env: WP_VERSION=latest {{#wp_versions_to_test}} - php: 5.6 env: WP_VERSION={{.}} {{/wp_versions_to_test}} - php: 5.6 env: WP_TRAVISCI=phpcs - php: 5.3 env: WP_VERSION=latest before_script: - export PATH="$HOME/.composer/vendor/bin:$PATH" - | if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then phpenv config-rm xdebug.ini else echo "xdebug.ini does not exist" fi - | if [[ ! -z "$WP_VERSION" ]] ; then bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." ]]; then composer global require "phpunit/phpunit=4.8.*" else composer global require "phpunit/phpunit=5.7.*" fi fi - | if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then composer global require wp-coding-standards/wpcs phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs fi script: - | if [[ ! -z "$WP_VERSION" ]] ; then phpunit WP_MULTISITE=1 phpunit fi - | if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then phpcs fi <?php function {{machine_name}}_init() { {{output}} } add_action( 'init', '{{machine_name}}_init' ); <?php /** * Class SampleTest * * @package {{plugin_package}} */ /** * Sample test case. */ class SampleTest extends WP_UnitTestCase { /** * A single example test. */ function test_sample() { // Replace this with some actual testing code. $this->assertTrue( true ); } } register_taxonomy( '{{slug}}', array( {{post_types}} ), array( 'hierarchical' => false, 'public' => true, 'show_in_nav_menus' => true, 'show_ui' => true, 'show_admin_column' => false, 'query_var' => true, 'rewrite' => true, 'capabilities' => array( 'manage_terms' => 'edit_posts', 'edit_terms' => 'edit_posts', 'delete_terms' => 'edit_posts', 'assign_terms' => 'edit_posts' ), 'labels' => array( 'name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), 'singular_name' => _x( '{{label_ucfirst}}', 'taxonomy general name', '{{textdomain}}' ), 'search_items' => __( 'Search {{label_plural}}', '{{textdomain}}' ), 'popular_items' => __( 'Popular {{label_plural}}', '{{textdomain}}' ), 'all_items' => __( 'All {{label_plural}}', '{{textdomain}}' ), 'parent_item' => __( 'Parent {{label}}', '{{textdomain}}' ), 'parent_item_colon' => __( 'Parent {{label}}:', '{{textdomain}}' ), 'edit_item' => __( 'Edit {{label}}', '{{textdomain}}' ), 'update_item' => __( 'Update {{label}}', '{{textdomain}}' ), 'add_new_item' => __( 'New {{label}}', '{{textdomain}}' ), 'new_item_name' => __( 'New {{label}}', '{{textdomain}}' ), 'separate_items_with_commas' => __( 'Separate {{label_plural}} with commas', '{{textdomain}}' ), 'add_or_remove_items' => __( 'Add or remove {{label_plural}}', '{{textdomain}}' ), 'choose_from_most_used' => __( 'Choose from the most used {{label_plural}}', '{{textdomain}}' ), 'not_found' => __( 'No {{label_plural}} found.', '{{textdomain}}' ), 'menu_name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), ), 'show_in_rest' => true, 'rest_base' => '{{slug}}', 'rest_controller_class' => 'WP_REST_Terms_Controller', ) ); <?php /** * Class SampleTest * * @package {{theme_package}} */ /** * Sample test case. */ class SampleTest extends WP_UnitTestCase { /** * A single example test. */ function test_sample() { // Replace this with some actual testing code. $this->assertTrue( true ); } } #!/usr/bin/env bash if [ $# -lt 3 ]; then echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]" exit 1 fi DB_NAME=$1 DB_USER=$2 DB_PASS=$3 DB_HOST=${4-localhost} WP_VERSION=${5-latest} SKIP_DB_CREATE=${6-false} TMPDIR=${TMPDIR-/tmp} TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} download() { if [ `which curl` ]; then curl -s "$1" > "$2"; elif [ `which wget` ]; then wget -nv -O "$2" "$1" fi } if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then WP_TESTS_TAG="branches/$WP_VERSION" elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x WP_TESTS_TAG="tags/${WP_VERSION%??}" else WP_TESTS_TAG="tags/$WP_VERSION" fi elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then WP_TESTS_TAG="trunk" else # http serves a single offer, whereas https serves multiple. we only want one download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') if [[ -z "$LATEST_VERSION" ]]; then echo "Latest WordPress version could not be found" exit 1 fi WP_TESTS_TAG="tags/$LATEST_VERSION" fi set -ex install_wp() { if [ -d $WP_CORE_DIR ]; then return; fi mkdir -p $WP_CORE_DIR if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then mkdir -p $TMPDIR/wordpress-nightly download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR else if [ $WP_VERSION == 'latest' ]; then local ARCHIVE_NAME='latest' elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then # https serves multiple offers, whereas http serves single. download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x LATEST_VERSION=${WP_VERSION%??} else # otherwise, scan the releases and get the most up to date minor version of the major release local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) fi if [[ -z "$LATEST_VERSION" ]]; then local ARCHIVE_NAME="wordpress-$WP_VERSION" else local ARCHIVE_NAME="wordpress-$LATEST_VERSION" fi else local ARCHIVE_NAME="wordpress-$WP_VERSION" fi download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR fi download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php } install_test_suite() { # portable in-place argument for both GNU sed and Mac OSX sed if [[ $(uname -s) == 'Darwin' ]]; then local ioption='-i .bak' else local ioption='-i' fi # set up testing suite if it doesn't yet exist if [ ! -d $WP_TESTS_DIR ]; then # set up testing suite mkdir -p $WP_TESTS_DIR svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data fi if [ ! -f wp-tests-config.php ]; then download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php # remove all forward slashes in the end WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php fi } install_db() { if [ ${SKIP_DB_CREATE} = "true" ]; then return 0 fi # parse DB_HOST for port or socket references local PARTS=(${DB_HOST//\:/ }) local DB_HOSTNAME=${PARTS[0]}; local DB_SOCK_OR_PORT=${PARTS[1]}; local EXTRA="" if ! [ -z $DB_HOSTNAME ] ; then if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" elif ! [ -z $DB_SOCK_OR_PORT ] ; then EXTRA=" --socket=$DB_SOCK_OR_PORT" elif ! [ -z $DB_HOSTNAME ] ; then EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" fi fi # create database mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA } install_wp install_test_suite install_db register_post_type( '{{slug}}', array( 'labels' => array( 'name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), 'singular_name' => __( '{{label_ucfirst}}', '{{textdomain}}' ), 'all_items' => __( 'All {{label_plural_ucfirst}}', '{{textdomain}}' ), 'new_item' => __( 'New {{label}}', '{{textdomain}}' ), 'add_new' => __( 'Add New', '{{textdomain}}' ), 'add_new_item' => __( 'Add New {{label}}', '{{textdomain}}' ), 'edit_item' => __( 'Edit {{label}}', '{{textdomain}}' ), 'view_item' => __( 'View {{label}}', '{{textdomain}}' ), 'search_items' => __( 'Search {{label_plural}}', '{{textdomain}}' ), 'not_found' => __( 'No {{label_plural}} found', '{{textdomain}}' ), 'not_found_in_trash' => __( 'No {{label_plural}} found in trash', '{{textdomain}}' ), 'parent_item_colon' => __( 'Parent {{label}}', '{{textdomain}}' ), 'menu_name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), ), 'public' => true, 'hierarchical' => false, 'show_ui' => true, 'show_in_nav_menus' => true, 'supports' => array( 'title', 'editor' ), 'has_archive' => true, 'rewrite' => true, 'query_var' => true, 'menu_icon' => 'dashicons-{{dashicon}}', 'show_in_rest' => true, 'rest_base' => '{{slug}}', 'rest_controller_class' => 'WP_REST_Posts_Controller', ) ); { "name": "{{plugin_slug}}", "version": "0.1.0", "main": "Gruntfile.js", "author": "{{plugin_author}}", "devDependencies": { "grunt": "~0.4.5", "grunt-wp-i18n": "~0.5.0", "grunt-wp-readme-to-markdown": "~1.0.0" } } <?php /** * PHPUnit bootstrap file * * @package {{plugin_package}} */ $_tests_dir = getenv( 'WP_TESTS_DIR' ); if ( ! $_tests_dir ) { $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib'; } if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) { throw new Exception( "Could not find $_tests_dir/includes/functions.php, have you run bin/install-wp-tests.sh ?" ); } // Give access to tests_add_filter() function. require_once $_tests_dir . '/includes/functions.php'; /** * Manually load the plugin being tested. */ function _manually_load_plugin() { require dirname( dirname( __FILE__ ) ) . '/{{plugin_slug}}.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); // Start up the WP testing environment. require $_tests_dir . '/includes/bootstrap.php'; # This file is for unifying the coding style for different editors and IDEs # editorconfig.org # WordPress Coding Standards # https://make.wordpress.org/core/handbook/coding-standards/ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = tab indent_size = 4 [{.jshintrc,*.json,*.yml}] indent_style = space indent_size = 2 [{*.txt,wp-config-sample.php}] end_of_line = crlf <?xml version="1.0"?> <ruleset name="WordPress Coding Standards for Plugins"> <description>Generally-applicable sniffs for WordPress plugins</description> <rule ref="WordPress-Core" /> <rule ref="WordPress-Docs" /> <!-- Check all PHP files in directory tree by default. --> <arg name="extensions" value="php"/> <file>.</file> <!-- Show progress and sniff codes in all reports --> <arg value="ps"/> <exclude-pattern>*/node_modules/*</exclude-pattern> <exclude-pattern>*/vendor/*</exclude-pattern> </ruleset> /* Theme Name: {{theme_name}} Theme URI: {{theme_uri}} Description: {{description}} Author: {{author}} Author URI: {{author_uri}} Template: {{parent_theme}} Version: 0.1.0 */ Theme %9{{slug}}%n details: Name: {{name}} Status: {{status}}%n Version: {{version}} Author: {{author}} <?php function {{machine_name}}_init() { {{output}} } add_action( 'init', '{{machine_name}}_init' ); function {{machine_name}}_updated_messages( $messages ) { global $post; $permalink = get_permalink( $post ); $messages['{{slug}}'] = array( 0 => '', // Unused. Messages start at index 1. 1 => sprintf( __('{{label_ucfirst}} updated. <a target="_blank" href="%s">View {{label}}</a>', '{{textdomain}}'), esc_url( $permalink ) ), 2 => __('Custom field updated.', '{{textdomain}}'), 3 => __('Custom field deleted.', '{{textdomain}}'), 4 => __('{{label_ucfirst}} updated.', '{{textdomain}}'), /* translators: %s: date and time of the revision */ 5 => isset($_GET['revision']) ? sprintf( __('{{label_ucfirst}} restored to revision from %s', '{{textdomain}}'), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, 6 => sprintf( __('{{label_ucfirst}} published. <a href="%s">View {{label}}</a>', '{{textdomain}}'), esc_url( $permalink ) ), 7 => __('{{label_ucfirst}} saved.', '{{textdomain}}'), 8 => sprintf( __('{{label_ucfirst}} submitted. <a target="_blank" href="%s">Preview {{label}}</a>', '{{textdomain}}'), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ), 9 => sprintf( __('{{label_ucfirst}} scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview {{label}}</a>', '{{textdomain}}'), // translators: Publish box date format, see https://secure.php.net/manual/en/function.date.php date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( $permalink ) ), 10 => sprintf( __('{{label_ucfirst}} draft updated. <a target="_blank" href="%s">Preview {{label}}</a>', '{{textdomain}}'), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ), ); return $messages; } add_filter( 'post_updated_messages', '{{machine_name}}_updated_messages' ); <?php add_action( 'wp_enqueue_scripts', '{{parent_theme_function_safe}}_parent_theme_enqueue_styles' ); function {{parent_theme_function_safe}}_parent_theme_enqueue_styles() { wp_enqueue_style( '{{parent_theme}}-style', get_template_directory_uri() . '/style.css' ); wp_enqueue_style( '{{slug}}-style', get_stylesheet_directory_uri() . '/style.css', array( '{{parent_theme}}-style' ) ); } machine: php: version: 5.6.22 environment: WP_TESTS_DIR: /tmp/wordpress-tests-lib WP_CORE_DIR: /tmp/wordpress/ PATH: $HOME/.composer/vendor/bin:$PATH dependencies: pre: - sudo apt-get update; sudo apt-get install subversion test: pre: - composer global require "phpunit/phpunit=5.7.*" - | composer global require wp-coding-standards/wpcs phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs override: - phpcs {{#wp_versions_to_test}} - | rm -rf $WP_TESTS_DIR $WP_CORE_DIR bash bin/install-wp-tests.sh wordpress_test ubuntu '' 127.0.0.1 {{.}} phpunit WP_MULTISITE=1 phpunit {{/wp_versions_to_test}} - | phpenv global 7.0.17 rm -rf $WP_TESTS_DIR $WP_CORE_DIR bash bin/install-wp-tests.sh wordpress_test ubuntu '' 127.0.0.1 latest phpunit WP_MULTISITE=1 phpunit - | phpenv global 7.1.3 rm -rf $WP_TESTS_DIR $WP_CORE_DIR bash bin/install-wp-tests.sh wordpress_test ubuntu '' 127.0.0.1 latest phpunit WP_MULTISITE=1 phpunit <?php /** * PHPUnit bootstrap file * * @package {{theme_package}} */ $_tests_dir = getenv( 'WP_TESTS_DIR' ); if ( ! $_tests_dir ) { $_tests_dir = '/tmp/wordpress-tests-lib'; } if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) { throw new Exception( "Could not find $_tests_dir/includes/functions.php, have you run bin/install-wp-tests.sh ?" ); } // Give access to tests_add_filter() function. require_once $_tests_dir . '/includes/functions.php'; function _register_theme() { $theme_dir = dirname( dirname( __FILE__ ) ); $current_theme = basename( $theme_dir ); register_theme_directory( dirname( $theme_dir ) ); add_filter( 'pre_option_template', function() use ( $current_theme ) { return $current_theme; }); add_filter( 'pre_option_stylesheet', function() use ( $current_theme ) { return $current_theme; }); } tests_add_filter( 'muplugins_loaded', '_register_theme' ); // Start up the WP testing environment. require $_tests_dir . '/includes/bootstrap.php'; variables: # Configure mysql service (https://hub.docker.com/_/mysql/) MYSQL_DATABASE: wordpress_tests MYSQL_ROOT_PASSWORD: mysql before_script: # Install dependencies # update the docker - apt-get clean - apt-get -yqq update # instll the required packages for the running CI tests - apt-get -yqqf install zip unzip subversion mysql-client libmysqlclient-dev --fix-missing # PHP extensions - docker-php-ext-enable mbstring mcrypt mysqli pdo_mysql intl gd zip bz2 # Set up WordPress tests - bash bin/install-wp-tests.sh wordpress_tests root mysql mysql latest true # Install PHPCS and WPCS - composer global require "squizlabs/php_codesniffer=*" - composer global require "wp-coding-standards/wpcs" - phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs PHPunit:PHP5.3:MySQL: image: tetraweb/php:5.3 services: - mysql:5.6 script: - phpcs - phpunit PHPunit:PHP5.6:MySQL: image: tetraweb/php:5.6 services: - mysql:5.6 script: - phpcs - phpunit PHPunit:PHP7.0:MySQL: image: tetraweb/php:7.0 services: - mysql:5.6 script: - phpcs - phpunit PHPunit:PHP7.1:MySQL: image: tetraweb/php:7.1 services: - mysql:5.6 script: - phpcs - phpunit module.exports = function( grunt ) { 'use strict'; // Project configuration grunt.initConfig( { pkg: grunt.file.readJSON( 'package.json' ), addtextdomain: { options: { textdomain: '{{textdomain}}', }, update_all_domains: { options: { updateDomains: true }, src: [ '*.php', '**/*.php', '!\.git/**/*', '!bin/**/*', '!node_modules/**/*', '!tests/**/*' ] } }, wp_readme_to_markdown: { your_target: { files: { 'README.md': 'readme.txt' } }, }, makepot: { target: { options: { domainPath: '/languages', exclude: [ '\.git/*', 'bin/*', 'node_modules/*', 'tests/*' ], mainFile: '{{plugin_slug}}.php', potFilename: '{{plugin_slug}}.pot', potHeaders: { poedit: true, 'x-poedit-keywordslist': true }, type: 'wp-plugin', updateTimestamp: true } } }, } ); grunt.loadNpmTasks( 'grunt-wp-i18n' ); grunt.loadNpmTasks( 'grunt-wp-readme-to-markdown' ); grunt.registerTask( 'i18n', ['addtextdomain', 'makepot'] ); grunt.registerTask( 'readme', ['wp_readme_to_markdown'] ); grunt.util.linefeed = '\n'; }; .DS_Store Thumbs.db wp-cli.local.yml node_modules/ *.sql *.tar.gz *.zip <?php /** * Plugin Name: {{plugin_name}} * Plugin URI: {{plugin_uri}} * Description: {{plugin_description}} * Author: {{plugin_author}} * Author URI: {{plugin_author_uri}} * Text Domain: {{textdomain}} * Domain Path: /languages * Version: 0.1.0 * * @package {{plugin_package}} */ <?php // autoload.php @generated by Composer require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit676e4dc925e4d952efb2d5f8c1115412::getLoader(); <?php // autoload_commands.php @generated by Composer require_once __DIR__ . '/composer/autoload_commands_real.php'; return ComposerAutoloaderInit48aaae54411fff198817d4d8c6978985::getLoader(); <?php // autoload_framework.php @generated by Composer require_once __DIR__ . '/composer/autoload_framework_real.php'; return ComposerAutoloaderInita2c7f3471000cfb340e7060992ed35d1::getLoader(); <?php /** * Generate a list of tags to skip during the test run. * * Require a minimum version of WordPress: * * @require-wp-4.0 * Scenario: Core translation CRUD * * Then use in bash script: * * BEHAT_TAGS=$(php behat-tags.php) * vendor/bin/behat --format progress $BEHAT_TAGS */ function version_tags( $prefix, $current, $operator = '<' ) { if ( ! $current ) return array(); exec( "grep '@{$prefix}-[0-9\.]*' -h -o features/*.feature | uniq", $existing_tags ); $skip_tags = array(); foreach ( $existing_tags as $tag ) { $compare = str_replace( "@{$prefix}-", '', $tag ); if ( version_compare( $current, $compare, $operator ) ) { $skip_tags[] = $tag; } } return $skip_tags; } $wp_version_reqs = array(); // Only apply @require-wp tags when WP_VERSION isn't 'latest' or 'nightly' // 'latest' and 'nightly' are expected to work with all features if ( ! in_array( getenv( 'WP_VERSION' ), array( 'latest', 'nightly', 'trunk' ), true ) ) { $wp_version_reqs = version_tags( 'require-wp', getenv( 'WP_VERSION' ), '<' ); } $skip_tags = array_merge( $wp_version_reqs, version_tags( 'require-php', PHP_VERSION, '<' ), version_tags( 'less-than-php', PHP_VERSION, '>' ) ); # Skip Github API tests by default because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 $skip_tags[] = '@github-api'; # Skip tests known to be broken. $skip_tags[] = '@broken'; # Require PHP extension, eg 'imagick'. function extension_tags() { $extension_tags = array(); exec( "grep '@require-extension-[A-Za-z_]*' -h -o features/*.feature | uniq", $extension_tags ); $skip_tags = array(); $substr_start = strlen( '@require-extension-' ); foreach ( $extension_tags as $tag ) { $extension = substr( $tag, $substr_start ); if ( ! extension_loaded( $extension ) ) { $skip_tags[] = $tag; } } return $skip_tags; } $skip_tags = array_merge( $skip_tags, extension_tags() ); if ( !empty( $skip_tags ) ) { echo '--tags=~' . implode( '&&~', $skip_tags ); } Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. { "$schema": "http://json-schema.org/draft-04/schema#", "name": "Package", "type": "object", "additionalProperties": false, "required": [ "name", "description" ], "properties": { "name": { "type": "string", "description": "Package name, including 'vendor-name/' prefix." }, "type": { "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", "type": "string" }, "target-dir": { "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", "type": "string" }, "description": { "type": "string", "description": "Short package description." }, "keywords": { "type": "array", "items": { "type": "string", "description": "A tag/keyword that this package relates to." } }, "homepage": { "type": "string", "description": "Homepage URL for the project.", "format": "uri" }, "version": { "type": "string", "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes." }, "time": { "type": "string", "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format." }, "license": { "type": ["string", "array"], "description": "License name. Or an array of license names." }, "authors": { "$ref": "#/definitions/authors" }, "require": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.", "additionalProperties": { "type": "string" } }, "replace": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that can be replaced by this package.", "additionalProperties": { "type": "string" } }, "conflict": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that conflict with this package.", "additionalProperties": { "type": "string" } }, "provide": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that this package provides in addition to this package's name.", "additionalProperties": { "type": "string" } }, "require-dev": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", "additionalProperties": { "type": "string" } }, "suggest": { "type": "object", "description": "This is a hash of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).", "additionalProperties": { "type": "string" } }, "config": { "type": "object", "description": "Composer options.", "properties": { "process-timeout": { "type": "integer", "description": "The timeout in seconds for process executions, defaults to 300 (5mins)." }, "use-include-path": { "type": "boolean", "description": "If true, the Composer autoloader will also look for classes in the PHP include path." }, "preferred-install": { "type": ["string", "object"], "description": "The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or a hash of {\"pattern\": \"preference\"}." }, "notify-on-install": { "type": "boolean", "description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true." }, "github-protocols": { "type": "array", "description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"git\", \"https\", \"http\"].", "items": { "type": "string" } }, "github-oauth": { "type": "object", "description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.", "additionalProperties": { "type": "string" } }, "gitlab-oauth": { "type": "object", "description": "A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"<token>\"}.", "additionalProperties": { "type": "string" } }, "gitlab-token": { "type": "object", "description": "A hash of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}.", "additionalProperties": true }, "disable-tls": { "type": "boolean", "description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini." }, "secure-http": { "type": "boolean", "description": "Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative." }, "cafile": { "type": "string", "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically." }, "capath": { "type": "string", "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory." }, "http-basic": { "type": "object", "description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.", "additionalProperties": { "type": "object", "required": ["username", "password"], "properties": { "username": { "type": "string", "description": "The username used for HTTP Basic authentication" }, "password": { "type": "string", "description": "The password used for HTTP Basic authentication" } } } }, "store-auths": { "type": ["string", "boolean"], "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt." }, "platform": { "type": "object", "description": "This is a hash of package name (keys) and version (values) that will be used to mock the platform packages on this machine.", "additionalProperties": { "type": "string" } }, "vendor-dir": { "type": "string", "description": "The location where all packages are installed, defaults to \"vendor\"." }, "bin-dir": { "type": "string", "description": "The location where all binaries are linked, defaults to \"vendor/bin\"." }, "data-dir": { "type": "string", "description": "The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes." }, "cache-dir": { "type": "string", "description": "The location where all caches are located, defaults to \"~/.composer/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows." }, "cache-files-dir": { "type": "string", "description": "The location where files (zip downloads) are cached, defaults to \"{$cache-dir}/files\"." }, "cache-repo-dir": { "type": "string", "description": "The location where repo (git/hg repo clones) are cached, defaults to \"{$cache-dir}/repo\"." }, "cache-vcs-dir": { "type": "string", "description": "The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}/vcs\"." }, "cache-ttl": { "type": "integer", "description": "The default cache time-to-live, defaults to 15552000 (6 months)." }, "cache-files-ttl": { "type": "integer", "description": "The cache time-to-live for files, defaults to the value of cache-ttl." }, "cache-files-maxsize": { "type": ["string", "integer"], "description": "The cache max size for the files cache, defaults to \"300MiB\"." }, "bin-compat": { "enum": ["auto", "full"], "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"full\" (compatible with both Windows and Unix-based systems)." }, "discard-changes": { "type": ["string", "boolean"], "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"." }, "autoloader-suffix": { "type": "string", "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated." }, "optimize-autoloader": { "type": "boolean", "description": "Always optimize when dumping the autoloader." }, "prepend-autoloader": { "type": "boolean", "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." }, "classmap-authoritative": { "type": "boolean", "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false." }, "apcu-autoloader": { "type": "boolean", "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, defaults to false." }, "github-domains": { "type": "array", "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", "items": { "type": "string" } }, "github-expose-hostname": { "type": "boolean", "description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname." }, "gitlab-domains": { "type": "array", "description": "A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].", "items": { "type": "string" } }, "archive-format": { "type": "string", "description": "The default archiving format when not provided on cli, defaults to \"tar\"." }, "archive-dir": { "type": "string", "description": "The default archive path when not provided on cli, defaults to \".\"." }, "htaccess-protect": { "type": "boolean", "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories." } } }, "extra": { "type": ["object", "array"], "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.", "additionalProperties": true }, "autoload": { "$ref": "#/definitions/autoload" }, "autoload-dev": { "type": "object", "description": "Description of additional autoload rules for development purpose (eg. a test suite).", "properties": { "psr-0": { "type": "object", "description": "This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "psr-4": { "type": "object", "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "classmap": { "type": "array", "description": "This is an array of directories that contain classes to be included in the class-map generation process." }, "files": { "type": "array", "description": "This is an array of files that are always required on every request." } } }, "archive": { "type": ["object"], "description": "Options for creating package archives for distribution.", "properties": { "exclude": { "type": "array", "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark." } } }, "repositories": { "type": ["object", "array"], "description": "A set of additional repositories where packages can be found.", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/repository" }, { "type": "boolean", "enum": [false] } ] }, "items": { "oneOf": [ { "$ref": "#/definitions/repository" }, { "type": "object", "additionalProperties": { "type": "boolean", "enum": [false] }, "minProperties": 1, "maxProperties": 1 } ] } }, "minimum-stability": { "type": ["string"], "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.", "pattern": "^dev|alpha|beta|rc|RC|stable$" }, "prefer-stable": { "type": ["boolean"], "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages." }, "bin": { "type": ["array"], "description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } }, "include-path": { "type": ["array"], "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", "items": { "type": "string" } }, "scripts": { "type": ["object"], "description": "Scripts listeners that will be executed before/after some events.", "properties": { "pre-install-cmd": { "type": ["array", "string"], "description": "Occurs before the install command is executed, contains one or more Class::method callables or shell commands." }, "post-install-cmd": { "type": ["array", "string"], "description": "Occurs after the install command is executed, contains one or more Class::method callables or shell commands." }, "pre-update-cmd": { "type": ["array", "string"], "description": "Occurs before the update command is executed, contains one or more Class::method callables or shell commands." }, "post-update-cmd": { "type": ["array", "string"], "description": "Occurs after the update command is executed, contains one or more Class::method callables or shell commands." }, "pre-status-cmd": { "type": ["array", "string"], "description": "Occurs before the status command is executed, contains one or more Class::method callables or shell commands." }, "post-status-cmd": { "type": ["array", "string"], "description": "Occurs after the status command is executed, contains one or more Class::method callables or shell commands." }, "pre-package-install": { "type": ["array", "string"], "description": "Occurs before a package is installed, contains one or more Class::method callables or shell commands." }, "post-package-install": { "type": ["array", "string"], "description": "Occurs after a package is installed, contains one or more Class::method callables or shell commands." }, "pre-package-update": { "type": ["array", "string"], "description": "Occurs before a package is updated, contains one or more Class::method callables or shell commands." }, "post-package-update": { "type": ["array", "string"], "description": "Occurs after a package is updated, contains one or more Class::method callables or shell commands." }, "pre-package-uninstall": { "type": ["array", "string"], "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands." }, "post-package-uninstall": { "type": ["array", "string"], "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands." }, "pre-autoload-dump": { "type": ["array", "string"], "description": "Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands." }, "post-autoload-dump": { "type": ["array", "string"], "description": "Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands." }, "post-root-package-install": { "type": ["array", "string"], "description": "Occurs after the root-package is installed, contains one or more Class::method callables or shell commands." }, "post-create-project-cmd": { "type": ["array", "string"], "description": "Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands." } } }, "support": { "type": "object", "properties": { "email": { "type": "string", "description": "Email address for support.", "format": "email" }, "issues": { "type": "string", "description": "URL to the issue tracker.", "format": "uri" }, "forum": { "type": "string", "description": "URL to the forum.", "format": "uri" }, "wiki": { "type": "string", "description": "URL to the wiki.", "format": "uri" }, "irc": { "type": "string", "description": "IRC channel for support, as irc://server/channel.", "format": "uri" }, "source": { "type": "string", "description": "URL to browse or download the sources.", "format": "uri" }, "docs": { "type": "string", "description": "URL to the documentation.", "format": "uri" }, "rss": { "type": "string", "description": "URL to the RSS feed.", "format": "uri" } } }, "non-feature-branches": { "type": ["array"], "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.", "items": { "type": "string" } }, "abandoned": { "type": ["boolean", "string"], "description": "Indicates whether this package has been abandoned, it can be boolean or a package name/URL pointing to a recommended alternative. Defaults to false." }, "_comment": { "type": ["array", "string"], "description": "A key to store comments in" } }, "definitions": { "authors": { "type": "array", "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", "items": { "type": "object", "additionalProperties": false, "required": [ "name"], "properties": { "name": { "type": "string", "description": "Full name of the author." }, "email": { "type": "string", "description": "Email address of the author.", "format": "email" }, "homepage": { "type": "string", "description": "Homepage URL for the author.", "format": "uri" }, "role": { "type": "string", "description": "Author's role in the project." } } } }, "autoload": { "type": "object", "description": "Description of how the package can be autoloaded.", "properties": { "psr-0": { "type": "object", "description": "This is a hash of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "psr-4": { "type": "object", "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "classmap": { "type": "array", "description": "This is an array of directories that contain classes to be included in the class-map generation process." }, "files": { "type": "array", "description": "This is an array of files that are always required on every request." }, "exclude-from-classmap": { "type": "array", "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]" } } }, "repository": { "type": "object", "oneOf": [ { "$ref": "#/definitions/composer-repository" }, { "$ref": "#/definitions/vcs-repository" }, { "$ref": "#/definitions/path-repository" }, { "$ref": "#/definitions/artifact-repository" }, { "$ref": "#/definitions/pear-repository" }, { "$ref": "#/definitions/package-repository" } ] }, "composer-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["composer"] }, "url": { "type": "string" }, "options": { "type": "object", "additionalProperties": true }, "allow_ssl_downgrade": { "type": "boolean" }, "force-lazy-providers": { "type": "boolean" } } }, "vcs-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["vcs", "github", "git", "gitlab", "git-bitbucket", "hg", "hg-bitbucket", "fossil", "perforce", "svn"] }, "url": { "type": "string" }, "no-api": { "type": "boolean" }, "secure-http": { "type": "boolean" }, "svn-cache-credentials": { "type": "boolean" }, "trunk-path": { "type": ["string", "boolean"] }, "branches-path": { "type": ["string", "boolean"] }, "tags-path": { "type": ["string", "boolean"] }, "package-path": { "type": "string" }, "depot": { "type": "string" }, "branch": { "type": "string" }, "unique_perforce_client_name": { "type": "string" }, "p4user": { "type": "string" }, "p4password": { "type": "string" } } }, "path-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["path"] }, "url": { "type": "string" }, "options": { "type": "object", "properties": { "symlink": { "type": ["boolean", "null"] } }, "additionalProperties": true } } }, "artifact-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["artifact"] }, "url": { "type": "string" } } }, "pear-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["pear"] }, "url": { "type": "string" }, "vendor-alias": { "type": "string" } } }, "package-repository": { "type": "object", "required": ["type", "package"], "properties": { "type": { "type": "string", "enum": ["package"] }, "package": { "oneOf": [ { "$ref": "#/definitions/inline-package" }, { "type": "array", "items": { "type": { "$ref": "#/definitions/inline-package" } } } ] } } }, "inline-package": { "required": ["name", "version"], "properties": { "name": { "type": "string", "description": "Package name, including 'vendor-name/' prefix." }, "type": { "type": "string" }, "target-dir": { "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", "type": "string" }, "description": { "type": "string" }, "keywords": { "type": "array", "items": { "type": "string" } }, "homepage": { "type": "string", "format": "uri" }, "version": { "type": "string" }, "time": { "type": "string" }, "license": { "type": [ "string", "array" ] }, "authors": { "$ref": "#/definitions/authors" }, "require": { "type": "object", "additionalProperties": { "type": "string" } }, "replace": { "type": "object", "additionalProperties": { "type": "string" } }, "conflict": { "type": "object", "additionalProperties": { "type": "string" } }, "provide": { "type": "object", "additionalProperties": { "type": "string" } }, "require-dev": { "type": "object", "additionalProperties": { "type": "string" } }, "suggest": { "type": "object", "additionalProperties": { "type": "string" } }, "extra": { "type": ["object", "array"], "additionalProperties": true }, "autoload": { "$ref": "#/definitions/autoload" }, "archive": { "type": ["object"], "properties": { "exclude": { "type": "array" } } }, "bin": { "type": ["array"], "description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } }, "include-path": { "type": ["array"], "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", "items": { "type": "string" } }, "source": { "type": "object", "required": ["type", "url", "reference"], "properties": { "type": { "type": "string" }, "url": { "type": "string" }, "reference": { "type": "string" }, "mirrors": { "type": "array" } } }, "dist": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string" }, "url": { "type": "string" }, "reference": { "type": "string" }, "shasum": { "type": "string" }, "mirrors": { "type": "array" } } } }, "additionalProperties": true } } } ## ## ca-bundle.crt -- Bundle of CA Root Certificates ## ## Certificate data from Mozilla as of: Sat Dec 29 20:03:40 2012 ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: ## http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1 ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## # @(#) $RCSfile: certdata.txt,v $ $Revision: 1.87 $ $Date: 2012/12/29 16:32:45 $ EE Certification Centre Root CA =============================== -----BEGIN CERTIFICATE----- MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw 93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU 3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM dcGWxZ0= -----END CERTIFICATE----- GTE CyberTrust Global Root ========================== -----BEGIN CERTIFICATE----- MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ -----END CERTIFICATE----- Thawte Server CA ================ -----BEGIN CERTIFICATE----- MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl /Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= -----END CERTIFICATE----- Thawte Premium Server CA ======================== -----BEGIN CERTIFICATE----- MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf 8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t UCemDaYj+bvLpgcUQg== -----END CERTIFICATE----- Equifax Secure CA ================= -----BEGIN CERTIFICATE----- MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW 8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 70+sB3c4 -----END CERTIFICATE----- Digital Signature Trust Co. Global CA 1 ======================================= -----BEGIN CERTIFICATE----- MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMTAeFw05ODEy MTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUA A4GLADCBhwKBgQCgbIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJE NySZj9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlVSn5JTe2i o74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0 dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw IoAPMTk5ODEyMTAxODEwMjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQY MBaAFGp5fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i+DAM BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB ACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lNQseSJqBcNJo4cvj9axY+IO6CizEq kzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4 RbyhkwS7hp86W0N6w4pl -----END CERTIFICATE----- Digital Signature Trust Co. Global CA 3 ======================================= -----BEGIN CERTIFICATE----- MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMjAeFw05ODEy MDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUA A4GLADCBhwKBgQC/k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGOD VvsoLeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3oTQPMx7JS xhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0 dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw IoAPMTk5ODEyMDkxOTE3MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQY MBaAFB6CTShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5WzAM BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB AEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHRxdf0CiUPPXiBng+xZ8SQTGPdXqfi up/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVLB3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1 mPnHfxsb1gYgAlihw6ID -----END CERTIFICATE----- Verisign Class 3 Public Primary Certification Authority ======================================================= -----BEGIN CERTIFICATE----- MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf Tqj/ZA1k -----END CERTIFICATE----- Verisign Class 3 Public Primary Certification Authority - G2 ============================================================ -----BEGIN CERTIFICATE----- MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT 1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 -----END CERTIFICATE----- GlobalSign Root CA ================== -----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- GlobalSign Root CA - R2 ======================= -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp 9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu 01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- ValiCert Class 1 VA =================== -----BEGIN CERTIFICATE----- MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP Orf1LXLI -----END CERTIFICATE----- ValiCert Class 2 VA =================== -----BEGIN CERTIFICATE----- MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 W9ViH0Pd -----END CERTIFICATE----- RSA Root Certificate 1 ====================== -----BEGIN CERTIFICATE----- MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td 3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs 3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r on+jjBXu -----END CERTIFICATE----- Verisign Class 3 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj 055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC /Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== -----END CERTIFICATE----- Verisign Class 4 Public Primary Certification Authority - G3 ============================================================ -----BEGIN CERTIFICATE----- MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM 8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== -----END CERTIFICATE----- Entrust.net Secure Server CA ============================ -----BEGIN CERTIFICATE----- MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= -----END CERTIFICATE----- Entrust.net Premium 2048 Secure Server CA ========================================= -----BEGIN CERTIFICATE----- MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx NzUwNTFaFw0xOTEyMjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo3QwcjARBglghkgBhvhC AQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGAvtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdER gL7YibkIozH5oSQJFrlwMB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0B AQUFAAOCAQEAWUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQh7A6tcOdBTcS o8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18f3v/rxzP5tsHrV7bhZ3QKw0z 2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfNB/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjX OP/swNlQ8C5LWK5Gb9Auw2DaclVyvUxFnmG6v4SBkgPR0ml8xQ== -----END CERTIFICATE----- Baltimore CyberTrust Root ========================= -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- Equifax Secure Global eBusiness CA ================================== -----BEGIN CERTIFICATE----- MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV -----END CERTIFICATE----- Equifax Secure eBusiness CA 1 ============================= -----BEGIN CERTIFICATE----- MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ 1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ KpYrtWKmpj29f5JZzVoqgrI3eQ== -----END CERTIFICATE----- Equifax Secure eBusiness CA 2 ============================= -----BEGIN CERTIFICATE----- MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEXMBUGA1UE ChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0y MB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoT DkVxdWlmYXggU2VjdXJlMSYwJAYDVQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCB nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn 2Z0GvxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/BPO3QSQ5 BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0CAwEAAaOCAQkwggEFMHAG A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUx JjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoG A1UdEAQTMBGBDzIwMTkwNjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9e uSBIplBqy/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQFMAMB Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAAyGgq3oThr1 jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia 78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUm V+GRMOrN -----END CERTIFICATE----- AddTrust Low-Value Services Root ================================ -----BEGIN CERTIFICATE----- MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= -----END CERTIFICATE----- AddTrust External Root ====================== -----BEGIN CERTIFICATE----- MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 +iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy 2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE----- AddTrust Public Services Root ============================= -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB /zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL +YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= -----END CERTIFICATE----- AddTrust Qualified Certificates Root ==================================== -----BEGIN CERTIFICATE----- MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx 64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= -----END CERTIFICATE----- Entrust Root Certification Authority ==================================== -----BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- RSA Security 2048 v3 ==================== -----BEGIN CERTIFICATE----- MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP +Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj 0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA pKnXwiJPZ9d37CAFYd4= -----END CERTIFICATE----- GeoTrust Global CA ================== -----BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet 8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm Mw== -----END CERTIFICATE----- GeoTrust Global CA 2 ==================== -----BEGIN CERTIFICATE----- MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF H4z1Ir+rzoPz4iIprn2DQKi6bA== -----END CERTIFICATE----- GeoTrust Universal CA ===================== -----BEGIN CERTIFICATE----- MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs 7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d 8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI P/rmMuGNG2+k5o7Y+SlIis5z/iw= -----END CERTIFICATE----- GeoTrust Universal CA 2 ======================= -----BEGIN CERTIFICATE----- MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP 20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG 8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 +/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ 4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- America Online Root Certification Authority 1 ============================================= -----BEGIN CERTIFICATE----- MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP 8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft 3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 -----END CERTIFICATE----- America Online Root Certification Authority 2 ============================================= -----BEGIN CERTIFICATE----- MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn 6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p +DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh 1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= -----END CERTIFICATE----- Visa eCommerce Root =================== -----BEGIN CERTIFICATE----- MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI /k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt 398znM/jra6O1I7mT1GvFpLgXPYHDw== -----END CERTIFICATE----- Certum Root CA ============== -----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ 89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ 0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== -----END CERTIFICATE----- Comodo AAA Services root ======================== -----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm 7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z 8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C 12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- Comodo Secure Services root =========================== -----BEGIN CERTIFICATE----- MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP 9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm 4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H RR3B7Hzs/Sk= -----END CERTIFICATE----- Comodo Trusted Services root ============================ -----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y /9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB /zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O 9y5Xt5hwXsjEeLBi -----END CERTIFICATE----- QuoVadis Root CA ================ -----BEGIN CERTIFICATE----- MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi 5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi 5nrQNiOKSnQ2+Q== -----END CERTIFICATE----- QuoVadis Root CA 2 ================== -----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt 66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK +JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II 4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u -----END CERTIFICATE----- QuoVadis Root CA 3 ================== -----BEGIN CERTIFICATE----- MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp 8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- Security Communication Root CA ============================== -----BEGIN CERTIFICATE----- MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw 8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX 5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g 0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ 6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi FL39vmwLAw== -----END CERTIFICATE----- Sonera Class 2 Root CA ====================== -----BEGIN CERTIFICATE----- MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 /Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt 0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH llpwrN9M -----END CERTIFICATE----- Staat der Nederlanden Root CA ============================= -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== -----END CERTIFICATE----- TDC Internet Root CA ==================== -----BEGIN CERTIFICATE----- MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc 5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ 2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l -----END CERTIFICATE----- UTN DATACorp SGC Root CA ======================== -----BEGIN CERTIFICATE----- MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA 9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv 33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI -----END CERTIFICATE----- UTN USERFirst Hardware Root CA ============================== -----BEGIN CERTIFICATE----- MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM //bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 nfhmqA== -----END CERTIFICATE----- Camerfirma Chambers of Commerce Root ==================================== -----BEGIN CERTIFICATE----- MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 erfutGWaIZDgqtCYvDi1czyL+Nw= -----END CERTIFICATE----- Camerfirma Global Chambersign Root ================================== -----BEGIN CERTIFICATE----- MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J 1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl 6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c 8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== -----END CERTIFICATE----- NetLock Notary (Class A) Root ============================= -----BEGIN CERTIFICATE----- MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC /tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM 8CgHrTwXZoi1/baI -----END CERTIFICATE----- NetLock Business (Class B) Root =============================== -----BEGIN CERTIFICATE----- MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr 1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM 43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI -----END CERTIFICATE----- NetLock Express (Class C) Root ============================== -----BEGIN CERTIFICATE----- MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== -----END CERTIFICATE----- XRamp Global CA Root ==================== -----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc /Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz 8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= -----END CERTIFICATE----- Go Daddy Class 2 CA =================== -----BEGIN CERTIFICATE----- MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv 2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b vZ8= -----END CERTIFICATE----- Starfield Class 2 CA ==================== -----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- StartCom Certification Authority ================================ -----BEGIN CERTIFICATE----- MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt 2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z 6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT 37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh 3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl 1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro g14= -----END CERTIFICATE----- Taiwan GRCA =========== -----BEGIN CERTIFICATE----- MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O 1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk 7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy +fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS -----END CERTIFICATE----- Firmaprofesional Root CA ======================== -----BEGIN CERTIFICATE----- MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMxIjAgBgNVBAcT GUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1dG9yaWRhZCBkZSBDZXJ0aWZp Y2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FA ZmlybWFwcm9mZXNpb25hbC5jb20wHhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTEL MAkGA1UEBhMCRVMxIjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMT OUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2 ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20wggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5uCp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5V j1H5WuretXDE7aTt/6MNbg9kUDGvASdYrv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJH lShbz++AbOCQl4oBPB3zhxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf 3H5idPayBQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcLiam8 NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcbAgMBAAGjgZ8wgZww KgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lvbmFsLmNvbTASBgNVHRMBAf8ECDAG AQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1Ud DwEB/wQEAwIBBjAdBgNVHQ4EFgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQAD ggEBAEdz/o0nVPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36mhoEyIwOdyPdf wUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzflZKG+TQyTmAyX9odtsz/ny4Cm 7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBpQWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YG VM+h4k0460tQtcsm9MracEpqoeJ5quGnM/b9Sh/22WA= -----END CERTIFICATE----- Wells Fargo Root CA =================== -----BEGIN CERTIFICATE----- MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMCVVMxFDASBgNV BAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhv cml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN MDAxMDExMTY0MTI4WhcNMjEwMTE0MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dl bGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEv MC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n135zHCLielTWi5MbqNQ1mX x3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHESxP9cMIlrCL1dQu3U+SlK93OvRw6esP3 E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4OJgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5 OEL8pahbSCOz6+MlsoCultQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4j sNtlAHCEAQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMBAAGj YTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcBCzAyMDAGCCsGAQUF BwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRwb2xpY3kwDQYJKoZIhvcNAQEFBQAD ggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrv m+0fazbuSCUlFLZWohDo7qd/0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0R OhPs7fpvcmR7nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ33ZwmVxwQ023 tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s= -----END CERTIFICATE----- Swisscom Root CA 1 ================== -----BEGIN CERTIFICATE----- MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn 7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW NY6E0F/6MBr1mmz0DlP5OlvRHA== -----END CERTIFICATE----- DigiCert Assured ID Root CA =========================== -----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO 9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW /lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF 66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i 8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== -----END CERTIFICATE----- DigiCert Global Root CA ======================= -----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H 4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y 7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm 8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- DigiCert High Assurance EV Root CA ================================== -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- Certplus Class 2 Primary CA =========================== -----BEGIN CERTIFICATE----- MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR 5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ 7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW //1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 l7+ijrRU -----END CERTIFICATE----- DST Root CA X3 ============== -----BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- DST ACES CA X6 ============== -----BEGIN CERTIFICATE----- MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 oKfN5XozNmr6mis= -----END CERTIFICATE----- TURKTRUST Certificate Services Provider Root 1 ============================================== -----BEGIN CERTIFICATE----- MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ 8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H -----END CERTIFICATE----- TURKTRUST Certificate Services Provider Root 2 ============================================== -----BEGIN CERTIFICATE----- MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr 5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P 9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 UrbnBEI= -----END CERTIFICATE----- SwissSign Gold CA - G2 ====================== -----BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR 7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm 5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr 44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- SwissSign Silver CA - G2 ======================== -----BEGIN CERTIFICATE----- MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm +/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH 6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P 4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L 3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx /uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- GeoTrust Primary Certification Authority ======================================== -----BEGIN CERTIFICATE----- MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG 1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= -----END CERTIFICATE----- thawte Primary Root CA ====================== -----BEGIN CERTIFICATE----- MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ 1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== -----END CERTIFICATE----- VeriSign Class 3 Public Primary Certification Authority - G5 ============================================================ -----BEGIN CERTIFICATE----- MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq -----END CERTIFICATE----- SecureTrust CA ============== -----BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b 01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR 3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= -----END CERTIFICATE----- Secure Global CA ================ -----BEGIN CERTIFICATE----- MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g 8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi 0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW -----END CERTIFICATE----- COMODO Certification Authority ============================== -----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH +7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV 4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA 1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN +8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== -----END CERTIFICATE----- Network Solutions Certificate Authority ======================================= -----BEGIN CERTIFICATE----- MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc /Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q 4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey -----END CERTIFICATE----- WellsSecure Public Root Certificate Authority ============================================= -----BEGIN CERTIFICATE----- MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ tylv2G0xffX8oRAHh84vWdw+WNs= -----END CERTIFICATE----- COMODO ECC Certification Authority ================================== -----BEGIN CERTIFICATE----- MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X 4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- IGC/A ===== -----BEGIN CERTIFICATE----- MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF 0mBWWg== -----END CERTIFICATE----- Security Communication EV RootCA1 ================================= -----BEGIN CERTIFICATE----- MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO /VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK 9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 -----END CERTIFICATE----- OISTE WISeKey Global Root GA CA =============================== -----BEGIN CERTIFICATE----- MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ /yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 +vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= -----END CERTIFICATE----- Microsec e-Szigno Root CA ========================= -----BEGIN CERTIFICATE----- MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA 4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a 86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= -----END CERTIFICATE----- Certigna ======== -----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY 1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- AC Ra\xC3\xADz Certic\xC3\xA1mara S.A. ====================================== -----BEGIN CERTIFICATE----- MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU 2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP 2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm 8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK 5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v /zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f /RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== -----END CERTIFICATE----- TC TrustCenter Class 2 CA II ============================ -----BEGIN CERTIFICATE----- MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB /wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB 7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk vQ== -----END CERTIFICATE----- TC TrustCenter Class 3 CA II ============================ -----BEGIN CERTIFICATE----- MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo 6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk 2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB /wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB 7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal 092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc 5A== -----END CERTIFICATE----- TC TrustCenter Universal CA I ============================= -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG 1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a 7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY -----END CERTIFICATE----- Deutsche Telekom Root CA 2 ========================== -----BEGIN CERTIFICATE----- MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU Cm26OWMohpLzGITY+9HPBVZkVw== -----END CERTIFICATE----- ComSign Secured CA ================== -----BEGIN CERTIFICATE----- MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs 49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH 7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP 51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== -----END CERTIFICATE----- Cybertrust Global Root ====================== -----BEGIN CERTIFICATE----- MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW 0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin 89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT 8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi 5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW WL1WMRJOEcgh4LMRkWXbtKaIOM5V -----END CERTIFICATE----- ePKI Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX 12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= -----END CERTIFICATE----- T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 ============================================================================================================================= -----BEGIN CERTIFICATE----- MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR 6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= -----END CERTIFICATE----- Buypass Class 2 CA 1 ==================== -----BEGIN CERTIFICATE----- MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV 1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt 7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho -----END CERTIFICATE----- Buypass Class 3 CA 1 ==================== -----BEGIN CERTIFICATE----- MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1 MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0 n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c 1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7 pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5 htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 -----END CERTIFICATE----- EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 ========================================================================== -----BEGIN CERTIFICATE----- MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB /wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK 1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt 2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT -----END CERTIFICATE----- certSIGN ROOT CA ================ -----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD 0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD -----END CERTIFICATE----- CNNIC ROOT ========== -----BEGIN CERTIFICATE----- MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m mxE= -----END CERTIFICATE----- ApplicationCA - Japanese Government =================================== -----BEGIN CERTIFICATE----- MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g /DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL rosot4LKGAfmt1t06SAZf7IbiVQ= -----END CERTIFICATE----- GeoTrust Primary Certification Authority - G3 ============================================= -----BEGIN CERTIFICATE----- MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr 2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt -----END CERTIFICATE----- thawte Primary Root CA - G2 =========================== -----BEGIN CERTIFICATE----- MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== -----END CERTIFICATE----- thawte Primary Root CA - G3 =========================== -----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC +BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY 7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC 8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= -----END CERTIFICATE----- GeoTrust Primary Certification Authority - G2 ============================================= -----BEGIN CERTIFICATE----- MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 npaqBA+K -----END CERTIFICATE----- VeriSign Universal Root Certification Authority =============================================== -----BEGIN CERTIFICATE----- MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj 1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 mJO37M2CYfE45k+XmCpajQ== -----END CERTIFICATE----- VeriSign Class 3 Public Primary Certification Authority - G4 ============================================================ -----BEGIN CERTIFICATE----- MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB /zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== -----END CERTIFICATE----- NetLock Arany (Class Gold) FÅ‘tanúsítvány ============================================ -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu 0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw /HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- Staat der Nederlanden Root CA - G2 ================================== -----BEGIN CERTIFICATE----- MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ 5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz +51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm 66+KAQ== -----END CERTIFICATE----- CA Disig ======== -----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA 4Z7CRneC9VkGjCFMhwnN5ag= -----END CERTIFICATE----- Juur-SK ======= -----BEGIN CERTIFICATE----- MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC +Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 yyqcjg== -----END CERTIFICATE----- Hongkong Post Root CA 1 ======================= -----BEGIN CERTIFICATE----- MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== -----END CERTIFICATE----- SecureSign RootCA11 =================== -----BEGIN CERTIFICATE----- MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= -----END CERTIFICATE----- ACEDICOM Root ============= -----BEGIN CERTIFICATE----- MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz 4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU 9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----END CERTIFICATE----- Verisign Class 3 Public Primary Certification Authority ======================================================= -----BEGIN CERTIFICATE----- MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/ D/xwzoiQ -----END CERTIFICATE----- Microsec e-Szigno Root CA 2009 ============================== -----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG 0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm 1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi LXpUq3DDfSJlgnCW -----END CERTIFICATE----- E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi =================================================== -----BEGIN CERTIFICATE----- MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3 MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0 cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY 8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk 9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4 Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX -----END CERTIFICATE----- GlobalSign Root CA - R3 ======================= -----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ 0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r kpeDMdmztcpHWD9f -----END CERTIFICATE----- TC TrustCenter Universal CA III =============================== -----BEGIN CERTIFICATE----- MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezELMAkGA1UEBhMC REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy IFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAe Fw0wOTA5MDkwODE1MjdaFw0yOTEyMzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNU QyBUcnVzdENlbnRlciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0Ex KDAmBgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF5+cvAqBNLaT6hdqbJYUt QCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYvDIRlzg9uwliT6CwLOunBjvvya8o84pxO juT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8vzArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+Eut CHnNaYlAJ/Uqwa1D7KRTyGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1 M4BDj5yjdipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBhMB8G A1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI4jANBgkqhkiG9w0BAQUFAAOCAQEA g8ev6n9NCjw5sWi+e22JLumzCecYV42FmhfzdkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+ KGwWaODIl0YgoGhnYIg5IFHYaAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhK BgePxLcHsU0GDeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPHLQNjO9Po5KIq woIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg== -----END CERTIFICATE----- Autoridad de Certificacion Firmaprofesional CIF A62634068 ========================================================= -----BEGIN CERTIFICATE----- MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY 7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx 51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi 6Et8Vcad+qMUu2WFbm5PEn4KPJ2V -----END CERTIFICATE----- Izenpe.com ========== -----BEGIN CERTIFICATE----- MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ 03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU +zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK 0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ 0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- Chambers of Commerce Root - 2008 ================================ -----BEGIN CERTIFICATE----- MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ 0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH 3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF 9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ -----END CERTIFICATE----- Global Chambersign Root - 2008 ============================== -----BEGIN CERTIFICATE----- MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB /gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp 1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG /5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg 9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z 09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B -----END CERTIFICATE----- Go Daddy Root Certificate Authority - G2 ======================================== -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq 9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD +qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r 5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 -----END CERTIFICATE----- Starfield Root Certificate Authority - G2 ========================================= -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx 4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 -----END CERTIFICATE----- Starfield Services Root Certificate Authority - G2 ================================================== -----BEGIN CERTIFICATE----- MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 -----END CERTIFICATE----- AffirmTrust Commercial ====================== -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv 0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= -----END CERTIFICATE----- AffirmTrust Networking ====================== -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 /PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 /ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= -----END CERTIFICATE----- AffirmTrust Premium =================== -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV 5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs +7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 /bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo +Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB /wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC 6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK +4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== -----END CERTIFICATE----- AffirmTrust Premium ECC ======================= -----BEGIN CERTIFICATE----- MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X 57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM eQ== -----END CERTIFICATE----- Certum Trusted Network CA ========================= -----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- Certinomis - Autorité Racine ============================= -----BEGIN CERTIFICATE----- MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw 2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g 530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna 4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ vgt2Fl43N+bYdJeimUV5 -----END CERTIFICATE----- Root CA Generalitat Valenciana ============================== -----BEGIN CERTIFICATE----- MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt +GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= -----END CERTIFICATE----- A-Trust-nQual-03 ================ -----BEGIN CERTIFICATE----- MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 ahq97BvIxYSazQ== -----END CERTIFICATE----- TWCA Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP 4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG 9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== -----END CERTIFICATE----- Security Communication RootCA2 ============================== -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ +T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R 3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk 3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- EC-ACC ====== -----BEGIN CERTIFICATE----- MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw 0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D 5EI= -----END CERTIFICATE----- Hellenic Academic and Research Institutions RootCA 2011 ======================================================= -----BEGIN CERTIFICATE----- MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI 1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa 71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u 8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH 3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD /md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N 7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 -----END CERTIFICATE----- Actalis Authentication Root CA ============================== -----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC 4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo 2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- Trustis FPS Root CA =================== -----BEGIN CERTIFICATE----- MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P 8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl iB6XzCGcKQENZetX2fNXlrtIzYE= -----END CERTIFICATE----- StartCom Certification Authority ================================ -----BEGIN CERTIFICATE----- MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt 2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z 6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT 37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA 2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= -----END CERTIFICATE----- StartCom Certification Authority G2 =================================== -----BEGIN CERTIFICATE----- MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG 4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K 2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG /+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm 7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm obp573PYtlNXLfbQ4ddI -----END CERTIFICATE----- Buypass Class 2 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn 9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b /+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN rJgWVqA= -----END CERTIFICATE----- Buypass Class 3 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR 5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh 7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH 2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV /afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz 6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi Cp/HuZc= -----END CERTIFICATE----- T-TeleSec GlobalRoot Class 3 ============================ -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK 9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W 0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== -----END CERTIFICATE----- 1.4.1QGeݛ-fS���GBMB